最近在看IDA的书,讲汇编语言的部分提到了一种防止递归向下汇编器逆向程序的方法
这里esp指向栈顶,也就是调用方最后入栈的返回地址。然而实际在VC2017里用内联汇编这么做是不行的,原因可以看看VC生成的汇编 代码:
1 int __stdcall func1(int param) 2 { 3 00AC10A0 push ebp 4 00AC10A1 mov ebp,esp 5 00AC10A3 sub esp,8 6 int local = param; 7 00AC10A6 mov eax,dword ptr [param] 8 00AC10A9 mov dword ptr [local],eax 9 int local2 = 1 + param; 10 00AC10AC mov ecx,dword ptr [param] 11 00AC10AF add ecx,1 12 00AC10B2 mov dword ptr [local2],ecx 13 _asm{ 14 add dword ptr[ebp+4],13 15 00AC10B5 add dword ptr [ebp+4],0Dh 16 } 17 return param*2; 18 00AC10B9 mov eax,dword ptr [param] 19 00AC10BC shl eax,1 20 } 21 00AC10BE mov esp,ebp 22 00AC10C0 pop ebp 23 }
可以看到VC生成的汇编代码中添加了一些前缀后缀:
前缀用来保存调用前堆栈顶ebp,还有设置新的堆栈顶位置到ebp。如果有局部变量,还要减少esp位置(相当于入栈几个未知数据)以留出局部变量的位置。注意函数堆栈是从内存大编号向小编号堆叠的,越大的地址编号越靠下,就像一个金字塔,下大上小。
后缀用来清理堆栈(mov esp,ebp),并且从堆栈中恢复此次调用之前的ebp(pop ebp)。不难发现在被调用的函数体内修改函数返回地址的话,就需略过ebp的位置。因此内嵌汇编的那一句需要用ebp+4来得到返回地址指针。后面地址+13是略过的调用方的一个printf方法调用,要跳过多少代码可以在反汇编窗口自行查看地址计算一下。
下面是调用方的代码:
int main() { 00AC1002 in al,dx 00AC1003 sub esp,1Ch 00AC1006 mov eax,dword ptr [__security_cookie (0AC3000h)] 00AC100B xor eax,ebp 00AC100D mov dword ptr [ebp-4],eax int d = 10; 00AC1010 mov dword ptr [d],0Ah func1(10); 00AC1017 push 0Ah 00AC1019 call func1 (0AC10A0h) printf("loc1\n"); 00AC101E push 0AC20F8h 00AC1023 call printf (0AC1140h) 00AC1028 add esp,4 printf("loc2\n"); 00AC102B push 0AC2100h 00AC1030 call printf (0AC1140h) 00AC1035 add esp,4 int a[] = {0,1,2,3,4}; 00AC1038 mov dword ptr [a],0 00AC103F mov dword ptr [ebp-14h],1 00AC1046 mov dword ptr [ebp-10h],2 00AC104D mov dword ptr [ebp-0Ch],3 00AC1054 mov dword ptr [ebp-8],4 printf("%d\b",func2(a)); 00AC105B lea eax,[a] 00AC105E push eax 00AC105F call func2 (0AC10D0h) 00AC1064 add esp,4 00AC1067 push eax 00AC1068 push 0AC2108h 00AC106D call printf (0AC1140h) 00AC1072 add esp,8 printf("writing code...\n"); 00AC1075 push 0AC210Ch 00AC107A call printf (0AC1140h) 00AC107F add esp,4 func3(); 00AC1082 call __vcrt_va_start_verify_argument_type<char const * const> (0AC10F0h) getchar(); 00AC1087 call dword ptr [__imp__getchar (0AC20A8h)] return 0; 00AC108D xor eax,eax }
还有要注意的是这里为了防止代码优化,要关闭vc的编译优化选项。用以上这种方法可以配合一些跳转让反汇编的工具不能正确预测哪部分是代码区,从而达到隐藏一部分代码的目的。
以上是STDCALL调用约定的例子,cdecl和其他约定的以后再尝试整理。
点击数:75