最近在看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和其他約定的以后再嘗試整理。