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

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


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

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

 


阅读全文....

Python C API 使用心得

Python C API 使用心得

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

 

工作的变化简直和人生变化一样不可预知.就在反外挂刚开始的时候,天天看的都是汇编和一大堆只有cracker(或想称为cracker)的人才会看的破解相关书籍,但是才过了几天,准备做被动的反外挂系统后,工作基本上就转到了用Python C API来写扩展模块和用Python来写Check程序了,真是突然。

8月份看完《Programming in LUA》以后,对脚本语言重拾兴趣,后来断断续续也算把《Python核心编程》,这本来是非常纯粹的Just for fun的事情,最后确还是免不了染上工作的性质,呵呵,这样说好像很奇怪,应该是说,凭借着一点兴趣,花了很多业余时间去学习的Python, 想不到竟然能这么快就在工作中有所应用与发挥,实话说,做老板的,肯定希望有这样的员工。。。。。何况,在老板心中我还算是新员工呢。我要是老板,我也希望做一个新东西前,本来说要调研调研的。。。。结果已经有员工用业余时间已经做过了,然后可以直接开工,唉。。。。我不知道碰到这样的情况我为什么会叹 气。。。。。。。

 

其实以前学的主要是Python语言的语法,用Python C API来写扩展模块还真没有写过,要是用LUA的话,虽然特别复杂,但是可能好歹还有个底。最后几乎是纯粹靠文档来解决了一切,(还有老总提供的一些没头没尾的例子)不要问我为什么不上网查,资料太少,而且在公司上网并不方便。

这里总结一下,以防忘记(我并不准备详细记录下每个步骤,仅仅记要点,假如需要详细步骤的,请参看Python document中的例子)

一:用C APIPythonC语言函数,以方便Python中调用,这种方法还是很简单的,和LUA C API的方法基本一样。(参看文档的Extending Python with C or C++部分)

1.  首先实现一个特定原型的函数,用Python C API来实现的话,所有函数必须是这种原型。必须是类似这样的

PyObject *Fun(PyObject *self, PyObject *args)

self应该是在用类的时候才会用到(我没有用到),args就是函数的参数。因为args是一个PyObject*类型(可以代表Python语言中的任何类型)

2.      将参数转换成C 语言表示的内容,用PyArg_ParseTuple函数。

3.      执行完需要的操作后,也必须返回一个PyObject*类型的值。通过Py_BuildValue函数来构建。

这里要说的是,假如希望返回一个Tuple类型的值,可以先用

PyObject *tuple = Py_BuildValue("(iis)", 1, 2, "three");

形式来构建,假如很多的话,可以用下面的方式来构建

PyObject *t;

 

t = PyTuple_New(3);

PyTuple_SetItem(t, 0, PyLong_FromLong(1L));

PyTuple_SetItem(t, 1, PyLong_FromLong(2L));

PyTuple_SetItem(t, 2, PyString_FromString("three"));

这一点在刚开始开工的时候迷惑了很久。

4.      将要输出的所有函数放入一个数组中,数组的结构是:

struct PyMethodDef {

    const char    *ml_name/* The name of the built-in function/method */

    PyCFunction  ml_meth;   /* The C function that implements it */

    int       ml_flags; /* Combination of METH_xxx flags, which mostly

                 describe the args expected by the C func */

    const char    *ml_doc;   /* The __doc__ attribute, or NULL */

};

数组以{NULL, NULL}结束

5.  构造一个Python import时初始化的函数

类似

PyMODINIT_FUNC

initexample(void)

{

    Py_InitModule("example", example_methods);

}

这里有个特别需要注意的是,初始化函数名字有严格要求,init后面必须跟模块名,否则Python找不到确定的函数会报没有初始化函数的错误

 

总结一下,记得第一次学习LUA C API接口的时候,碰到类似的扩展模块写法,还是感觉很奇怪,“怎么搞这么复杂啊”。。。。。当时为了实现一个简单的扩展都是折腾了很久,到了再一次碰到类似的问题,(Python C API写扩展的时候几乎就是和LUA C API一样),基本已经轻车熟路了。这里突然也想说一句,就算不是真的写非常底层的接口,但是用的接口多了,都是能有些感觉的。

 

