LuckStar

对程序使用保护的分析

对加载dll文件的效验。


.text:0040147F push ebx .text:00401480 push esi .text:00401481 mov esi, ds:GetModuleHandleA .text:00401487 push edi .text:00401488 push offset ModuleName ; "ntdll.dll" .text:0040148D call esi ; GetModuleHandleA .text:0040148F mov edx, offset aA7d7bcc95a86a6 ; "a7d7bcc95a86a6df3b0a1ccb3c69d440" .text:00401494 mov ecx, eax .text:00401496 call sub_401360 .text:0040149B push offset ModuleName ; "ntdll.dll" .text:004014A0 mov dword_407378, eax .text:004014A5 call esi ; GetModuleHandleA .text:004014A7 mov edx, offset a8eb35a28209979 ; "8eb35a28209979fe6a9983cff0d23c5a" .text:004014AC mov ecx, eax .text:004014AE call sub_401360 .text:004014B3 push offset aKernel32Dll_0 ; "kernel32.dll" .text:004014B8 mov dword_407380, eax .text:004014BD call esi ; GetModuleHandleA .text:004014BF mov edx, offset a05577e1568efa1 ; "05577e1568efa10b8166728bfb414c59" .text:004014C4 mov ecx, eax .text:004014C6 call sub_401360 .text:004014CB mov ecx, dword_407378 .text:004014D1 mov dword_407374, eax .text:004014D6 test ecx, ecx .text:004014D8 jz loc_4015A3

这段对所加载的dll文件进行MD5值效验

反调试措施

反调试一

NtQuerySystemInformation()
定义如下:

NTSTATUS WINAPI NtQuerySystemInformation(
  _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
  _Inout_   PVOID                    SystemInformation,
  _In_      ULONG                    SystemInformationLength,
  _Out_opt_ PULONG                   ReturnLength
);

当第一个参数传入SystemKernelDebuggerInformation(即0x23)时,第二个参数将会返回系统调试状态。

此处的反调试策略是patch修改掉地址 .rdata:00403148 处的几个debugger名称字符串。

反调试二

NtSetInformationThread方法

这个也是使用Windows的一个未公开函数的方法,你可以在当前线程里调用NtSetInformationThread,调用这个函数时,如果在第二个参数里指定0x11这个值(意思是ThreadHideFromDebugger),等于告诉操作系统,将所有附加的调试器统统取消掉。示例代码:

我们patch掉此处的函数调用。

执行到此处,我们点开main函数,得到解密后的逻辑。

区域选中右键重新Analyse,到函数开头处,快捷键p定义函数。

反调试三

但是发现还是到不了main函数。

这里需要PE文件结构知识了。

使用PEview查看程序的Optional_HEADER,看可以看到TLS表的偏移。

使用IDA查看

不知道为什么TLS返回后为什么程序停不下来。

我直接set EIP至main函数处发现解密出来的函数段是错误的,所有从TLS到main函数处,一定是哪里调用了rand(),但是由于看不到交叉引用......。(不知道出题人用的啥编译器,第一次见这种程序入口,自己太菜了。)

由于TLS回调函数执行之后转至程序入口出,Ctrl+e转到start处,开始向下分析。

这里我是动态逐步的分析,先单步执行,哪里停不下来就转进去,最终定位该反调试三0x0040251b。(反调试一定是程序员写的,所以一定在用户内存空域,所以我们没必要进入dll内存空域兜圈子。)

接下来就是出题人的卖弄。
绕过sleep函数和歌曲,我们可以直接set EIP,到我们要执行的代码处。

之后我们便可以得到0x4015E0处函数逻辑。

之后就是平凡的逆向工作了。

注意到这里base64 table是被替换过了,但是还好只是大小写互换。

之后它又取随机数,对得到的base64值进行了异或操作。


v19 = strlen(base64_); if ( v19 > 0 ) { do { v16 = 6; do { v17 = rand() % 4; v18 = v16; v16 -= 2; result = (_BYTE)v17 << v18; base64_[v14] ^= result; } while ( v16 > -2 ); ++v14; } while ( v14 < v19 );

每一位使用四次随机数,这里的随机数只能调试时记录了。


data=[0x49,0xE6,0x57,0xBD,0x3A,0x47,0x11,0x4C,0x95,0xBC,0xEE,0x32,0x72,0xA0,0xF0,0xDE,0xAC,0xF2,0x83,0x56,0x83,0x49,0x6E,0xA9,0xA6,0xC5,0x67,0x3C,0xCA,0xC8,0xCC,0x05] xor=[0,0,8,0,128,0,0,1,0,48,8,1,128,0,12,1,64,0,0,0,0,0,8,1,64,0,0,2,0,16,4,0,192,16,0,0,192,48,0,2,128,16,8,0,64,32,4,2,0,48,0,3,192,16,4,2,192,0,8,1,128,48,0,2,192,0,0,1,128,16,4,1,128,48,4,2,0,16,12,2,192,0,4,3,0,32,12,1,0,16,12,0,192,32,12,3,192,16,0,2,128,48,0,2,64,16,12,3,64,32,4,2,128,0,12,0,128,48,8,1,192,48,0,1,0,48,8,0,0,16,4,0,0,0,8,0,128,0,12,3,192,0,12,2,192,32,8,1,64,48,12,3,0,0,12,1,0,0,4,1] base = [] for i in range(len(data)): base.append(data[i]^xor[4*i]^xor[4*i+1]^xor[4*i+2]^xor[4*i+3]) for i in range(len(base)): if ord("a")<=base[i]<=ord("z"): base[i] -= 32 elif ord("A")<=base[i]<=ord("Z"): base[i] += 32 print "".join([chr(x) for x in base]).decode("base64")

参考链接:

https://www.cnblogs.com/killmyday/archive/2011/05/31/2064171.html
https://www.cnblogs.com/killmyday/archive/2011/05/31/2064171.html