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

通过全局变量和自擦除代码来防Dump(2)


通过全局变量和自擦除代码来防Dump(2)

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

讨论新闻组及文件

通过全局变量和自擦除代码来防Dump 》中我提到了用WriteProcessMemory来完成代码段的自擦除,其实这并不是什么好的手段,因为使用了Win32 API,对于分析你代码的人来说,这是明摆着告诉对方你要干什么,破解手段在原文中也提到了不少。其实,当时我去搜寻WriteMemroy API但是并不存在的时候,我就在想,为什么微软不提供,并且,同样的,有ReadProcessMemory但是有ReadMemory,为什么要使用的时候都需要通过GetCurrentProcess()作为第一参数这样奇怪的形式,这个MS一贯的作风不符。这两个函数只在WinCE中有提供,普通Windows并没有,其实后来仔细想想就想通了,因为读写另外的进程的内存你必须要通过额外API,所以有这样的API给你来使用,但是读写自己的内存根本就不需要别的特殊的API,自己进程访问和改写自己进程中的数据是没有限制的。因此,需要做自擦除的操作仅仅需要memcpy或者memset就可以了!

下面是改进的示例:

 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*)ldwBegin, 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     memcpy((void*)ldwBegin, lpbyRand, ldwFuncLen);
38
39     delete[] lpbyRand;
40 }
41
42 int main(int argc, char* argv[])
43 {
44     Run();
45     MessageBox(NULL, _T("OK"), _T("OK"), MB_OK);
46
47     return 0;
48 }
49
50

 

这样做后,就没有办法简单的将memcpy挂上使其无效化来使得擦除无效,而且,从发现你的目的的难度上来讲也更加大一些,毕竟memcpy这种函数使用的频率太高了,你可能因为各种原因使用它,而不仅仅是反Dump的目的。这也加大了分析的难度:)

 

 

 

 

 

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

 

阅读全文....

通过全局变量和自擦除代码来防Dump


通过全局变量和自擦除代码来防Dump

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

讨论新闻组及文件

一般而言,你的程序一旦运行起来就没有办法防止Dump了,因为所有的数据都在内存中了,而且,为了更好的Dump完整程序,程序将要启动,还未启动时的Dump,任你程序中有多少防Dump的方法都没有用。这里只能结合两种方式来实现反Dump,其一,程序运行的时候其本身数据并不是完整的,以前我已经讲过方法了, EXE文件不能直接启动的方法以防止直接调试的方法》,只要多用几次这样的方法,比如启动代码无效,中间某段代码也无效,然后通过与启动程序的交互来完成中间代码的修改,只在启动程序通知后才继续运行,以防止错误。这样就没有办法通过在文中介绍的启动时DumpDump数据了。对于Dump的作用还不理解的,可以去使用一下LordPEDump功能,保证你能够见识到工具的作用性如此之大,并认识到自己程序多么的脆弱。

通过全局变量防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

 

阅读全文....

因为无法对抗,所以就放弃抵抗吗?--反外挂的迷思


因为无法对抗,所以就放弃抵抗吗?--反外挂的迷思

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

讨论新闻组及文件

因为破解总是不可避免的,所以就应该开着大门让人破解吗?因为外挂总是不可避免,所以就应该开着大门让人做外挂吗?

       即便是专业的安全专家,也没有办法做出无法破解的壳,我们是程序员,更加没有办法,所以这就是我们放弃抵抗的理由吗?-_-!

       总觉得自己是待宰的羔羊一样。。。。。。。。

       不过既然无论怎么样也没有办法避免,的确会不值得用太多时间,太多代价去做这样的无效工作。毕竟作为一个游戏开发公司,最最重要的当然是游戏产品。。。。呵呵,可怜的当然就像是我这样的小程序员罗,虽然是做反外挂的工作,但是却没有得到足够的时间,在最最短的时间内要开发出自己完全不知道的东西,只能是奇迹才可能了-_-!......................

       当然,还好的就是老总也没有对我有更高的要求-_-!呵呵,以前是游戏不能直接运行就行。最近的加壳工作仅仅是用个现成的壳就行。。。。。。。唉。。。哪怕这个壳对于我这个初学者来说手动脱壳都不需要半分钟........

       这里有个疑问就是,一个游戏的反静态分析,反调试,反dump,加壳的工作到底应该做到什么地步才算是比较合适的?

       难道真是按老总说的那样预防住那些完全没有技术的初学者就行了吗?能让那些有兴趣但是没有技术的人知难而退就够了?因为对于真的想破解并分析游戏并做外挂赚钱的人来说,没有什么可以难到他们-_-!

       刚开始还以为反外挂工作能让我有时间好好学习学习win32汇编,看来事实上机会还是不多了-_-!又一次的感叹,自己学的是越来越杂了,做游戏就从服务器做到客户端,到工具,光是语言也是从C学到C++Lua,python,加反复学习的汇编,平台也是windowsLinux兼收。。。。但是好像却很难有时间精通一样技术了.......................唉。。。。最后怎么学到这样了呢-_-!前段时间甚至还有学习Object-C或者JAVA去弄iPhoneAndroid的想法。。。。

 

 

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

 