扩展模块写完后,编译成动态库(Python要求此动态库名字为pyd,实际就是改个后缀而已)。就可以直接在Python脚本中用import的方式加载了,对于使用来说,根本不需要知道此库是用C API扩展写的还是直接用Python语句写的(这点Lua做的也是一样好)

最后,python的源代码中附带了一个叫做example_nt的例子,可以参考一样,完整的扩展代码如下:

#include "Python.h"

 

static PyObject *

ex_foo(PyObject *self, PyObject *args)

{

    printf("Hello, world/n");

    Py_INCREF(Py_None);

    return Py_None;

}

 

static PyMethodDef example_methods[] = {

    {"foo", ex_foo, METH_VARARGS, "foo() doc string"},

    {NULL, NULL}

};

 

PyMODINIT_FUNC

initexample(void)

{

    Py_InitModule("example", example_methods);

}

 

 

二.C语言中调用Python语句

首先,void Py_Initialize()用来初始化,void Py_Finalize()用来结束Python的调用,这是必须要的。

燃火分两种情况,假如仅仅是几条语句的话,那么以PyRun_为前缀的一些函数都很好用,比如

int PyRun_SimpleString(const char *command)

函数就可以直接执行一条char*Python语句。

需要获得返回值得话

PyObject* PyRun_String(const char *str, int start, PyObject *globals, PyObject *locals)

也很好用,以上两个函数用来处理Python源代码已经读入内存的情况,在文件中的时候

int PyRun_SimpleFile(FILE *fp, const char *filename)

PyObject* PyRun_File(FILE *fp, const char *filename, int start, PyObject *globals, PyObject *locals)

使用类似。不多讲了。

假如是个模块的话(比如一个函数),希望在C语言中调用的话那么使用起来就稍微复杂了一点。这种情况的需要在于你可以从C语言中向Python函数中传入参数并且执行,然后获取结果。

此处又分为几种情况:

在文件中,在内存中,编译过的,源代码。

在文件中都很好解决,和上面一样。这里主要讲在内存中的情况。(事实上我工作中需要并且耗费了很长时间才找到解决方法的就是这种情况)

未编译时:(也就是源代码)

1.通过

PyObject* Py_CompileString(const char *str, const char *filename, int start)

API首先编译一次。此API的参数我说明一下,str就是内存中的源代码,filename主要是出错时报错误用的,事实测试证明,你随意给个字符串也没有关系,但给NULL参数在运行时必然报错。start我一般用的是Py_file_input,因为的确是从文件中读取过来的,相对的还有Py_single_input用来表示一条语句,Py_eval_input的用法我也不是太清楚。

源代码通过此函数调用后,获得编译后的PyObject*,(其实假如跟进源代码中去看,是一个PyCodeObject结构)假设命名为lpCode

2.此时再调用API

PyObject* PyImport_ExecCodeModule(char *name, PyObject *co)

导入模块。参数也说明一下,name为导入的模块名,co就是前面编译过的代码对象(lpCode)。返回的就是模块对象了,假设命名为lpMod

3.再调用API

PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name)

