从看雪论坛转过来,好好收藏!
程序员部落酋长Joel在他所著的《软件随想录》说,大学里用Java来教学生,会毁了计算机系的学生,Java只适合在社区学校里做技能培训。回想这两年的工作,发觉自己对计算机真的是越来越陌生了,我怀疑会不会在两年之后,自己除了面向对象什么都不会了。我苦恼,懊悔自己当初的选择。
重新定位,重新摆正学习的方向,重新捡起自己感兴趣的东西,重新走自己喜欢的路,一切重新开始……,于是两个月内恶补win32汇编与软件加密解密基础,使用的教材都是大家耳熟能详的《Windows环境下32位汇编语言程序设计》及《加密与解密》,经常在看雪上潜水,由于是非正式会员,仅能浏览大师们的帖子,故在此原创一文,期盼能获得一邀请码,也能在各个版面和大师们煮酒论是非。
首次发帖,略显激动,废话感慨颇多,现言归正传……
我们采用《Windows环境下32位汇编语言程序设计》13.3.3节(调试API的使用)中的例子程序Text.exe,该程序使用Upx做了压缩处理,类似于简单的加壳。加壳之后,原来的程序代码已被做了处理,只进行静态分析已经不能起作用了。此时,必须采用动态分析技术,跟踪代码一直到可执行程序在内存中被恢复为止,然后分析原始的可执行程序,手工地打上补丁。或者,在分析原程序的基础上,写出内存补丁。书本上的破解例子是采用单步中断方式,一直到00401000,本文的实现是在对原程序分析的基础上,一步到位在00401000处下断点的方式,其实效果都一样,异曲同工。
原程序运行时的状况:
补丁在哪里打?打什么?当然要动态分析一下了……,打开OD,加载Text.exe,Ctrl+G,跳到0040526F的地方,按F2打断点,按F9执行到此处,然后F8单步调试。 代码:
到此对原程序的分析已经完毕,我们的内存补丁思路也已经清晰了: 以调试方式打开被破解的程序,利用CreateProcess()函数 等待调试事件发生,利用WaitForDebugEvent()函数 当被调试程序的进程创建完毕的时候,保存0x0040526F处的原指令,打断点(int 3指令) 程序执行到0x0040526F时产生中断,此时执行内存补丁,恢复0x0040526F处的原指令,并让程序从0x0040526F处重新执行 下面贴出源代码供大家交流: 代码:[/cpp]
其中代码中,判断中断的位置是否是我们自己下断点的地方是很关键的,因为程序会在其它地方也发生断点中断。而如果在那时候处理,原程序还没有在内存中恢复。 该程序是在VS2008下编译通过的,运行程序,看一下结果:
程序员部落酋长Joel在他所著的《软件随想录》说,大学里用Java来教学生,会毁了计算机系的学生,Java只适合在社区学校里做技能培训。回想这两年的工作,发觉自己对计算机真的是越来越陌生了,我怀疑会不会在两年之后,自己除了面向对象什么都不会了。我苦恼,懊悔自己当初的选择。
重新定位,重新摆正学习的方向,重新捡起自己感兴趣的东西,重新走自己喜欢的路,一切重新开始……,于是两个月内恶补win32汇编与软件加密解密基础,使用的教材都是大家耳熟能详的《Windows环境下32位汇编语言程序设计》及《加密与解密》,经常在看雪上潜水,由于是非正式会员,仅能浏览大师们的帖子,故在此原创一文,期盼能获得一邀请码,也能在各个版面和大师们煮酒论是非。
首次发帖,略显激动,废话感慨颇多,现言归正传……
我们采用《Windows环境下32位汇编语言程序设计》13.3.3节(调试API的使用)中的例子程序Text.exe,该程序使用Upx做了压缩处理,类似于简单的加壳。加壳之后,原来的程序代码已被做了处理,只进行静态分析已经不能起作用了。此时,必须采用动态分析技术,跟踪代码一直到可执行程序在内存中被恢复为止,然后分析原始的可执行程序,手工地打上补丁。或者,在分析原程序的基础上,写出内存补丁。书本上的破解例子是采用单步中断方式,一直到00401000,本文的实现是在对原程序分析的基础上,一步到位在00401000处下断点的方式,其实效果都一样,异曲同工。
原程序运行时的状况:
补丁在哪里打?打什么?当然要动态分析一下了……,打开OD,加载Text.exe,Ctrl+G,跳到0040526F的地方,按F2打断点,按F9执行到此处,然后F8单步调试。 代码:
00401000 33C0 xor eax, eax 00401002 0BC0 or eax, eax 00401004 74 15 je short 0040101B 00401006 6A 00 push 0 00401008 68 40204000 push 00402040 0040100D 68 2C204000 push 0040202C 00401012 6A 00 push 0 00401014 E8 19000000 call 00401032 ; jmp to USER32.MessageBoxA 00401019 EB 10 jmp short 0040102B 0040101B 6A 10 push 10 0040101D 6A 00 push 0 0040101F 68 10204000 push 00402010 00401024 6A 00 push 0 00401026 E8 07000000 call 00401032 ; jmp to USER32.MessageBoxA 0040102B 6A 00 push 0 0040102D E8 06000000 call 00401038 ; jmp to kernel32.ExitProcess 00401032 - FF25 08204000 jmp dword ptr [402008] ; USER32.MessageBoxA 00401038 - FF25 00204000 jmp dword ptr [402000] ; kernel32.ExitProcess由于xor eax, eax,故eax==0,or eax,eax结果仍为零,所以je short 0040101B这里一定跳转(这是测试CM,所以有些小儿科……^+^),而它跳到的地方就是提示“盗版软件”的地方,这里就是需要打补丁的地方。所以,我们可以在此处做文章,可以将00401004与00401005处的字节改为90h与90h(即nop指令),可以将je改为jne,也可以将00401005处的15改为00,直接让程序走下一条指令。效果都是一样的,这里选择最后一种来实现。
到此对原程序的分析已经完毕,我们的内存补丁思路也已经清晰了: 以调试方式打开被破解的程序,利用CreateProcess()函数 等待调试事件发生,利用WaitForDebugEvent()函数 当被调试程序的进程创建完毕的时候,保存0x0040526F处的原指令,打断点(int 3指令) 程序执行到0x0040526F时产生中断,此时执行内存补丁,恢复0x0040526F处的原指令,并让程序从0x0040526F处重新执行 下面贴出源代码供大家交流: 代码:[/cpp]
#include "stdafx.h" #include "windows.h" int _tmain(int argc, _TCHAR* argv[]) { STARTUPINFO stSi; PROCESS_INFORMATION stPi; DEBUG_EVENT stDe; int nAddrOfBreakPoint = 0x0040526F; //需要下断点的地址 int nAddrOfPatch = 0x00401005; //需要打补丁处的地址 unsigned char cOldByte; //用于保存nAddrOfBreakPoint断点处的原指令 unsigned char cBreakPoint = 0xCC; //int 3指令 unsigned char cPatchByte = 0x00; //补丁字节 CONTEXT stThreadContext; BOOL bCreated = FALSE; BOOL bFinished = FALSE; LPTSTR szCmdLine = _tcsdup(TEXT("test.exe")); GetStartupInfo(&stSi); bCreated = CreateProcess(NULL,szCmdLine,NULL,NULL,FALSE,DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&stSi,&stPi); if(!bCreated){ MessageBox(GetActiveWindow(),TEXT("不能打开test.exe文件!"),TEXT("Result"),MB_OK); //不能打开test.exe时清除资源 if(szCmdLine!=NULL) free(szCmdLine); return 0; } //开始调试运行,等待调试事件发生 while(WaitForDebugEvent(&stDe,INFINITE)){//有调试事件发生 switch(stDe.dwDebugEventCode){ case CREATE_PROCESS_DEBUG_EVENT: //写断点(int 3[0xCC])到nAddrOfBreakPoint,之前要保存原始指令 ReadProcessMemory(stPi.hProcess,(LPVOID)nAddrOfBreakPoint,&cOldByte,1,NULL); WriteProcessMemory(stPi.hProcess,(LPVOID)nAddrOfBreakPoint,&cBreakPoint,1,NULL); break; case EXCEPTION_DEBUG_EVENT: if(stDe.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT){//断点异常 //若不是在nAddrOfBreakPoint处中断,则继续等待 stThreadContext.ContextFlags = CONTEXT_CONTROL; GetThreadContext(stPi.hThread,&stThreadContext); if(stThreadContext.Eip!=nAddrOfBreakPoint+1) break; //执行内存补丁 WriteProcessMemory(stPi.hProcess,(LPVOID)0x00401005,&cPatchByte,1,NULL); //恢复nAddrOfBreakPoint原始指令,并重新执行该指令 WriteProcessMemory(stPi.hProcess,(LPVOID)nAddrOfBreakPoint,&cOldByte,1,NULL); stThreadContext.ContextFlags = CONTEXT_FULL; GetThreadContext(stPi.hThread,&stThreadContext); stThreadContext.Eip = nAddrOfBreakPoint; SetThreadContext(stPi.hThread,&stThreadContext); } break; case EXIT_PROCESS_DEBUG_EVENT: bFinished = TRUE; } ContinueDebugEvent(stPi.dwProcessId,stPi.dwThreadId,DBG_CONTINUE);//继续让被调试的程序执行 if(bFinished)break; } //破解成功后清除内存资源 if(szCmdLine!=NULL) free(szCmdLine); CloseHandle(stPi.hThread); CloseHandle(stPi.hProcess); return 0; }
其中代码中,判断中断的位置是否是我们自己下断点的地方是很关键的,因为程序会在其它地方也发生断点中断。而如果在那时候处理,原程序还没有在内存中恢复。 该程序是在VS2008下编译通过的,运行程序,看一下结果:
收藏的用户(0) X
正在加载信息~
推荐阅读
最新回复 (0)
站点信息
- 文章2302
- 用户1336
- 访客10968998
每日一句
Qingming Festival invites us to honor ancestors with quiet reflection and respect.
清明节邀请我们以静思与敬意祭奠祖先。
清明节邀请我们以静思与敬意祭奠祖先。
新会员