阅读全文....

让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

 

阅读全文....

手动脱UPX壳


手动脱UPX

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

讨论新闻组及文件

个人手动脱壳定义,不是完全不用工具,仅仅是指不用脱壳机,并且手动寻找OEP,恢复IAT的时候使用ImportREC,但是手动找到IAT的位置,不用自动搜寻功能,其实找到了位置后,ImportREC还是做了新添加一个段,拷贝2进制数据,修改PE头中的IAT偏移地址这种工作,因为重复性太高,不手动进行了。

这些自然不是最佳,最快脱壳的方式,仅仅是学习。。。。。

需要用到的工具有OllyDbg(用于调试),LordPE(用于DumpLordPEDump似乎比OllyDbgDump插件更稳定,因为好像OlldyDbgDump插件还尝试做了一些其他工作),ImportREC(恢复IAT)

其实对于UPX这样著名的壳,网上教程实在是多,所以我没有必要再教大家一次,我不过是利用这个机会,练习练习而已,毕竟UPX定位于压缩壳,脱起来实在是比较容易。

 

先尝试:

upx203w版本

选择的对象就是发布的upx本身,因为其本身就是用此版本加的壳-_-!

1.OEP

方法其实有很多种,但是对于UPX壳。。。我发现最简单的方法就是直接向下翻页,找到最后一个00前的内容。。。。。。因为UPX将其他内容全部换成00了,这样的标志太明显了。

我这里最后几条语句是:

0048149D   .  61            POPAD

0048149E   .  8D4424 80     LEA     EAX, DWORD PTR [ESP-80]

004814A2   >  6A 00         PUSH    0

004814A4   .  39C4          CMP     ESP, EAX

004814A6   .^ 75 FA         JNZ     SHORT upx.004814A2

004814A8   .  83EC 80       SUB     ESP, -80

004814AB   .- E9 60FDF7FF   JMP     upx.00401210

 

最后一行的地址其实就是OEP了。。。。。。。。。。设个断,跟过去,直接用LordPE Dump all。。。。。工作已经完成一半,我在学习之前也没有想过会这么简单。

接下来找IAT,找到第一个系统函数调用,

00401218    FF15 E4E24700   CALL    NEAR DWORD PTR [47E2E4]          ; msvcrt.__set_app_type

__set_app_type,地址就在47E2E4这个地方了,数据窗口跟随地址,看到一堆函数,向前放到0000,在这里

0047E20C  00000000

0047E210  00000000

0047E214  7C835505  kernel32.AddAtomA

0047E218  7C812EAD  kernel32.CreateSemaphoreA

向后翻到0000,在这里

0047E3C0  77BED711  msvcrt.strtol

0047E3C4  77C1AECF  msvcrt.time

0047E3C8  77BEC9C9  msvcrt.tolower

0047E3CC  00000000

0047E3D0  00000000

然后开启ImortRec,选择正挂起的进程,将左下角的OEP填上刚找到的地址1210,(这里去掉了基址,仅需要RVA就可以了)

IATRVA一栏填上开始地址7E214,长度用7E3C8-7E214得到1B4(这一步仅仅是为了学习的目的,其实按IAT AutoSearch一般也可以得到正确的结果)。

然后点击Get Imports按钮,上面的列表框就有结果了,我这里显示一起都正常。即valid:YES

此时一切工作接近尾声,点击Fix Dump按钮,选择刚才Dump出来的文件,完成修复。脱壳完成。

这种脱壳的目的仅仅是为了更好的去调试和分析代码,其实并不完善,因为UPX加壳后保留的UPX0UPX1段名没有改,壳其实还是保留在文件中,这里仅仅是将入口改到了实际入口(OEP),并将数据恢复到了正确的地方。