获得函数对象。o就是模块对象(lpMod,attr_name就是你想要调用的函数名了,假设叫main的函数,就是”main”,然后返回的就是函数对象,假设命名为lpFun

4.此时可以用API

int PyCallable_Check(PyObject *o)

去检查一下是不是获得了一个函数。假如确定的话,就可以直接用

PyObject_Call开头的一族函数调用lpFun了。这些函数包括很多,一般就是输入参数的不同,但是效果都是一样的,就是调用函数而已。参数一般可以通过前面说过的build函数来获得,返回值也是获得一个PyObject*,可以通过PyArg_那个函数来获取,但是好像不太好,那是分析参数用的。推荐用确定类型(假设为type)的类似Py[type]_As的函数来获取。

比如:

long PyLong_AsLong(PyObject *pylong)获取long

double PyLong_AsDouble(PyObject *pylong)获取double

 

这里想说的是,应该有直接从源代码中获取函数调用对象的方式,但是我本人没有试出来,有人知道请一定赐教!

 

编译过的代码:

对于编译过的代码和上面就是获得编译后的PyCodeObject对象,当然在源代码中表示还是PyObject*的方法不同(上例中的lpCode)。

当然要想以后获得一个编译后的lpCode,自然要先编译一下啦。但是纯粹编译成pyc结尾的文件后,直接读入内存,我没有找到将其转化为PyCodeObject对象的方法(也希望有人知道能告诉我!)

 

我找到的方法是先用

PyObject* PyMarshal_WriteObjectToString(PyObject *value, int version)

void PyMarshal_WriteLongToFile(long value, FILE *file, int version)

两个函数先把PyCodeObject对象(lpCode)序列化到文件或者内存中。

再在需要的时候用函数

PyObject* PyMarshal_ReadObjectFromFile(FILE *file)

PyObject* PyMarshal_ReadObjectFromString(char *string, Py_ssize_t len)

读出来,读出来的PyObject*其实就是想要的PyCodeObject对象了(lpCode)。接下来的步骤与未编译时的步骤一样。

光是这个扭曲的方法我还是参考老总给的半边资料反复研究出来的。而真正直接有效的方法我还是没有找到。

 

我希望能解决的问题我总结一下,有2

1.     直接从源代码中读入,转成模块对象(lpMod),而不需要先通过Py_CompileString的调用。

2.     直接从pyc文件(内存)中读入,转成代码对象(lpCode)或者模块对象(lpMod),扭曲的通过序列化PyCodeObject对象再序列化出来的方式。

 

希望我从文档中摸索出来的东西对于有些同样需要的人有价值。这一些方法我在网上也找到,但是没有找到,最后还是靠自己摸索才找出了一些扭曲的方式来解决问题。

这里想对那些动不动就说你想把文档都看看的,你先把什么都看仔细了的兄弟们说一下,并不是每个老板都允许员工干一件事情前去把一种新的语言全学精了,哪怕他明知道这就是一种你不知道的语言。有的时候,just for work而 已。当然,在有时间的时候,自然我也是想把该学的都好好学了,只是,工作的时候,身不由己啊,何况,该学的东西是很多的,个人也可能有个人的学习计划,并不是工作中碰到一种问题,我的业余时间也得全部搭上去,把这件事情学通,所以请多给一些谅解,碰到有人问对你来说很初级的问题时,你好心的就给个回答,我 很感谢,不想回答的起码也不要说风凉话,我也很感谢了。其实我倒是也没有真发帖去问,因为等到有人回答估计一天的工作时间都耽误了,只是看到有类似的情况,所以随便说说,希望不要见怪。

 

 

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

 

阅读全文....

SoftIce,IDA pro强强联合!从SOFTICE中打开IDA Pro输出的map信息文件


SoftIce,IDA pro强强联合!从SOFTICE中打开IDA Pro输出的map信息文件

 

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

 

很搞笑的事情,才知道原来没有关系(指两个公司没有关系)的softice,ida pro两个软件其实也是可以交互的,这点除了在Linux下有这样的思想,其他地方还真少看到,一般都要等某个公司独霸了市场,然后大家慢慢向他靠拢以形成标准。才有可能做到通用。

其实softice支持的信息文件格式为nym格式的(我也不知道是什么格式),IDA Pro作为反汇编工具可以输出的格式还真算比较多,其中有种是 borland公司的map格式,这点倒好,还是没有办法通用,kris kaspersky的黑客调试技术揭秘中提到有个工具可以转换,叫做idasym,但是我一通暴搜,一无所获。最后几近周折(从官网找到了几个业余爱好者 blog)找到了一个插件,但是不能用。然后从ida pro的配置文件注释中发现了i2s插件,去下了一个,发现版本太老,不用用于最近的IDA,但是,就在绝望之中,发现了让人惊奇的事 实。。。。。。。。。。。。。。。。。。。。。。。。VS中有提供从mapnym转换的工具!!!至于为什么微软提供这样的工具我实在是不得而知。。。 但是。。。首次。。。由衷地感谢微软!

顺便说下工具的名字,MapSym.Exe
VS安装目录的Common7/Tools/Bin/下(VS2005),其他版本有没有我不缺确认,可以去找一找。

再顺便说下使用方法,格式如同mapsym -o outfile.nym infile.map

然后用softicesymload打开转换后的nym文件,载入成功后再载入需要调试的文件,这个时候就爽了!!!
可以任意在你分析后命了名的代码下断,呵呵,从此后,没有加壳的代码。。。。。无论是看,还是调试。。。基本已经和一般的源码么有区别了。。。(当然指的是非宏汇编源码)

 

 

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

阅读全文....