九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

让EXE文件不能直接启动的方法以防止直接调试的方法

让EXE文件不能直接启动的方法以防止直接调试的方法

write by九天雁翎(JTianLing) – www.jtianling.com**

讨论新闻组及文件

 

游戏中经常需要这样的技术,即让游戏主程序不能直接启动,通过这样的方式,可以在一定程序上达到防止调试的目的,虽然仅仅是最最简单的防止,但是仍然有一定的作用。

这里讲讲这样的技术。。。。。其实研究甚浅。。。。

基本思路有两种,其一就是直接破坏PE头,那么此文件根本无法加载,自然更没有办法加载了。但是我们自己必须的完全模拟系统加载PE文件的过程,代价有点大。所以,虽然此方案更好,但是我没有深入的研究。其二就是仅仅破坏一段有效的代码,只要你在程序必须执行的地方插入了一堆无效数据,程序自然一运行就崩溃。目的达到了。

这里从破坏《从最简单的Win32汇编程序,HelloWorld说起》文中介绍的一个最简单的程序开始。

原程序源代码:

**.486**                          ; create 32 bit code  
**.model**  flat, **stdcall**               ; 32 bit memory model  
**option**  casemap :none               ; case sensitive

**include**  windows.inc  
**include**  masm32.inc  
**include**  user32.inc  
**include**  kernel32.inc

**includelib**  masm32.lib  
**includelib**  user32.lib  
**includelib**  kernel32.lib

**.data**  
    szCaption **db**  "A MessageBox !",0  
    szText **db**  "Hello,World !",0

**.code**

start:  
    **invoke**  MessageBox,NULL,**offset**  szText,/  
            **offset**  szCaption,MB_OK  
    **invoke**  ExitProcess,NULL

**end**  start

 

反汇编的代码:

00401000 >/$  6A 00         PUSH    0                                ; /Style = MB_OK|MB_APPLMODAL

00401002  |.  68 00304000   PUSH    helloWor.00403000                ; |Title = "A MessageBox !"

00401007  |.  68 0F304000   PUSH    helloWor.0040300F                ; |Text = "Hello,World !"

0040100C  |.  6A 00         PUSH    0                                ; |hOwner = NULL

0040100E  |.  E8 07000000   CALL    <JMP.&user32.MessageBoxA>        ; /MessageBoxA

00401013  |.  6A 00         PUSH    0                                ; /ExitCode = 0

00401015  /.  E8 06000000   CALL    <JMP.&kernel32.ExitProcess>      ; /ExitProcess

