文章总结: 该文档详细记录了针对BVES虚拟逆向挑战的完整分析过程,作者通过逆向分析exe和bvs文件,利用IDA、010Editor等工具解析程序结构,追踪文件读取操作,最终成功解密出Flag。文章提供了具体的逆向技术方法和关键步骤,属于典型的CTF二进制逆向工程实战经验分享。 综合评分: 85 文章分类: 逆向分析,二进制安全,CTF,漏洞分析,技术标准
BVES 虚拟逆向:从 Opcode 到 Flag 的完整分析
原创
小张 小张
网络安全研习社
2026年5月15日 15:25 陕西
在小说阅读器读本章
去阅读
免责声明:涉及到的所有技术仅用来学习交流,严禁用于非法用途,未经授权请勿非法渗透,否则产生的一切后果自行承担,如有侵权,请及时联系删帖!
一、前言
下载链接:https://crackmes.one/crackme/69ffdc47d7ff92e1214c0079
二、正文
这道题给了我们两个文件,一个exe一个bvs文件。作者也给了提示如下:
使用bvessel可执行文件运行随附的.bvs 文件。接下来我们执行一下
通过运行给出了很明显的提示in.txt,以及三个变量
我们用ida查看exe,010查看bvs文件,如下:
该程序中没有main函数,且start函数看着不像如下:
__int64 sub_140001075(){ signed __int64 StackBase_2; // rcx signed __int64 *v1; // rdx int v3; // [rsp+3Ch] [rbp-54h] BYREF __int64 v4; // [rsp+40h] [rbp-50h] signed __int64 *v5; // [rsp+48h] [rbp-48h] __int64 v6; // [rsp+50h] [rbp-40h] signed __int64 StackBase_1; // [rsp+58h] [rbp-38h] signed __int64 *v8; // [rsp+60h] [rbp-30h] struct _TEB *v9; // [rsp+68h] [rbp-28h] int n48; // [rsp+70h] [rbp-20h] int Code; // [rsp+74h] [rbp-1Ch] PVOID StackBase; // [rsp+78h] [rbp-18h] signed __int64 StackBase_3; // [rsp+80h] [rbp-10h] int v14; // [rsp+8Ch] [rbp-4h] StackBase_3 = 0; n48 = 48; v9 = NtCurrentTeb(); StackBase = v9->NtTib.StackBase; v14 = 0; Code = 0; while ( 1 ) { v8 = &qword_140010098; StackBase_1 = (signed __int64)StackBase; v6 = 0; StackBase_2 = (signed __int64)StackBase; v1 = &qword_140010098; StackBase_3 = _InterlockedCompareExchange64(&qword_140010098, (signed __int64)StackBase, 0); if ( !StackBase_3 ) break; if ( (PVOID)StackBase_3 == StackBase ) { v14 = 1; break; } Sleep(0x3E8u); } if ( n2 == 1 ) amsg_exit(31); if ( n2 ) { dword_14001001C = 1; } else { n2 = 1; sub_1400023B0(); qword_140010110 = (__int64)SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)&lpTopLevelExceptionFilter_); sub_1400095C0(sub_140001000); sub_140002A80(); dword_140010018 = sub_140001398(); if ( unk_1400100D0 ) _set_app_type(_crt_gui_app); else _set_app_type(_crt_console_app); *(_DWORD *)sub_140009470() = unk_140010100; *(_DWORD *)sub_140009480() = unk_1400100C0; Code = sub_140001990(); if ( Code < 0 ) amsg_exit(8); if ( unk_14000A060 == 1 ) sub_1400024CA(sub_140001AA0); if ( unk_14000A040 == -1 ) sub_140009600(0xFFFFFFFFLL); if ( (unsigned int)sub_140009410(&unk_14000C928, &unk_14000C930) ) return 255; v3 = unk_1400100A0; Code = _getmainargs(&dword_140010004, &qword_140010008, &qword_140010010, unk_14000A030, &v3); if ( Code < 0 ) amsg_exit(8); Code = sub_14000149C((unsigned int)dword_140010004, &qword_140010008); if ( Code ) amsg_exit(8); initterm(&First_, &Last_); sub_140001967(); n2 = 2; } if ( !v14 ) { v5 = &qword_140010098; v4 = 0; v1 = (signed __int64 *)_InterlockedExchange64(&qword_140010098, 0); } if ( TlsCallback_0 ) TlsCallback_0(0, 2, 0); *(_QWORD *)sub_140009490(StackBase_2, v1) = qword_140010010; Code = sub_140009820((unsigned int)dword_140010004, qword_140010008, qword_140010010); if ( !dword_140010018 ) exit(Code); if ( !dword_14001001C ) cexit(); return (unsigned int)Code;}
不像主逻辑,当然除了看函数还可以看导入表跟string表,同时我们通过bvs中可以看到读取文件的操作,可以通过fopen()也来追踪。
其中这个里面很明显的主程序字符串–debug/IMPEXT/invalid bvs等,跟进快速定位到函数
__int64 sub_140009820(){ int n3_1; // ecx int n3; // ebx __int64 v2; // rdx __int64 v3; // rsi _DWORD *v4; // rdi __int64 i; // rcx Stream *Stream; // rsi unsigned int v7; // edi __int64 v8; // rax _BYTE *v9; // r14 int n_3; // r13d int n62; // r15d __int64 j; // r12 int j_1; // ebp int n62_1; // eax unsigned __int64 n512_1; // rbp unsigned __int64 n512; // r12 size_t Size; // rdx _DWORD *v18; // rcx __int64 k; // rax char *v20; // rax __int64 v21; // r15 char v22; // cl char *v23; // rdx __int64 m; // r14 __int64 v25; // rdx int n_8; // ebp char *v27; // rdx int n_6; // esi __int64 (__fastcall *psub_140009620)(); // r13 __int64 v30; // rax int n_7; // r8d _BYTE *v32; // rsi const char *mutable; // r14 __int64 v34; // r15 __int64 v35; // rax const char *v36; // r9 int v37; // r8d const char *v38; // rbx int n; // esi __int64 n_4; // rsi __int64 v41; // rsi char *FileName; // r15 Stream *Stream_1; // rax Stream *Stream_2; // r14 size_t v45; // rax __int16 v46; // ax __int64 v47; // r13 __int64 v48; // rsi char *Destination; // rcx __int64 v50; // rax __int64 v51; // r8 __int64 n6; // r8 __int64 n_5; // rdx const char *v54; // r8 int n_1; // edx unsigned __int16 Buffer_; // [rsp+3Ah] [rbp-7F86Eh] BYREF _BYTE Buffer[4]; // [rsp+3Ch] [rbp-7F86Ch] BYREF _BYTE v59[8]; // [rsp+40h] [rbp-7F868h] BYREF _BYTE v60[522224]; // [rsp+48h] [rbp-7F860h] BYREF int n_2; // [rsp+7F838h] [rbp-70h] char *v62; // [rsp+7F840h] [rbp-68h] unsigned __int64 n512_2; // [rsp+7F848h] [rbp-60h] unsigned __int64 n512_3; // [rsp+7F850h] [rbp-58h] int n_9; // [rsp+7F858h] [rbp-50h] int v66; // [rsp+7F85Ch] [rbp-4Ch] _BYTE v67[40]; // [rsp+7F860h] [rbp-48h] BYREF sub_140002F40(); n3 = n3_1; v3 = v2; sub_140001967(); if ( n3 <= 1 ) { return 1; } else { v4 = v59; for ( i = 130568; i; --i ) *v4++ = 0; if ( n3 == 3 && !strcmp(*(const char **)(v3 + 16), "--debug") ) v66 = 1; Stream = fopen(*(const char **)(v3 + 8), "rb"); if ( fread(Buffer, 1u, 4u, Stream) != 4 || (v7 = memcmp(Buffer, "BVES", 4u)) != 0 ) { v8 = psub_140009620(); sub_140002F80(v8, "bvessel error: %s\n", "invalid bvs"); exit(1); } v9 = v59; n_3 = 0; v59[0] = fgetc(Stream); fgetc(Stream); n_2 = (unsigned __int8)fgetc(Stream) >> 1; while ( n_3 < n_2 ) { n62 = 0; for ( j = 0; ; v9[j + 7] = n62_1 ) { j_1 = j; n62_1 = fgetc(Stream); if ( n62_1 == -1 ) break; if ( n62 == 62 && n62_1 == 62 ) { j_1 = j - 1; v67[4112 * n_3 - 522265 + (int)j] = 0; break; } ++j; n62 = n62_1; } ++n_3; v9 += 4112; *((_QWORD *)v9 - 1) = j_1; } n512_1 = 0; n512 = 512; v62 = (char *)malloc(0x3800u); while ( fread(&Buffer_, 2u, 1u, Stream) == 1 ) { if ( n512_1 >= n512 ) { Size = 56 * n512; n512 *= 2LL; v62 = (char *)realloc(v62, Size); } v18 = &unk_14000B180; for ( k = 0; k != 14; ++k ) { if ( *v18 == Buffer_ ) { v20 = (char *)&unk_14000B180 + 16 * k; goto LABEL_29; } v18 += 4; } v20 = 0;LABEL_29: v21 = 28 * n512_1; v22 = 0; v23 = &v62[28 * n512_1]; *(_DWORD *)v23 = Buffer_; if ( v20 ) v22 = v20[4]; v23[4] = v22; for ( m = 0; (unsigned __int8)v62[v21 + 4] > (int)m; ++m ) { v25 = 2 * m; fread(&v62[v21 + 6 + v25], 2u, 1u, Stream); } ++n512_1; } n512_2 = n512_1; fclose(Stream); while ( 1 ) { n_8 = n_9; if ( n_9 || n512_3 >= n512_2 ) break; v27 = &v62[28 * n512_3++]; if ( !*((_DWORD *)v27 + 6) ) { switch ( *(_DWORD *)v27 ) { case 1: n_9 = 1; goto LABEL_47; case 2: n_4 = *((_WORD *)v27 + 3) & 0x7FFF; if ( (int)n_4 < n_2 ) { v41 = 4112 * n_4; if ( !v60[v41 + 4104] ) { FileName = &v59[4112 * (*((_WORD *)v27 + 3) & 0x7FFF) + 8]; Stream_1 = fopen(FileName, "rb"); Stream_2 = Stream_1; if ( Stream_1 ) { v45 = fread(FileName, 1u, 0xFFFu, Stream_1); v60[v41 + 4104] = 1; *(_QWORD *)&v60[v41 + 4096] = v45; v67[v41 - 522264 + v45] = 0; fclose(Stream_2); } } } break; case 3: v46 = *((_WORD *)v27 + 4); if ( v46 >= 0 ) { v47 = 4112LL * (unsigned __int16)v46; if ( !v60[v47 + 4104] ) { v48 = *((unsigned __int16 *)v27 + 3); Destination = &v59[v47 + 8]; if ( (v48 & 0x8000u) == 0LL ) { strcpy(Destination, &v59[4112 * (unsigned __int16)v48 + 8]); v50 = *(_QWORD *)&v60[4112 * v48 + 4096]; } else { v50 = (int)sub_140005C70(Destination, 4096, "%u", v48 & 0x7FFF); } *(_QWORD *)&v60[v47 + 4096] = v50; } } break; case 4: v51 = 0; goto LABEL_60; case 5: v51 = 1;LABEL_60: ((void (__fastcall *)(_BYTE *, char *, __int64))sub_14000174B)(v59, v27, v51); break; case 6: n6 = 6; goto LABEL_63; case 7: n6 = 0; goto LABEL_63; case 8: n6 = 2; goto LABEL_63; case 9: n6 = 1; goto LABEL_63; case 0xA: n6 = 3; goto LABEL_63; case 0xB: n6 = 4; goto LABEL_63; case 0xC: n6 = 5;LABEL_63: ((void (__fastcall *)(_BYTE *, char *, __int64))sub_1400017EF)(v59, v27, n6); break; case 0xD: n_5 = *((unsigned __int16 *)v27 + 3); if ( (n_5 & 0x8000u) == 0LL ) { if ( (unsigned __int16)n_5 < n_2 ) puts(&v59[4112 * n_5 + 8]); } else { sub_140005BF0("%u\n", n_5 & 0x7FFF); } break; default: break; } } if ( v66 ) { n_6 = n_2; psub_140009620 = psub_140009620; v30 = psub_140009620(); n_7 = n_6; v32 = v59; sub_140002F80(v30, " vars (%d):\n", n_7); while ( n_8 < n_2 ) { mutable = "mutable"; if ( v32[4112] ) mutable = "frozen"; v34 = *((_QWORD *)v32 + 513); v35 = ((__int64 (__fastcall *)(__int64))psub_140009620)(2); v36 = v32 + 8; v37 = n_8++; v32 += 4112; sub_140002F80(v35, " [%d] %s (len %zu, %s)\n", v37, v36, v34, mutable); } } }LABEL_47: v38 = v60; for ( n = 0; n_2 > n; ++n ) { v54 = v38; n_1 = n; v38 += 4112; sub_140005BF0("Var[%d]: %s\n", n_1, v54); } free(v62); } return v7;}
对于这个函数我们就找到了主要逻辑点了,
以下是参数校验,以及变量提取
sub_140002F40(); n3 = n3_1; // 变量个数 ______ = _______1; // 存储值-参数 sub_140001967(); if ( n3 <= 1 ) // 参数不够直接推 { return 1; } else { _____4 = _____2; for ( i = 130568; i; --i ) *_____4++ = 0; if ( n3 == 3 && !strcmp(*(const char **)(______ + 16), "--debug") )// 看第三个参数是否是--debug ____ = 1; // 调试开关 Stream = fopen(*(const char **)(______ + 8), "rb");// 读取第二个参数-bvs文件 if ( fread(Buffer, 1u, 4u, Stream) != 4 || (v7 = memcmp(Buffer, "BVES", 4u)) != 0 )// 对应bvs四个字节 检验魔数 { v8 = ::psub_140009620(); sub_140002F80(v8, "bvessel error: %s\n", "invalid bvs"); exit(1); } _____1 = _____2; // 变量指针 n_3 = 0; // 变量个数-文件 _____2[0] = fgetc(Stream); // 读一个字节对应01 fgetc(Stream); // 再读一个不处理 n_2 = (unsigned __int8)fgetc(Stream) >> 1; // 读取到06再右移一位到3 while ( n_3 < n_2 ) // 参数读取循环三次 { n62 = 0; // 上一个字符 for ( j = 0; ; _____1[j + 7] = n62_1 ) // 把当前读到的字符写进当前变量字符串区 { j_1 = j; // 保存当前索引 n62_1 = fgetc(Stream); if ( n62_1 == -1 ) break; if ( n62 == '>' && n62_1 == '>' ) // 读取到>>这个进入 { j_1 = j - 1; // 去掉前面那个多余的 > v67[4112 * n_3 - 522265 + (int)j] = 0;// 在字符串末尾补 \0 break; } ++j; n62 = n62_1; // 把当前字符保存为“上一个字符”,供下一轮判断 >> } ++n_3; _____1 += 4112; // 跳到下一个变量槽 *((_QWORD *)_____1 - 1) = j_1; }
这一整段读完后的结果
你的样本会得到:
Var[0] = "in.txt"Var[1] = "you failure"Var[2] = "you winner"
接下来
n512_1 = 0; // 当前指令数 n512 = 512; // 初始容量 v62 = (char *)malloc(0x3800u); // 分布内存 while ( fread(&Buffer_, 2u, 1u, Stream) == 1 )// 每次读 1 个元素每个元素 2 字节 { if ( n512_1 >= n512 ) // 扩容 { Size = 56 * n512; n512 *= 2LL; v62 = (char *)realloc(v62, Size); } v18 = &unk_14000B180; // opcode 表查找,通过当前读取到的opcode对应指令 for ( k = 0; k != 14; ++k ) { if ( *v18 == Buffer_ ) { v20 = (char *)&unk_14000B180 + 16 * k; goto LABEL_29; } v18 += 4; } v20 = 0;LABEL_29: // 把当前指令保存到内部结构 v21 = 28 * n512_1; v22 = 0; v23 = &v62[28 * n512_1]; *(_DWORD *)v23 = Buffer_; if ( v20 ) v22 = v20[4]; v23[4] = v22; for ( m = 0; (unsigned __int8)v62[v21 + 4] > (int)m; ++m )// 读取指令参数 { v25 = 2 * m; fread(&v62[v21 + 6 + v25], 2u, 1u, Stream); } ++n512_1; // 当前指令数加一 }
这里就是转换opcode到指令的核心部分了。其中opcode表如下
div.Section0{page:Section0;} unk_14000B180 db 0 ; DATA XREF: sub_140009820:loc_1400099F5↑o.rdata:000000014000B181 db 0.rdata:000000014000B182 db 0.rdata:000000014000B183 db 0.rdata:000000014000B184 db 0.rdata:000000014000B185 db 0.rdata:000000014000B186 db 0.rdata:000000014000B187 db 0.rdata:000000014000B188 dq offset unk_14000B128.rdata:000000014000B190 db 1.rdata:000000014000B191 db 0.rdata:000000014000B192 db 0.rdata:000000014000B193 db 0.rdata:000000014000B194 db 0.rdata:000000014000B195 db 0.rdata:000000014000B196 db 0.rdata:000000014000B197 db 0.rdata:000000014000B198 dq offset unk_14000B12C.rdata:000000014000B1A0 db 2.rdata:000000014000B1A1 db 0.rdata:000000014000B1A2 db 0.rdata:000000014000B1A3 db 0.rdata:000000014000B1A4 db 1.rdata:000000014000B1A5 db 0.rdata:000000014000B1A6 db 0.rdata:000000014000B1A7 db 0.rdata:000000014000B1A8 dq offset aImpext ; "IMPEXT".rdata:000000014000B1B0 db 3.rdata:000000014000B1B1 db 0.rdata:000000014000B1B2 db 0.rdata:000000014000B1B3 db 0.rdata:000000014000B1B4 db 2.rdata:000000014000B1B5 db 0.rdata:000000014000B1B6 db 0.rdata:000000014000B1B7 db 0.rdata:000000014000B1B8 dq offset aMov ; "MOV".rdata:000000014000B1C0 db 4.rdata:000000014000B1C1 db 0.rdata:000000014000B1C2 db 0.rdata:000000014000B1C3 db 0.rdata:000000014000B1C4 db 2.rdata:000000014000B1C5 db 0.rdata:000000014000B1C6 db 0.rdata:000000014000B1C7 db 0.rdata:000000014000B1C8 dq offset aAdd ; "ADD".rdata:000000014000B1D0 db 5.rdata:000000014000B1D1 db 0.rdata:000000014000B1D2 db 0.rdata:000000014000B1D3 db 0.rdata:000000014000B1D4 db 2.rdata:000000014000B1D5 db 0.rdata:000000014000B1D6 db 0.rdata:000000014000B1D7 db 0.rdata:000000014000B1D8 dq offset aSub ; "SUB".rdata:000000014000B1E0 db 6.rdata:000000014000B1E1 db 0.rdata:000000014000B1E2 db 0.rdata:000000014000B1E3 db 0.rdata:000000014000B1E4 db 1.rdata:000000014000B1E5 db 0.rdata:000000014000B1E6 db 0.rdata:000000014000B1E7 db 0.rdata:000000014000B1E8 dq offset aJmp ; "JMP".rdata:000000014000B1F0 db 7.rdata:000000014000B1F1 db 0.rdata:000000014000B1F2 db 0.rdata:000000014000B1F3 db 0.rdata:000000014000B1F4 db 3.rdata:000000014000B1F5 db 0.rdata:000000014000B1F6 db 0.rdata:000000014000B1F7 db 0.rdata:000000014000B1F8 dq offset aJil ; "JIL".rdata:000000014000B200 db 9.rdata:000000014000B201 db 0.rdata:000000014000B202 db 0.rdata:000000014000B203 db 0.rdata:000000014000B204 db 3.rdata:000000014000B205 db 0.rdata:000000014000B206 db 0.rdata:000000014000B207 db 0.rdata:000000014000B208 dq offset aJig ; "JIG".rdata:000000014000B210 db 8.rdata:000000014000B211 db 0.rdata:000000014000B212 db 0.rdata:000000014000B213 db 0.rdata:000000014000B214 db 3.rdata:000000014000B215 db 0.rdata:000000014000B216 db 0.rdata:000000014000B217 db 0.rdata:000000014000B218 dq offset aJle ; "JLE".rdata:000000014000B220 db 0Ah.rdata:000000014000B221 db 0.rdata:000000014000B222 db 0.rdata:000000014000B223 db 0.rdata:000000014000B224 db 3.rdata:000000014000B225 db 0.rdata:000000014000B226 db 0.rdata:000000014000B227 db 0.rdata:000000014000B228 dq offset aJge ; "JGE".rdata:000000014000B230 db 0Bh.rdata:000000014000B231 db 0.rdata:000000014000B232 db 0.rdata:000000014000B233 db 0.rdata:000000014000B234 db 3.rdata:000000014000B235 db 0.rdata:000000014000B236 db 0.rdata:000000014000B237 db 0.rdata:000000014000B238 dq offset aJe ; "JE".rdata:000000014000B240 db 0Ch.rdata:000000014000B241 db 0.rdata:000000014000B242 db 0.rdata:000000014000B243 db 0.rdata:000000014000B244 db 3.rdata:000000014000B245 db 0.rdata:000000014000B246 db 0.rdata:000000014000B247 db 0.rdata:000000014000B248 dq offset aJne ; "JNE".rdata:000000014000B250 db 0Dh.rdata:000000014000B251 db 0.rdata:000000014000B252 db 0.rdata:000000014000B253 db 0.rdata:000000014000B254 db 1.rdata:000000014000B255 db 0.rdata:000000014000B256 db 0.rdata:000000014000B257 db 0.rdata:000000014000B258 dq offset aPrint ; "PRINT".rdata:000000014000B260 TlsDirectory dq offset TlsStart.rdata:000000014000B268 TlsEnd_ptr dq offset TlsEnd.rdata:000000014000B270 TlsIndex_ptr dq offset TlsIndex.rdata:000000014000B278 TlsCallbacks_ptr dq offset TlsCallbacks
到这里bvs文件里面的内容就转换成了
0: IMPEXT 01: JE 4, 420, Var[0]2: PRINT 13: JMP 54: PRINT 25: RET
到这里答案就已经出来了,后面的代码其实就是通过switch case跳到相关指令区域,执行指令
最后本题答案就是创建in.txt里面写入420如下
文章中使用到的工具,如有需要,在公众号私信回复 20260515 即可获取下载链接!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:网络安全研习社 小张 小张《BVES 虚拟逆向:从 Opcode 到 Flag 的完整分析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论