Loading... > 因为一些原因,系列文章《某游戏自动化脚本系列》提前终止,因此记录下自己对花指令处理方式。 # 一、背景 不管是x86还是x64下函数调用过程,都包括:函数返回地址push->EIP(x64下是RIP)跳转->局部变量空间开辟的过程。而栈寄存器EBP和ESP(x64下是RBP和RSP)则保存了栈顶和栈底在函数调用过程中的“环境”。 # 二、实例说明(以x64为例) 对于如下简单程序: ```c++ #include <iostream> uint64_t add(uint64_t a, int b) { return a >> b; } int main() { std::cout << "Hello World!\n"; auto result = add(12008, 2); std::cout << result; } ``` 其`main`函数部分反汇编如下: ```c++ 00007FF72046234E mov edx,2 //第二个参数传递 00007FF720462353 mov ecx,2EE8h //第一个参数传递 00007FF720462358 call add (07FF72046120Dh) 00007FF72046235D mov qword ptr [result],rax ``` 单步执行到call add时,各寄存器值如下: ```c++ RAX = 00007FFCA9873080 RBX = 0000000000000000 RCX = 0000000000002EE8 <-----参数1 RDX = 0000000000000002 <-----参数2 RSI = 0000000000000000 RDI = 0000000000000000 R8 = 0000000000000003 R9 = 00000001F537F3E8 R10 = 0000000000000014 R11 = 00000001F537EFE0 R12 = 0000000000000000 R13 = 0000000000000000 R14 = 0000000000000000 R15 = 0000000000000000 RIP = 00007FF720462358 <-----当前所执行代码地址 RSP = 00000001F537F850 RBP = 00000001F537F870 EFL = 00000244 ``` 单步步进执行到`call add `头部,各寄存器值如下图所示:  我们发现,此时,RSP指针就指向了`00007FF720462358 call add (07FF72046120Dh)`下一行代码所在地址`00007FF72046235D`,也就是所谓的call调用返回地址(Ret Address)。 # 三、实际处理 某游戏`UI`控件注册`Event`的函数部分反汇编代码如下: ```汇编 .text:00000001408A2FE0 40 53 push rbx .text:00000001408A2FE2 56 push rsi .text:00000001408A2FE3 57 push rdi .text:00000001408A2FE4 48 83 EC 40 sub rsp, 40h .text:00000001408A2FE8 33 D2 xor edx, edx .text:00000001408A2FEA 48 8B D9 mov rbx, rcx .text:00000001408A2FED E8 7E 76 00 00 call sub_1408AA670 .text:00000001408A2FF2 .text:00000001408A2FF2 loc_1408A2FF2: ; CODE XREF: sub_140888910:loc_140888A4C↑j .text:00000001408A2FF2 BA 02 00 00 00 mov edx, 2 .text:00000001408A2FF7 48 8B CB mov rcx, rbx .text:00000001408A2FFA 48 8B F0 mov rsi, rax .text:00000001408A2FFD E8 4E A4 C6 FF call sub_14050D450 .text:00000001408A3002 85 C0 test eax, eax .text:00000001408A3004 0F 85 95 05 00 00 jnz loc_1408A359F .text:00000001408A300A 4C 8B 44 24 58 mov r8, [rsp+58h] .text:00000001408A300F 90 nop .text:00000001408A3010 F9 stc .text:00000001408A3011 72 46 jb short near ptr loc_1408A3057+2 .text:00000001408A3013 80 C1 12 add cl, 12h .text:00000001408A3016 83 C5 4F add ebp, 4Fh ; 'O' .text:00000001408A3019 80 EF 0B sub bh, 0Bh .text:00000001408A301C 51 push rcx .text:00000001408A301D 83 C0 C9 add eax, 0FFFFFFC9h .text:00000001408A3020 C7 C2 E0 00 63 E3 mov edx, 0E36300E0h .text:00000001408A3026 68 47 CB C1 85 push 0FFFFFFFF85C1CB47h .text:00000001408A302B E8 1C 46 00 00 call loc_1408A764C .text:00000001408A3030 80 EF 79 sub bh, 79h ; 'y' .text:00000001408A3033 0F 88 D8 D3 01 00 js loc_1408C0411 .text:00000001408A3039 .text:00000001408A3039 loc_1408A3039: ; CODE XREF: sub_14088F610+3DD↑j .text:00000001408A3039 80 C1 0F add cl, 0Fh .text:00000001408A303C E8 FB 43 01 00 call loc_1408B743C .text:00000001408A3041 80 C0 3D add al, 3Dh ; '=' .text:00000001408A3044 70 32 jo short near ptr loc_1408A3077+1 .text:00000001408A3046 83 C6 58 add esi, 58h ; 'X' .text:00000001408A3049 80 EB D3 sub bl, 0D3h .text:00000001408A304C F6 DE neg dh .text:00000001408A304E F6 DD neg ch .text:00000001408A3050 0F 31 rdtsc .text:00000001408A3052 E8 51 7C 01 00 call loc_1408BACA8 .text:00000001408A3057 .text:00000001408A3057 loc_1408A3057: ; CODE XREF: sub_1408A2FE0+31↑j .text:00000001408A3057 F6 B5 48 8B 15 88 div byte ptr [rbp-77EA74B8h] .text:00000001408A3057 ; --------------------------------------------------------------------------- .text:00000001408A305D 0F db 0Fh .text:00000001408A305E ; --------------------------------------------------------------------------- .text:00000001408A305E C7 01 80 C8 00 C0 mov dword ptr [rcx], 0C000C880h .text:00000001408A3064 F4 hlt .text:00000001408A3065 ; --------------------------------------------------------------------------- .text:00000001408A3065 00 71 4C add [rcx+4Ch], dh .text:00000001408A3068 C7 C6 F6 65 83 B8 mov esi, 0B88365F6h .text:00000001408A306E 81 C1 43 B ``` 我们发现,自`00000001408A3004`这后面开始,就充满了乱七八糟的指令操作,完全看不明白,然后尝试按F5进行分析,结果IDA提示失败。 通过仔细研究`00000001408A3004`之前的指令以及前面内容,我们发现,`rsp+58h`就是指向的该函数执行完毕后的返回地址,那这就比较清楚了。 但是地址`00000001408A3011`处的指令为啥是`jb short near ptr loc_1408A3057+2`这种呢? 我们跳转到地址`loc_1408A3057`处发现,此处代码为如下所示: ```汇编 .text:00000001408A3057 F6 B5 48 8B 15 88 div byte ptr [rbp-77EA74B8h] .text:00000001408A3057 ; --------------------------------------------------------------------------- .text:00000001408A305D 0F db 0Fh .text:00000001408A305E ; --------------------------------------------------------------------------- .text:00000001408A305E C7 01 80 C8 00 C0 mov dword ptr [rcx], 0C000C880h .text:00000001408A3064 F4 hlt .text:00000001408A3065 ; --------------------------------------------------------------------------- .text:00000001408A3065 00 71 4C add [rcx+4Ch], dh .text:00000001408A3068 C7 C6 F6 65 83 B8 mov esi, 0B88365F6h .text:00000001408A306E 81 C1 43 B8 F5 1C add ecx, 1CF5B843h .text:00000001408A3074 80 EF E3 sub bh, 0E3h ``` 但是跳转指向的是`loc_1408A3057+2`,而不是`loc_1408A3057`,因此将`loc_1408A3057`处数据设置为`Undefine`,然后将`loc_1408A3057+2`处数据设置为`code`,有了如下变化: ```汇编 .text:00000001408A3057 F6 db 0F6h .text:00000001408A3058 B5 db 0B5h .text:00000001408A3059 ; --------------------------------------------------------------------------- .text:00000001408A3059 .text:00000001408A3059 loc_1408A3059: ; CODE XREF: sub_1408A2FE0+31↑j .text:00000001408A3059 48 8B 15 88 0F C7 01 mov rdx, cs:qword_142513FE8 .text:00000001408A3060 80 C8 00 or al, 0 .text:00000001408A3063 C0 F4 00 sal ah, 0 ``` 这下就都能看明白了,原来该游戏在`loc_1408A3057`塞了两字节无用数据,干扰了IDA反汇编分析。 对各加塞了字节部分进行处理后,其流程图如下所示(依旧是一团乱麻):  别慌,容我按个F5,然后泡杯茶,发现F5插件成功识别出函数了,并且堆栈无太大问题,其伪代码**及其分析**如下: ```c++ // bad sp value at call has been detected, the output may be wrong! // positive sp value has been detected, the output may be wrong! __int64 __fastcall sub_1408A2FE0(_QWORD *a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10, __int64 a11, __int64 a12, __int64 a13, __int64 a14, __int64 a15, __int64 a16, __int64 a17, __int64 a18, __int64 a19, __int64 a20, __int64 a21, __int64 a22, __int64 a23, __int64 a24, __int64 a25, __int64 a26, __int64 a27, __int64 a28, __int64 a29, __int64 a30, __int64 a31, __int64 a32, __int64 a33, __int64 a34, __int64 a35, __int64 a36, __int64 a37, __int64 a38, __int64 a39, __int64 a40, __int64 a41, __int64 a42, __int64 a43, __int64 a44, __int64 a45, __int64 a46, __int64 a47, __int64 a48, __int64 a49, __int64 a50, __int64 a51, __int64 a52, __int64 a53, __int64 a54, __int64 a55, __int64 a56, __int64 a57, __int64 a58, __int64 a59, __int64 a60, __int64 a61, __int64 a62, __int64 a63) { __int64 v63; // rdi __int64 v64; // rbx _BYTE *pFrame_Rcx; // rsi int v66; // ecx unsigned __int64 v67; // r8 __int64 v68; // rdx char v69; // of __int64 v70; // rcx char v71; // cc char v72; // dl char v73; // al bool v74; // cl char v75; // dl char v76; // al char v77; // al char v78; // ch int v79; // esi bool v80; // sf unsigned int v81; // esi __int64 v82; // rcx __int64 v83; // rcx int v84; // eax __int64 v85; // rdx char v86; // cl _BYTE *v87; // r8 _BYTE *v88; // rdi unsigned __int128 v89; // rtt bool v90; // sf char *v91; // rcx unsigned __int64 v92; // rax int v93; // ecx __int64 v94; // rcx __int64 *v95; // rax __int64 eventNameHashID; // rax int v97; // eax int v98; // ecx __int64 v99; // r9 unsigned __int64 v100; // r8 __int64 v101; // rdx __int64 v102; // rcx __int64 v103; // rcx bool v104; // cf bool v105; // zf bool v106; // cc char v107; // dl char v108; // al bool v109; // cl char v110; // cl char v111; // dl char v112; // al char v113; // al char v114; // dl char v115; // pf int v116; // eax int v117; // er8 char v119; // [rsp+60h] [rbp-1149Eh] __int128 v120; // [rsp+78h] [rbp-11486h] __int64 v121[5]; // [rsp+88h] [rbp-11476h] BYREF __int64 v122; // [rsp+B0h] [rbp-1144Eh] unsigned __int64 retAddress; // [rsp+B8h] [rbp-11446h] __int64 v124; // [rsp+C0h] [rbp-1143Eh] __int64 v125; // [rsp+C8h] [rbp-11436h] __int64 v126; // [rsp+D0h] [rbp-1142Eh] unsigned __int64 v127; // [rsp+D8h] [rbp-11426h] __int64 v128; // [rsp+E0h] [rbp-1141Eh] __int64 v129; // [rsp+E8h] [rbp-11416h] __int64 v130; // [rsp+F0h] [rbp-1140Eh] __int64 v131; // [rsp+F8h] [rbp-11406h] __int64 v132; // [rsp+100h] [rbp-113FEh] __int64 v133; // [rsp+108h] [rbp-113F6h] __int64 v134; // [rsp+110h] [rbp-113EEh] __int64 v135; // [rsp+118h] [rbp-113E6h] __int64 v136; // [rsp+120h] [rbp-113DEh] __int64 v137; // [rsp+128h] [rbp-113D6h] __int64 v138; // [rsp+130h] [rbp-113CEh] __int64 v139; // [rsp+138h] [rbp-113C6h] __int64 v140; // [rsp+140h] [rbp-113BEh] __int64 v141; // [rsp+148h] [rbp-113B6h] __int64 v142; // [rsp+150h] [rbp-113AEh] __int64 v143; // [rsp+158h] [rbp-113A6h] __int64 v144; // [rsp+160h] [rbp-1139Eh] __int64 v145; // [rsp+168h] [rbp-11396h] __int64 v146; // [rsp+170h] [rbp-1138Eh] __int64 v147; // [rsp+178h] [rbp-11386h] __int64 v148; // [rsp+180h] [rbp-1137Eh] __int64 v149; // [rsp+188h] [rbp-11376h] __int64 v150; // [rsp+190h] [rbp-1136Eh] __int64 v151; // [rsp+198h] [rbp-11366h] __int64 v152; // [rsp+1A0h] [rbp-1135Eh] __int64 v153; // [rsp+1A8h] [rbp-11356h] __int64 v154; // [rsp+1B0h] [rbp-1134Eh] __int64 v155; // [rsp+1B8h] [rbp-11346h] __int64 v156; // [rsp+1C0h] [rbp-1133Eh] __int64 v157; // [rsp+1C8h] [rbp-11336h] __int64 v158; // [rsp+1D0h] [rbp-1132Eh] __int64 v159; // [rsp+1D8h] [rbp-11326h] __int64 v160; // [rsp+1E0h] [rbp-1131Eh] __int64 v161; // [rsp+1E8h] [rbp-11316h] __int64 v162; // [rsp+1F0h] [rbp-1130Eh] __int64 v163; // [rsp+1F8h] [rbp-11306h] __int64 v164; // [rsp+200h] [rbp-112FEh] __int64 v165; // [rsp+208h] [rbp-112F6h] __int64 v166; // [rsp+210h] [rbp-112EEh] __int64 v167; // [rsp+218h] [rbp-112E6h] __int64 v168; // [rsp+220h] [rbp-112DEh] __int64 v169; // [rsp+8974h] [rbp-8B8Ah] v64 = (__int64)a1; pFrame_Rcx = sub_1408AA670(a1, 0i64); if ( !sub_14050D450(v64, 2i64) ) { v67 = retAddress; v68 = 0x7FF60A5C1000i64; if ( v69 ) JUMPOUT(0x1408A3077i64); v127 = retAddress; if ( retAddress < 0x7FF60A5C1000i64 || retAddress >= 0x7FF60CABDE50i64 )// 判断返回地址是否为游戏主模块内地址, 0x7FF60A5C1000i64为模块基址, 0x7FF60CABDE50i64为模块基址+模块大小 goto LABEL_96; if ( ((v64 & 0x8000) != 0) ^ v69 | (BYTE1(v64) == 0) )// 暂不清楚V69是啥 { v70 = *(unsigned __int8 *)(retAddress - 5);// retAddress-5 为call function 指令所在地址 v71 = (unsigned __int8)v70 <= 0xE8u; if ( (_BYTE)v70 == 0xE8 ) // 判断 调用call地址处数据第一个字节是否为E8(call指令) // 正常call调用,此处条件,肯定是满足的,因此,跳转到label_31处看看 goto LABEL_31; if ( *(_BYTE *)(retAddress - 7) != 0xFF || (v72 = *(_BYTE *)(retAddress - 6), (((v72 & 0x38) - 16) & 0xF7) != 0) )// 暂时不清楚 { v72 = *(_BYTE *)(retAddress - 6); v73 = 0; } else { v73 = 1; } v74 = v72 == -1 && (((v70 & 0x38) - 16) & 0xF7) == 0; LOBYTE(v70) = v73 | v74; if ( *(_BYTE *)(retAddress - 4) != 0xFF || (v75 = *(_BYTE *)(retAddress - 3), (((v75 & 0x38) - 16) & 0xF7) != 0) ) v75 = *(_BYTE *)(retAddress - 3); else LOBYTE(v70) = 1; if ( v75 == -1 ) JUMPOUT(0x1408A3178i64); } else { v71 = ((char)(v64 + 37) < 0) ^ __OFADD__(37, (_BYTE)v64); LOBYTE(v64) = v64 + 37; if ( !v71 ) JUMPOUT(0x1408BD8B1i64); BYTE1(v66) += 26; v68 = 0xFCD0EB4Ai64; v70 = (unsigned int)(v66 - 1451520475); if ( (_DWORD)v70 ) JUMPOUT(0x1408A3109i64); --*(_BYTE *)v63; BYTE1(v68) = -62; if ( (((v119 & 0x38) - 16) & 0xF7) == 0 ) { v76 = 1; goto LABEL_28; } } v68 = *(unsigned __int8 *)(v67 - 2); v76 = 0; LABEL_28: LOBYTE(v70) = v76 | v70; if ( (_BYTE)v68 == 0xFF && (v77 = (*(_BYTE *)(v67 - 1) & 0x38) - 16, (v71 = (v77 & 0xF7) == 0) != 0) || (v71 = (_BYTE)v70 == 0, (_BYTE)v70) ) { LABEL_31: if ( v71 && !v71 ) // v71&& !v71 这个条件容易晓得,肯定是永远不会满足,因此看看 else 分支 { v78 = -BYTE1(v70); v71 = __CFADD__(BYTE1(v68), 0x96) || BYTE1(v68) == 0x6A; BYTE1(v68) -= 0x6A; if ( v71 ) { v79 = (_DWORD)pFrame_Rcx + 0x46108BE0; BYTE1(v70) = -v78; v80 = v79 + 0x54 < 0; v81 = v79 + 0x54; if ( !__SETP__(v81, 0) ) { if ( !v80 ) { ((void (__fastcall *)(__int64, __int64))loc_1408A69AB)(v70, v68); ((void (*)(void))loc_1408B6F4B)(); v169 = v82; JUMPOUT(0x1408B27C0i64); } v84 = ((__int64 (__fastcall *)(__int64, __int64))((char *)&loc_1408A8C0A + 1))(v70, v68); if ( ((int)(v81 + 158830431) < 0) ^ __OFADD__(158830431, v81) | (v81 == -158830431) ) { if ( v81 < 0xF68870A1 ) { v88 = (_BYTE *)(*(_DWORD *)(v63 + 1075777452) + __CFADD__(v81, 158830431) + (unsigned int)v63); *v88 += v86; JUMPOUT(0x1408A3269i64); } JUMPOUT(0x1408B0031i64); } v122 = (unsigned int)(v84 - 0x7E7); *(_QWORD *)&v89 = v87; *((_QWORD *)&v89 + 1) = v85; *v87 = v89 / (unsigned __int64)(v84 - 0x7E7); JUMPOUT(0x1408A32F9i64); } JUMPOUT(0x1408ACB9Fi64); } JUMPOUT(0x1408BA645i64); } v83 = (unsigned int)dword_1400C1894[(unsigned __int64)((unsigned int)v67 - (unsigned int)&_ImageBase) >> 14];// 由前文可知,V67是retaddress // 因此retaddress-_ImageBase就是64位的retaddress相对于模块基址(_ImageBase)的偏移,这里不需要用64位去计算,只需计算低32位就可以了 // 然后用偏移右移14位后得到的值去查表,获取一个期望值v83 if ( !(_DWORD)v83 ) JUMPOUT(0x1408A3264i64); v90 = (__int64)dword_1400C1894 + v83 < 0; v91 = (char *)dword_1400C1894 + v83; if ( (__int64)v91 >= 0 && v90 ) // 条件不满足为正常情况,因此需要满足几个条件: // 1.v90为真 <=> (__int64)dword_1400c1894+v83<0 // 2.v91>=0 <=> (char*)dword_1400c1894+v83>=0 { LOBYTE(v91) = 16; if ( !__OFADD__(0xAD, v67 - (unsigned __int8)&_ImageBase) ) { __rdtsc(); LOBYTE(v64) = -(char)v64; v92 = __rdtsc(); ((void (__fastcall *)(char *, _QWORD))((char *)&loc_1408AB66A + 1))(v91, HIDWORD(v92)); if ( !v105 ) { BYTE1(v64) = 110; v94 = (unsigned int)(v93 - 1); BYTE1(v94) += 13; ((void (__fastcall *)(__int64))loc_1408ADE9A)(v94); LOBYTE(v64) = v64 + 119; __asm { jmp fword ptr [rbx] } } JUMPOUT(0x1408A6230i64); } JUMPOUT(0x1408B9BB5i64); } JUMPOUT(0x1408A33FAi64); } LABEL_96: JUMPOUT(0x1408A345Ci64); } v95 = lua_tostring(v121, v64, 2u); eventNameHashID = function_EventNameToHashID(v95); v97 = j_function_RegisterEventToFrame((__int64)pFrame_Rcx, eventNameHashID); if ( v97 == 2 ) { v100 = retAddress; v101 = 0x7FF60A5C1000i64; *((_QWORD *)&v120 + 1) = retAddress; if ( retAddress >= 0x7FF60A5C1000i64 && retAddress < 0x7FF60CABDE50i64 )// 同上述分析 { if ( !__OFSUB__(retAddress, 0x7FF60CABDE50i64) && v69 ) { v102 = (unsigned int)(v98 + 70); LOBYTE(v102) = v102 - 45; ((void (__fastcall *)(__int64, __int64))((char *)&qword_1408B5708[42] + 7))(v102, 0x7FF60A5C1000i64); JUMPOUT(0x1408A36D5i64); } v103 = *(unsigned __int8 *)(retAddress - 5); v104 = (unsigned __int8)v103 < 0xE8u; v105 = (_BYTE)v103 == 0xE8; v106 = (unsigned __int8)v103 <= 0xE8u; if ( (_BYTE)v103 == 0xE8 ) goto LABEL_77; if ( *(_BYTE *)(retAddress - 7) != 0xFF || (v107 = *(_BYTE *)(retAddress - 6), (((v107 & 0x38) - 16) & 0xF7) != 0) ) { v107 = *(_BYTE *)(retAddress - 6); v108 = 0; } else { v108 = 1; } v109 = v107 == -1 && (((v103 & 0x38) - 16) & 0xF7) == 0; v110 = v108 | v109; if ( *(_BYTE *)(retAddress - 4) != 0xFF || (v111 = *(_BYTE *)(retAddress - 3), (((v111 & 0x38) - 16) & 0xF7) != 0) ) v111 = *(_BYTE *)(retAddress - 3); else v110 = 1; if ( v111 != -1 || (v101 = *(unsigned __int8 *)(retAddress - 2), (((v101 & 0x38) - 16) & 0xF7) != 0) ) { v101 = *(unsigned __int8 *)(retAddress - 2); v112 = 0; } else { v112 = 1; } LOBYTE(v103) = v112 | v110; if ( (_BYTE)v101 == 0xFF ) { v113 = (*(_BYTE *)(retAddress - 1) & 0x38) - 16; v104 = 0; v105 = (v113 & 0xF7) == 0; v106 = v105; if ( (v113 & 0xF7) == 0 ) goto LABEL_77; } v104 = 0; v105 = (_BYTE)v103 == 0; v106 = (_BYTE)v103 == 0; if ( (_BYTE)v103 ) { LABEL_77: if ( v106 ) { while ( !v104 && !v105 ) { ((void (__fastcall *)(__int64, __int64, unsigned __int64, __int64, _QWORD, _QWORD, __int64, __int64, __int64, __int64, __int64, __int64, unsigned __int64, __int64, __int64, __int64, unsigned __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64, __int64))loc_1408A8176)( v103, v101, v100, v99, v120, *((_QWORD *)&v120 + 1), v121[0], v121[1], v121[2], v121[3], v121[4], v122, retAddress, v124, v125, v126, v127, v128, v129, v130, v131, v132, v133, v134, v135, v136, v137, v138, v139, v140, v141, v142, v143, v144, v145, v146, v147, v148, v149, v150, v151, v152, v153, v154, v155, v156, v157, v158, v159, v160, v161, v162, v163, v164, v165, v166, v167, v168); if ( v114 != -85 ) JUMPOUT(0x1408C241Ai64); ((void (*)(void))((char *)&loc_1408B03A3 + 3))(); ((void (*)(void))loc_1408A9F24)(); if ( v115 ) JUMPOUT(0x1408BC9DFi64); ((void (*)(void))((char *)&loc_1408B2AA9 + 3))(); ((void (*)(void))((char *)&loc_1408BA3D4 + 3))(); ((void (*)(void))loc_1408B726E)(); if ( !v105 ) { v116 = ((__int64 (*)(void))((char *)&loc_1408ACE5D + 1))(); if ( dword_1400C1894[(unsigned __int64)(unsigned int)(v117 - (v116 - 9058281)) >> 14] ) JUMPOUT(0x1408A39CAi64); JUMPOUT(0x1408A3834i64); } } } JUMPOUT(0x1408A37E2i64); } } JUMPOUT(0x1408A3A28i64); } lua_pushboolean(v64, v97 == 0); return 1i64; } ``` 因此,我们可以得出该游戏函数返回地址校验,经历了以下步骤: 1. 返回地址范围判断:`retAddress < 0x7FF60A5C1000i64 || retAddress >= 0x7FF60CABDE50i64` 2. 函数调用指令判断:`(retAddress - 5)== 0xE8` 3. 前一个指令判断:`(retAddress - 7) != 0xFF || , ((((retAddress - 6) & 0x38) - 16) & 0xF7) != 0` 4. 返回地址右移14位后查表判断,此处为循环判断,直到跳出循环或者执行到异常,游戏崩溃。 # 四、后话 当然,把校验逻辑分析出来,不是说就能pass掉返回地址校验,游戏还有其他手段,来确保校验正常生效。只是想把自己分析的过程和思路记录下来,以免遗忘。 该游戏在应用层(R3层)存在的其他保护手段(不全)如下,值得学习: 1. rtds时间校验(vt技术hook指令可过); 2. 花指令及乱序跳转(干扰IDA等工具静态分析,动态调试可直接pass); 3. 正常执行触发异常,程序自身接管(盲猜VEH异常链,调试器附加,则奔溃); 4. 很少(应该说没有)的异常提示字符,发现被调试或者异常调用链,游戏执行触发异常代码,客户端直接崩溃退出; 5. 采用mmap重新映射进程内存空间方式,避免内存篡改。 6. 关键代码及关键数据crc校验,避免内存篡改。 7. 进程遍历、模块遍历、线程遍历、内存块遍历...... 最后修改:2021 年 09 月 19 日 11 : 57 PM © 禁止转载