0040101A   $- FF25 08204000 JMP     NEAR DWORD PTR [<&user32.Message>;  user32.MessageBoxA

00401020   .- FF25 00204000 JMP     NEAR DWORD PTR [<&kernel32.ExitP>;  kernel32.ExitProcess

 

这里我将其前一个字节改为55,那么其前三句将会解析成如下形式:

00401000 >/$  55            PUSH    EBP                               ; |Title = ""

00401001  |.  0068 00       ADD     BYTE PTR [EAX], CH                ; |

00401004  |.  3040 00       XOR     BYTE PTR [EAX], AL                ; |

 

这是再运行这个程序必然是崩溃的。这里必须要我们自己的程序去修复它,然后再运行它才能运行成功,这里以一个字节的修改为例,其实实际中你愿意改多少,改多少段完全是由你自己决定的。

启动程序基本思路及过程如下,首先用CreateProcess启动刚才修改过的应用程序,但是将其挂起,然后用VirtualProctectEx函数将挂起的进程需要修改的代码段属性设为可写,然后再用WriteProcessMemroy函数将正确的结果写入,然后再通过ResumeThread将挂起的进程运行。这时,就可以通过你的启动程序去启动被破坏的程序了,而正常情况下,被破坏的程序只能是由你的启动程序来启动。

 

全部启动源代码如下:

 1 #include <windows.h>  
 2 #include <tchar.h>  
 3   
 4   
 5 **int**  main(**int**  argc, **char** * argv[])  
 6 {  
 7     STARTUPINFO si;  
 8     PROCESS_INFORMATION pi;  
 9     LPTSTR szCmdline=_tcsdup(TEXT("HelloWorld2"));  
10     
11     ZeroMemory( &si, **sizeof**(si) );  
12     si.cb = **sizeof**(si);  
13     ZeroMemory( &pi, **sizeof**(pi) );  
14     
15     // Start the child process.   
16     **if**( !CreateProcess( NULL,   // No module name (use command line)  
17         szCmdline,      // Command line  
18         NULL,           // Process handle not inheritable  
19         NULL,           // Thread handle not inheritable  
20         FALSE,          // Set handle inheritance to FALSE  
21         CREATE_SUSPENDED,              // Suspended the process, the key!  
22         NULL,           // Use parent's environment block  
23         NULL,           // Use parent's starting directory   
24         &si,            // Pointer to STARTUPINFO structure  
25         &pi )           // Pointer to PROCESS_INFORMATION structure  
26         )   
27     {  
28         printf( "CreateProcess failed (%d)./n", GetLastError() );  
29         **return**  -1;  
30     }  
31     
32     DWORD ldwOldPro = 0;  
33     **if**(!VirtualProtectEx(pi.hProcess, (**void** *)0x401000, 1, PAGE_EXECUTE_READWRITE, &ldwOldPro))  
34     {  
35         printf( "VirtualProtectEx failed (%d)./n", GetLastError() );  
36         TerminateProcess(pi.hProcess, -1);  
37         // Close process and thread handles.   
38         CloseHandle( pi.hProcess );  
39         CloseHandle( pi.hThread );  
40         **return**  -1;  
41     }  
42     
43     DWORD ldwWritten = 0;  
44     BYTE lbt = 0x6A;  
45     **if**(!WriteProcessMemory(pi.hProcess, (**void** *)0x401000, &lbt, 1, &ldwWritten))  
46     {  
47         printf( "WriteProcessMemory failed (%d)./n", GetLastError() );  
48         TerminateProcess(pi.hProcess, -1);  
49         // Close process and thread handles.   
50         CloseHandle( pi.hProcess );  
51         CloseHandle( pi.hThread );  
52         **return**  -1;  
53     }  
54     
55     **if**(!VirtualProtectEx(pi.hProcess, (**void** *)0x401000, 1, ldwOldPro, &ldwOldPro))  
56     {  
57         printf( "VirtualProtectEx failed (%d)./n", GetLastError() );  
58         TerminateProcess(pi.hProcess, -1);  
59         // Close process and thread handles.   
60         CloseHandle( pi.hProcess );  
61         CloseHandle( pi.hThread );  
62         **return**  -1;  
63     }  
64     
65     **if**(-1==ResumeThread(pi.hThread))  
66     {  
67         printf( "ResumeThread failed (%d)./n", GetLastError() );  
68         TerminateProcess(pi.hProcess, -1);  
69         // Close process and thread handles.   
70         CloseHandle( pi.hProcess );  
71         CloseHandle( pi.hThread );  
72         **return**  -1;  
73     }  
74   
75   
76   
77     // Wait until child process exits.  
78     WaitForSingleObject( pi.hProcess, INFINITE );  
79   
80     // Close process and thread handles.   
81     CloseHandle( pi.hProcess );  
82     CloseHandle( pi.hThread );  
83   
84     
85     **return**  0;  
86 }

 

再次说明,这里仅仅是示例,所以仅仅修改了一个字节,假如你改动字节比较多的话,直接通过OD或者SoftIce来调试你的应用程序就没有办法了,当然,假如你的启动程序没有任何防护,是可以先调试你的启动程序的。

 

 

但是方法有个完全的破坏方法,那就是Dump。

Dump方法:

因为此例实在是太过于简单,所以在程序运行后,也就是弹出对话框后,再调用LordPE载入进程Dump也完全可以,但是在实际中,可能因为程序在启动时修改该了一些全局的数据,此时Dump会有问题。

正确的Dump步骤应该是在程序恢复运行的一瞬间,也就是将要启动却还未启动的时候。以前在一个程序将要启动却还未启动时Dump有个小技巧,那就是将其前一个字节修改为0xCC,那么,启动的一瞬间就会出现调试中断,然后将OD等调试工具设为默认调试工具,就可以在此时中断进程,并进行Dump,但是在此例中比较特殊,因为第一个字节本来就是由程序写入的,所以你没有办法通过修改文件首字节的办法来完成工作:)而且就我注意到,目前所有可以Dump的程序(也许是我见得不多,http://www.pediy.com/bbshtml/bbs7/pediy7-659-5.htm

一文中介绍的Dump工具我都用过)

都是先遍历进程,然后再Dump的,但是挂起还没有运行的进程它们竟然都检查不出来-_-!这点我很奇怪,我也不知道他们都是用什么来遍历进程的,但是windows的任务管理器就可以遍历出来(Windows的任务管理器其实相当的强悍,以前我做了一个进程占用CPU,内存资源百分比的监视工具,才知道要做好那么多任务不是那么简单的)。

呵呵,可以进行的办法是动态修改首字节,或者自己写一个给出进程ID就可以进行Dump的工具。。。。或者直接修改我比较习惯的LordPE的遍历进程方式,让其可以遍历出挂起的进程。。。。

这里我做个程序用于Dump上述程序。

此程序用于将指定进程ID的进程首字节动态改为CC以出现调试中断,并且将原有的首字节读取出来,并输出,以方便中断出现后,在OD中将其改为原有值,然后Dump。经测试,此方式的确可以做到Dump上述动态修改的挂起进程的目的:)

