一、反调试
为了保护自己的程序,现在很多程序都加入了反调试,那么,有什么简单的方法来进行调试检测么?
在程序启动后,会有一个STARTUPINFO的结构体变量,来保存程序启动的信息,我们通过其中参数的改变来检测程序是正常运行还是在调试器中运行的。
在一般情况下,程序是由explorer.exe调用的(如果对此有疑问,你可以安装一个hips来了解下,例如:2012/4/17 星期二 15:23:42 c:\windows\explorer.exe 创建新进程 d:\program files\屏幕录像专家\屏录专家.exe 允许 [应用程序]* 命令行: "D:\Program Files\屏幕录像专家\屏录专家.exe" ),具体就是explorer.exe使用shell32中的ShellExecute来运行程序,而ShellExecute会在执行时清除不使用的值。而如果程序是由OD调用的话,会对STARTUPINFO结构体中的dwFlags 进行修改。
好,下面我们先来具体看看STARTUPINFO结构体的情况:
1 typedef struct _STARTUPINFOW { 2 DWORD cb; 3 LPWSTR lpReserved; 4 LPWSTR lpDesktop; 5 LPWSTR lpTitle; 6 DWORD dwX; 7 DWORD dwY; 8 DWORD dwXSize; 9 DWORD dwYSize; 10 DWORD dwXCountChars; 11 DWORD dwYCountChars; 12 DWORD dwFillAttribute; 13 DWORD dwFlags; 14 WORD wShowWindow; 15 WORD cbReserved2; 16 LPBYTE lpReserved2; 17 HANDLE hStdInput; 18 HANDLE hStdOutput; 19 HANDLE hStdError; 20 } STARTUPINFOW, *LPSTARTUPINFOW;
其中的参数很多,不过我们只关心DWORD类型的变量。
要想弄懂问题,我们就先来看看代码吧。
1 #include <windows.h> 2 #include <stdio.h> 3 4 #pragma comment(linker, "/subsystem:windows /entry:main") 5 6 int main() 7 { 8 STARTUPINFO si; 9 si.cb=sizeof(si); 10 GetStartupInfo(&si); 11 DWORD dwX; 12 DWORD dwY; 13 DWORD dwXSize; 14 DWORD dwYSize; 15 DWORD dwXCountChars; 16 DWORD dwYCountChars; 17 DWORD dwFillAttribute; 18 DWORD dwFlags; 19 20 if ( 21 (si.dwX != 0) || 22 (si.dwY != 0) || 23 (si.dwXCountChars != 0) || 24 (si.dwYCountChars != 0) || 25 (si.dwFillAttribute != 0) || 26 (si.dwXSize != 0) || 27 (si.dwYSize != 0) || 28 (si.dwFlags & STARTF_FORCEOFFFEEDBACK) 29 ) 30 { 31 MessageBox(NULL, "found debugger!", NULL, 0); 32 ExitProcess(0); 33 } 34 else 35 { 36 MessageBox(NULL, "not found debugger!", NULL, 0); 37 } 38 39 return 0; 40 }
程序生成好了之后,如果程序正常运行,就会提示“not found debugger!”,而如果在OD中运行,则会提示“found debugger!”。
二、反反调试
我们先用OD载入debug版的程序。对汇编代码进行观察发现,我们在程序中加入的if判断语句变成了几个cmp比较和jnz跳转。
我们来看看代码
1 00401033 . 50 push eax ; /pStartupinfo 2 00401034 . FF15 90A14200 call dword ptr [<&KERNEL32.GetStartup>; \GetStartupInfoA 3 0040103A . 3BF4 cmp esi, esp 4 0040103C . E8 DF000000 call _chkesp 5 00401041 . 837D CC 00 cmp dword ptr [ebp-34], 0 6 00401045 . 75 31 jnz short 00401078 7 00401047 . 837D D0 00 cmp dword ptr [ebp-30], 0 8 0040104B . 75 2B jnz short 00401078 9 0040104D . 837D DC 00 cmp dword ptr [ebp-24], 0 10 00401051 . 75 25 jnz short 00401078 11 00401053 . 837D E0 00 cmp dword ptr [ebp-20], 0 12 00401057 . 75 1F jnz short 00401078 13 00401059 . 837D E4 00 cmp dword ptr [ebp-1C], 0 14 0040105D . 75 19 jnz short 00401078 15 0040105F . 837D D4 00 cmp dword ptr [ebp-2C], 0 16 00401063 . 75 13 jnz short 00401078 17 00401065 . 837D D8 00 cmp dword ptr [ebp-28], 0 18 00401069 . 75 0D jnz short 00401078 19 0040106B . 8B4D E8 mov ecx, dword ptr [ebp-18] 20 0040106E . 81E1 80000000 and ecx, 80 21 00401074 . 85C9 test ecx, ecx 22 00401076 . 74 2D je short 004010A5 23 00401078 > 8BF4 mov esi, esp 24 0040107A . 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL 25 0040107C . 6A 00 push 0 ; |Title = NULL 26 0040107E . 68 34204200 push 00422034 ; |Text = "found debugger!" 27 00401083 . 6A 00 push 0 ; |hOwner = NULL 28 00401085 . FF15 ACA24200 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA
我们看到,从00401041开始,就是一连串的比较,其实我们只要找到正常执行代码的起始地址,然后直接一个jmp即可,或者修改所有的jnz为je。
1 004010A5 > \8BF4 mov esi, esp 2 004010A7 . 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL 3 004010A9 . 6A 00 push 0 ; |Title = NULL 4 004010AB . 68 1C204200 push 0042201C ; |Text = "not found debugger!" 5 004010B0 . 6A 00 push 0 ; |hOwner = NULL 6 004010B2 . FF15 ACA24200 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA
我们看到004010A5为当前正常运行的地址,我们在第一个比较的地方,即地址为00401041的地方进行反汇编,写入
JMP 004010A5后保存即可。
下面我们再来看看Release版的程序,其实都是一个意思,只不过release版的程序代码有些不同罢了,比较由cmp变成了test,但也是同样的效果,我们同样在比较的开始修改为jmp +
“正常运行的地址”。然后保存就可以了。
三、扩展
除了对OD的检测,现在对虚拟机检测的程序也越来越多,方式也越来越多。
比如,通过特权指令来检测、利用IDT基址检测、利用LDT和GDT的检测方法、基于STR(Store Task Register)的检测方法、通过注册表检测虚拟机(是否安装了tools工具等)、基于时间差的检测方式(通过RDTSC)、利用虚拟硬件指纹检测(对MAC地址检查等)等。