其实从功能上来讲用原有upx工具的-d 选项来脱壳要好的多:)

 

目前UPX官方最新版的是upx303w

我还以为其有一定的改进,就实际效果而言,完全没有变化,可能毕竟定位于压缩壳的UPX稳定才是最重要的,所以脱壳基本上没有什么难度,其也没有想做成有什么难度,不然也不会提供自动脱壳功能。。。。。

要说明的一点是新版的壳用OllydbgDump插件,勾上FixIAT选项Dump会出现错误,ImportREC自动搜索IAT的时候大小和位置也会找错,我这里的效果是总是从第一调用的函数开始向后找,手动输入正确的IAT地址和长度就没有任何问题。

最后,UPX是有源码的,很适合作为第一款来脱的壳和第一款用来学习做壳的壳:)但是也给了我们一个提示,不要将无用的代码位置置为00,那样只能是给破解者最好的定位方式。

 

 

 

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

 

阅读全文....

手动脱ASPack壳


手动脱ASPack

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

讨论新闻组及文件

个人手动脱壳定义,不是完全不用工具,仅仅是指不用脱壳机,并且手动寻找OEP,恢复IAT的时候使用ImportREC,但是手动找到IAT的位置,不用自动搜寻功能,其实找到了位置后,ImportREC还是做了新添加一个段,拷贝2进制数据,修改PE头中的IAT偏移地址这种工作,因为重复性太高,不手动进行了。

这些自然不是最佳,最快脱壳的方式,仅仅是学习。。。。。

需要用到的工具有OllyDbg(用于调试),LordPE(用于DumpLordPEDump似乎比OllyDbgDump插件更稳定,因为好像OlldyDbgDump插件还尝试做了一些其他工作),ImportREC(恢复IAT)

压缩壳总是容易脱的,作为脱壳练手,再来一个著名的壳,ASPack

我用的版本是212,测试对象是用其将windows的计算器程序打包。

获取OEP

ASPack获取OEP就没有UPX那么简单了,当然,其实也还是不难,毕竟是压缩壳嘛。而且我尝试过压缩简单的程序(比如以前用汇编写的那个HelloWorld,几乎不会改变代码段,仅仅加密了IAT),因为calc.exe还算是一个正规的程序,所以体积不算很小,然后可以达到50%以上的压缩率,代码段自然也破坏了。

我首先用的是一种很笨的办法,直接通过段之间的跳转来定位,但是因为ASPack相当的狡猾。。。。它不是一个一个段的解压而是交替进行解压,导致的结果就是我没有办法通过在一段内存上面设一次断点,也没有办法通过先在数据段设断,然后再在代码段设断的方式获取OEP,最后只能用蛮办法,用OllyDbgRUN跟踪,设置跟踪中断条件为EIP执行到代码段,然后跟踪步进。。。。。。即便这样,中途ASPack竟然还特意到代码段Ret2次,呵呵,无耻啊。当然,最后通过这种办法,耗时超久,才找到了OEP.OllyDbg跟踪的指令条数在500W以上。。

01012475   .  6A 70         PUSH    70

01012477   .  68 E0150001   PUSH    复件_cal.010015E0

0101247C   .  E8 47030000   CALL    复件_cal.010127C8

01012481   .  33DB          XOR     EBX, EBX

01012483   .  53            PUSH    EBX                              ; /pModule => NULL

01012484   .  8B3D 20100001 MOV     EDI, DWORD PTR [1001020]         ; |kernel32.GetModuleHandleA

0101248A   .  FFD7          CALL    NEAR EDI                         ; /GetModuleHandleA

 

第一句就是入口

然后再通过堆栈平衡的方法来找OEP,结果不需要一秒钟就能找到,简直崩溃。

方法是在外壳通过PUSHAD保护现场后(很多外壳第一句都这样)

查看堆栈

0006FFA4   7C930208  ntdll.7C930208

0006FFA8   FFFFFFFF

0006FFAC   0006FFF0

0006FFB0   0006FFC4     -- 原有ESP

0006FFB4   7FFDB000

0006FFB8   7C92E4F4  ntdll.KiFastSystemCallRet

0006FFBC   0006FFB0

0006FFC0   00000000

 

