通过全局变量和自擦除代码来防Dump
通过全局变量和自擦除代码来防Dump
write by 九天雁翎(JTianLing) -- www.jtianling.com
一般而言,你的程序一旦运行起来就没有办法防止Dump了,因为所有的数据都在内存中了,而且,为了更好的Dump完整程序,程序将要启动,还未启动时的Dump,任你程序中有多少防Dump的方法都没有用。这里只能结合两种方式来实现反Dump,其一,程序运行的时候其本身数据并不是完整的,以前我已经讲过方法了, 《让EXE文件不能直接启动的方法以防止直接调试的方法》,只要多用几次这样的方法,比如启动代码无效,中间某段代码也无效,然后通过与启动程序的交互来完成中间代码的修改,只在启动程序通知后才继续运行,以防止错误。这样就没有办法通过在文中介绍的启动时Dump来Dump数据了。对于Dump的作用还不理解的,可以去使用一下LordPE的Dump功能,保证你能够见识到工具的作用性如此之大,并认识到自己程序多么的脆弱。
通过全局变量防Dump
但是这种方法有个缺陷是,程序一旦运行起来,所有代码段的数据都是正确的了,还是可以Dump出来,有种方式是用某个全局变量来指示是否是Dump出来的数据或者是正常运行的数据。
这里介绍一下:
1
2 #include "stdafx.h"
3 #include "windows.h"
4 #include "tchar.h"
5
6 bool gbDumped = false;
7
8 int main(int argc, char* argv[])
9 {
10 if(!gbDumped)
11 {
12 gbDumped = true;
13 MessageBox(NULL, _T("Right"), _T("Hello World"), MB_OK);
14 }
15 else
16 {
17 MessageBox(NULL, _T("Dumped me?!"), _T("Find you Dumped me!!"), MB_OK);
18 }
19 return 0;
20 }
21
这样只要实在你弹出了对话框后Dump出来的程序其实就是不对的程序,虽然逻辑上将,应该会弹出另外一个对话框,但是实际上对于这样正常流程根本不会走到的地方甚至有可能被编译器所优化,然后导致Dump出来的程序直接崩溃。但是这样的程序可以被跟踪调试,通过找到全局变量并修改实在不是什么很难的问题。
下面再将一个稍微复杂一点的办法,让代码一旦运行起来,程序代码段就被破坏,那么这样运行时的Dump也就更加无效了,并且此种方法还可以防止调试。因为完整的程序已经不存在了。
通过自擦除代码来防Dump
原理上也很简单,对于那些只会运行一次的代码,直接在运行后将自己在代码段的内容擦除,可以写入任意值来迷惑调试者,效果更佳。
源代码:
1 #include "windows.h"
2 #include "tchar.h"
3
4 void Run()
5 {
6 // begin of the func
7 DWORD ldwBegin = 0;
8 __asm
9 {
10 call $+5
11 pop eax
12 mov ldwBegin, eax
13 }
14 DWORD ldwOldPro = 0;
15 // Must have this step
16 if(!VirtualProtect((void*)0x401000, 1000, PAGE_EXECUTE_READWRITE, &ldwOldPro))
17 {
18 printf( "VirtualProtectEx failed (%d)./n", GetLastError() );
19 return;
20 }
21
22 MessageBox(NULL, _T("Right"), _T("Hello World"), MB_OK);
23
24 // begin of the func
25 DWORD ldwEnd = 0;
26 __asm
27 {
28 call $+5
29 pop eax
30 mov ldwEnd, eax
31 }
32
33 DWORD ldwFuncLen = ldwEnd - ldwBegin;
34 BYTE *lpbyRand = new BYTE[ldwFuncLen];
35
36 DWORD ldwWritten = 0;
37 if(!WriteProcessMemory(GetCurrentProcess(), (void*)ldwBegin, lpbyRand, ldwFuncLen, &ldwWritten))
38 {
39 printf( "WriteProcessMemory failed (%d)./n", GetLastError() );
40 }
41 delete[] lpbyRand;
42 }
43
44 int main(int argc, char* argv[])
45 {
46 Run();
47 MessageBox(NULL, _T("OK"), _T("OK"), MB_OK);
48
49 return 0;
50 }
以上代码,在弹出OK后再Dump,下次再进入Run函数会直接报错,因为后来添进去的其实是堆上的随机数值。
这里需要说明的是
call $+5
pop eax
两句内嵌代码的含义是获取当前的EIP,这在壳中用的非常多,我在这里套用了一下。这样ldwBegin就是Run函数的开始EIP,ldwEnd虽然不是函数结束的EIP,但是主体部分已经包括在内了, 达到这样的效果就足够了。
通过上面的两种方式基本上可以预防住运行时的一次性Dump,而且可以将上述方式扩展,将多段擦除,结合以前讲的方式,将多段代码由启动程序来写入,这样无论是启动时的Dump,还是运行时的Dump都不能获取到正确的内容了。
但是破解方式还是有的,比如这种方式,只需要跟踪调试程序,绕过WriteProcessMemroy函数的调用就可以了,或者直接一点,直接将此API函数挂接上并使其无效,无论多少此的自擦除都会无效。
write by 九天雁翎(JTianLing) -- www.jtianling.com
Posted By 九天雁翎 at 九天雁翎的博客 on 2009年02月08日