此程序也起到了很方便的作用:)

源代码如下:

 1 #include "windows.h"  
 2 int main(int argc, char* argv[])  
 3 {  
 4     printf("Give me a Process ID: ");  
 5   
 6     DWORD ldwPid = 0;  
 7     scanf("%d", &ldwPid);  
 8   
 9     HANDLE lhPro = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,  
10         FALSE, ldwPid);  
11   
12     if(lhPro == NULL)  
13     {  
14         printf( "Error Process ID(%u) or OpenProcess failed (%d)./n",ldwPid, GetLastError() );  
15         return -1;  
16     }  
17   
18     DWORD ldwOldPro = 0;  
19     if(!VirtualProtectEx(lhPro, (void*)0x401000, 1, PAGE_EXECUTE_READWRITE, &ldwOldPro))  
20     {  
21         printf( "VirtualProtectEx failed (%d)./n", GetLastError() );  
22         CloseHandle( lhPro );  
23         return -1;  
24     }  
25   
26     DWORD ldwReaded = 0;  
27     BYTE lbtFirst = 0;  
28     if(!ReadProcessMemory(lhPro, (void*)0x401000, &lbtFirst, 1, &ldwReaded))  
29     {  
30         printf( "ReadProcessMemory failed (%d)./n", GetLastError() );  
31         CloseHandle( lhPro );  
32         return -1;  
33     }  
34   
35   
36     DWORD ldwWritten = 0;  
37     BYTE lbt = 0xcc;  
38     if(!WriteProcessMemory(lhPro, (void*)0x401000, &lbt, 1, &ldwWritten))  
39     {  
40         printf( "WriteProcessMemory failed (%d)./n", GetLastError() );  
41         CloseHandle( lhPro );  
42         return -1;  
43     }  
44   
45     printf( "First BYTE Changed to CC,and origin first BYTE is %X./n", lbtFirst);  
46   
47     if(!VirtualProtectEx(lhPro, (void*)0x401000, 1, ldwOldPro, &ldwOldPro))  
48     {  
49         printf( "VirtualProtectEx failed (%d)./n", GetLastError() );  
50         CloseHandle( lhPro );  
51         return -1;  
52     }  
53     // Close process handles.   
54     CloseHandle( lhPro );  
55   
56   
57     
58     return 0;  
59 }

因为此程序已经有一定的实用性,方便了目前不是太方便的动态修改头字节为CC以实现Dump动态修改并挂起的进程,为了方便不喜欢编译的兄弟,我将其编译后放到讨论新闻组及文件 ,名字为DynamicChangeFirstBYTE.exe

 

 

 

 

 

write by九天雁翎(JTianLing) – www.jtianling.com

分类:  未分类 
标签: 

Posted By 九天雁翎 at 九天雁翎的博客 on 2009年02月06日

前一篇: 手动脱UPX壳 后一篇: 因为无法对抗,所以就放弃抵抗吗?--反外挂的迷思