然后根据堆栈平衡条件,在6FFA4下访问中断(一般的情况是在这里调用了POPAD了),或者在在6FFC4(原来的ESP-4=6FFC0处(其实也可以通过PUSHAD压入8个寄存器算到,6FFA4+(8-1)*4=6FFC0)下写中断(一般的情况是POPAD后,通过PUSH 地址,然后RET返回原有代码段),这样只要壳没有特意的隐藏OEP,一般一次下断就能获取OEP

 

恢复IAT

根据

010124E3   .  FF15 0C120001 CALL    NEAR DWORD PTR [100120C]         ;  msvcrt.__set_app_type

一句,可以找到100120C属于IAT的一部分。然后用ImportRec恢复就好了,不多说了。

 

非加密壳,还是比较容易脱的,当然,写就没有那么容易了。。。。

 

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

 

阅读全文....

女朋友用Python实现的猜数字游戏:)

1 from random import randint
 2
 3 def ran():
 4     return randint(1,100)
 5

 6 c=ran()
 7

 8 # Guess
Number

 9 # Writen By
小乖乖

10 count=0
11
while(count<5):
12     
13
    a=input()
14
15
    if a>c:
16         print "too large ,try again"
17
    elif a<c:
18         print "too small, again"
19
    else:    
20
        print "good
,right
"
21         print "count number
times=
",count+1
22         break
23
    count=count+1
24 else:
25     print "answer=",c
26     
27

阅读全文....

C++中字母大小写转换实现的优化

 

 

C++中字母大小写转换实现的优化

write by 九天雁翎(JTianLing) --
blog.csdn.net/vagrxie

讨论新闻组及文件

在本文中全部以转换为小写为例。

从推荐复用代码的角度来看,用库函数是不错的办法:

方案一:

 char gc1[53] = "abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ";

 

void wayOne()

{

    strlwr(gc1);

}

 

优点是使用方便,别人看着也容易理解,但是效率慢的让人吐血。

extern "C" char * __cdecl _strlwr (

        char * string

        )

{

    if (__locale_changed == 0)

    {

        char * cp;

 

        /* validation
section */

        _VALIDATE_RETURN(string != NULL,
EINVAL, NULL);

 

        for (cp=string; *cp; ++cp)

        {

            if
('A' <= *cp
&& *cp <= 'Z')

                *cp
+= 'a' - 'A';

        }

 

        return(string);

    }

    else

    {

        _strlwr_s_l(string, (size_t)(-1),
NULL);

        return string;

    }

}

 

循环中平均2.5次的判断,(*cp一次,ifA<=一次,*cp<=版次)加平均每次0.5次的加法,虽然这样的转换O(n)是必不可少的,但是对于这样多的操作还是慢的可怕。

2

char gc2[53]
= "abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ";

namespace MYTEST

{

    inline char*
strlwr(char
*asz)

    {

       for(char*
lp = gc2;
*lp != 0; ++lp)

       {

           *lp |= 0x20;

       }

 

       return asz;

    }

}

 

 

void wayTwo()

{

    MYTEST::strlwr(gc2);

}

此例中利用了ASCII字母值的特点,一共只有一次判断(*lp=0,一次位或操作。算法上提高了很多:)其实已经达到了1/3的效率提升。。。。。

将原来一大堆的代码,转化成了反汇编只有4句的程序:

00401020 80 08 20         or          byte ptr [eax],20h

00401023 83 C0 01         add         eax,1

00401026 80 38 00         cmp         byte ptr [eax],0

00401029 75 F5            jne         wayTwo+10h (401020h)

但是考虑到char只是1个字节,看到

00401020 80 08 20         or          byte ptr [eax],20h

一句都感觉不爽,白白浪费了eax 这样4个字节的寄存器,于是可以这样优化:

namespace MYTEST2

{

    inline char*
strlwr(char
*asz)

    {

       long* lp
= (long*)gc3;

       for(; *((char*)lp) != 0; ++lp)

       {

           (long)(*lp) |= 0x20202020;

       }

 

       for(char*
lpc = (char*)lp;*lpc!=0; ++lpc)

       {

           *lpc |= 0x20;

       }

 

       return asz;

    }

}

 

说实话,。。。。。。。。。。。没有任何清晰性可言,没有任何可读性可言,但是优化的思想就是充分的利用4个字节的寄存器,并且以DWORD来读取内存,这是很有效率的方式。汇编代码其实比C语言代码更加清晰,原因在于C语言代码还需要处理大量与类型相关的事情,汇编代码不需要。

第一个循环汇编代码如下:

00401040 81 08 20 20 20 20 or          dword ptr [eax],20202020h

00401046 83 C0 04         add         eax,4

00401049 80 38 00         cmp         byte ptr [eax],0

0040104C 75 F2            jne         wayThree+10h (401040h)

将循环次数减少了3/4。。。。所以效率的优化还是很明显的。单指令多数据操作的思想不过就是这种思想的延生罢了。。。呵呵,但是说在前面,如此影响可读性的效率优化,除非在很必要的情况下,不然慎用。。。。。

为了证实效率的优化,起码也得给出一个测试结果给大家看看吧,不然还以为我胡扯了。

 

void wayOne()

// Hit Count          : 1

// Time               : 5553.00

// Time with Children : 5553.00

{

       strlwr(gc1);

}

 

void wayTwo()

// Hit Count          : 1

// Time               : 247.00

// Time with Children : 247.00

{

       MYTEST::strlwr(gc2);

}

 

void wayThree()

// Hit Count          : 1

// Time               : 180.00

// Time with Children : 180.00

{

       MYTEST2::strlwr(gc3);

}

 

int _tmain(int argc, _TCHAR* argv[])

// Hit Count          : 1

// Time               : 6836996435.00

// Time with Children : 6837002415.00

{

       wayThree();

       wayTwo();

       wayOne();

}

 

测试结果为AQtime5测试数据,单位为机器周期,因为结果已经很明显了,所以没有进行多次循环的测试。并且为了排除缓存的影响,将最快的放在了最前面,那么哪怕有缓存的影响,对于wayThree也是最不利的才对。库函数的5000多的结果,说慢的可怕并不为过。在数据量很大的时候,这种优化的差异可不是一点点而已。

 

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

 

阅读全文....

调试用Python C API 写的程序问题还真多,关于import搜索路径的,复制过来,以防忘记

其实本地的import调试倒是感觉没有什么问题,但是一旦通过网络的序列化,然后再反序列化出来,PyImpor模块的时候,再报模块不存在,就很让人郁闷了,刚开始根本就不知道是什么问题,本地测好的程序,怎么一到了网络环境就出问题了呢?郁闷啊。找了半天,才发现是自己写的扩展库在本地的路径没有找到,而实际上Python API报的错误根本就不靠谱,唉。。。。。。。。。。。吃一堑长一智啊。。。。

 

原帖位置:

http://groups.google.com/group/python-cn/browse_thread/thread/f4e2b245147182ad

 

1、可以修改python的环境变量PYTHONPATH
2、可以在程序里动态的修改sys.path
import sys
# 获得当前文件的路径并添加到sys.path
sys.path.append(os.getcwd())
import other_python

阅读全文....

偏偏给我碰到Python C API的Bug?Py_Initialze()直接死循环

 今天在一个程序中,在不同的两个函数,先用Py_Initialize()初始化,然后用Py_Finalize()结束,在本机都调试没有问题,然后将DLL发给同事后,老是在Py_Initialize()进入死循环,莫名其妙!怎么调试都没有用,奇怪了,然后将前一次的Py_Finalize()和后一次Py_Initialize()的取消,也就是仅仅经过一次的Py_Initialize(),Py_Finalize()就完全没有问题。

这点也很奇怪,毕竟本机调试是没有任何问题的,在同事那里虽然换成了多线程环境,但是也没有道理总是必在Py_Initialize()死掉吧?看了Python的文档,才发现Py_Finalize()函数后有这么一段

 

Bugs and caveats: The destruction of modules and objects in
modules is done in random order; this may cause destructors (__del__() methods) to fail when they depend on other objects
(even functions) or modules. Dynamically loaded extension modules loaded by
Python are not unloaded. Small amounts of memory allocated by the Python
interpreter may not be freed (if you find a leak, please report it). Memory tied
up in circular references between objects is not freed. Some memory allocated by
extension modules may not be freed. Some extensions may not work properly if
their initialization routine is called more than once;
this can happen if an
application calls Py_Initialize() and Py_Finalize() more than once.

 

难道是这个问题?我这里也不是extensions不工作的问题啊。。。。。。。或者还是多线程的问题?莫名奇妙啊。。。。。。

 

这里顺便记个关于Python在对线程环境下工作的文章,明天去好好研究一下是什么问题。

C++调用PythonAPI线程状态和全局解释器锁

http://blog.csdn.net/marising/archive/2008/10/08/3032278.aspx

 


阅读全文....