原文:https://www.pediy.com/kssd/pediy11/104782.html
从2010\1\8开始,在这此帖整理自己的学习笔记:
1>2009\2\7就买到了《加密与解密》第三版,可到现在只会对无壳的软件暴破(太懒了!),自己都无颜了,再这样下去,怕是走不进逆向的世界了。
2>在此帖中得到无形的监督
3>相信也有雪友在学习中,希望能相互交流,共同进步。
故,开始《加》的学习。
----------------------------------------------开始-------------------------------------------------------------
;--------include files-------- include gdi32.inc includelib gdi32.lib include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib ...
打开文件对话框Comctl32.dll:标题栏、进度条、状态栏、滚动条、工具栏、提示文本、树型视图、列表视图等控件的相关API,都要用到这个库。
保存文件对话框
字体选择对话框
颜色选择对话框
查找和替换文本对话框
页面设置对话框
浏览目录对话框 等相关API,就要用到comdlg32.dll
1.6 windows各操作系统与Unicode的处理代码:;---------------------------------------------- ;EQU define ;---------------------------------------------- DLG_MAIN EQU 1000 对话框 ICO_MAIN EQU 100 程序图标 ;... ...s RED_TXT EQU 1001 Richedit控件 IDM_OPENFILE EQU 10002 以下都是菜单项的句柄 IDM_EXIT EQU 10003 IDM_BASICINFO EQU 10004 IDM_IMPORT EQU 10005 IDM_EXPORT EQU 10006 IDM_RESOURCE EQU 10007 IDM_RELOCATION EQU 10008 IDM_VERSION EQU 10009
windows98里MessageBoxW函数的内部定义 int MessageBoxW(MessageBoxExW{ // 调用MessageBoxExW()函数);WideCharToMultiByte(); // 取得要显示文本的长度 GlobalAlloc(); // 按字符串长度分配内存 WideCharToMultiByte(); // 将UNICODE文本转换成ANSI字符串 WideCharToMultiByte(); // 取得消息框标题文本的长度 GlobalAlloc(); // 按字符串长度分配内存 WideCharToMultiByte(); // 将UNICODE文本转换成ANSI字符串 MessageBoxExA(); // 最终还是调用ANSI版的MessageBoxExA();函数显示窗口 GlobalFree(); // 释放内存 GlobalFree(); // 释放内存}
eg: int MessageBoxA( MessageBoxA{ // 调用MessageBoxExA()函数 MBToWCSEx(); // 将消息框主体文字转换成Unicode字符串 MBToWCSEx(); // 将消息框标题文字转换成Unicode字符串 MessageBoxExW() // 调用MessageBoxExW()函数 HeapFree() // 释放内存 } );
;-------------------------------------------------------- ;time:2009-11-3 13:46 ;author:lichsword ;function: ; ;-------------------------------------------------------- .386 .model flat,stdcall option casemap:none ;---------------------------------------------- ;include file define ;---------------------------------------------- include windows.inc include gdi32.inc includelib gdi32.lib include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib ;---------------------------------------------- ;EQU define ;---------------------------------------------- ICO_MAIN EQU 100 ;... ... ;---------------------------------------------- ;.data segment ;---------------------------------------------- .data? hInstance dd ? hWinMain dd ? hIconMain dd ? .data ;... ... .const szClassName db 'MyClassName',0 szCaptionName db 'My Window Caption',0 szText db '这是一个窗口的框架。',0 ;--------------------------------------------- ;.code segment ;--------------------------------------------- .code ;--------------------------------------------- ;Main Window's process ;--------------------------------------------- _ProcWinMain proc uses ebx edi esi hWnd,uMsg,wParam,lParam LOCAL @stPs:PAINTSTRUCT LOCAL @stRect:RECT LOCAL @hDc mov eax,uMsg ;------------------------------------------- .if eax==WM_PAINT invoke BeginPaint,hWnd,addr @stPs mov @hDc,eax invoke GetClientRect,hWnd,addr @stRect invoke DrawText,@hDc,addr szText,-1,addr @stRect,DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd,addr @stPs ;-------------------------------- ;add your code here... ;-------------------------------- .elseif eax==WM_COMMAND ;-------------------------------- .elseif eax==WM_CREATE invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,hIconMain .elseif eax==WM_CLOSE invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret _ProcWinMain endp ;--------------------------------------------- ;Main Window ;--------------------------------------------- _WinMain proc LOCAL @stWndClass:WNDCLASSEX LOCAL @stMsg:MSG invoke GetModuleHandle,NULL mov hInstance,eax invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass ;--------------------------------- ;注册窗口类 ;--------------------------------- invoke LoadCursor,0,IDC_ARROW mov @stWndClass.hCursor,eax invoke LoadIcon,hInstance,ICO_MAIN mov hIconMain,eax push hInstance pop @stWndClass.hInstance mov @stWndClass.cbSize,sizeof WNDCLASSEX mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW mov @stWndClass.lpfnWndProc,offset _ProcWinMain mov @stWndClass.hbrBackground,COLOR_WINDOW+1 mov @stWndClass.lpszClassName,offset szClassName invoke RegisterClassEx,addr @stWndClass ;--------------------------------- ;Create and show window ;--------------------------------- invoke CreateWindowEx,WS_EX_CLIENTEDGE,\ offset szClassName,offset szCaptionName,\ WS_OVERLAPPEDWINDOW,\ 100,100,600,400,\ NULL,NULL,hInstance,NULL mov hWinMain,eax;save handle of window invoke ShowWindow,hWinMain,SW_SHOWNORMAL;show window invoke UpdateWindow,hWinMain ;update window ;--------------------------------- ;message circle 消息循环 ;--------------------------------- .while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax==0;when @stMsg = WM_QUIT , eax=0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endw ret _WinMain endp ;------------------------------------------------- start: call _WinMain invoke ExitProcess,NULL end start
/*-------------------- include files define ----------------------------*/ #include<windows.h> #include<windowsx.h> #include<winuser.h> #include<stdio.h> /*-------------------- defines files ----------------------------------*/ #define WIN32_LEAN_AND_MEAN // 不使用MFC #define WINDOW_CLASS_NAME "WINCLASS" /*------------------- struct define ----------------------------*/ /* ------------------- funciton indentifier -----------------------------*/ /* ------------------- funcitons -----------------------------*/ LRESULT CALLBACK WindowProc(HWND hWnd, UINT stMsg, WPARAM wParam, LPARAM lParam) { /* 窗口进程回调函数 完成windows消息分支处理 */ PAINTSTRUCT stPs; HDC hDc; switch(stMsg) { case WM_CREATE: { /* 窗口初始化 */ return(0); }break; case WM_PAINT: { /* 客户区绘制 */ hDc=BeginPaint(hWnd,&stPs); EndPaint(hWnd,&stPs); }break; case WM_DESTROY: { /* 窗口摧毁 */ PostQuitMessage(0); return(0); }break; default: break; } // end switch // 处理其它系统默认消息 return (DefWindowProc(hWnd,stMsg,wParam,lParam)); } // end WindowPric int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdline,int nCmdShow) { WNDCLASSEX stWndClass; // 保存建立的窗口类 HWND hWnd; // 保存窗口句柄 MSG stMsg; // 保存消息结构 /* 清空结构 */ RtlZeroMemory(&stWndClass,sizeof stWndClass); /* 填充窗口类 */ stWndClass.cbSize =sizeof(WNDCLASSEX); // 类大小 stWndClass.style =CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; // 窗口属性 stWndClass.lpfnWndProc =WindowProc; // 窗口进程函数 stWndClass.hInstance =hInstance; // 窗口句柄 stWndClass.hIcon =LoadIcon(NULL,IDI_APPLICATION); // 窗口主图标 stWndClass.hCursor =LoadCursor(NULL,IDC_ARROW); // 光标 stWndClass.hbrBackground =(HBRUSH)GetStockObject(BLACK_BRUSH); // 客户区背景 stWndClass.lpszMenuName =NULL; // 菜单 stWndClass.lpszClassName =WINDOW_CLASS_NAME; // 窗口类名 /* 注册窗口类 */ if(! RegisterClassEx(&stWndClass)) return 0; /* 创建窗口 */ if(!(hWnd=CreateWindowEx( WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, "DataStruct Demo", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100,100, 600,400, NULL,NULL, hInstance, NULL))) { return 0; } ShowWindow(hWnd,SW_SHOWNORMAL); UpdateWindow(hWnd); /* 进入事件循环 */ while(TRUE) { GetMessage(&stMsg,NULL,0,0); if(stMsg.message==WM_QUIT) break; // 处理快捷键 TranslateMessage(&stMsg); // 消息转发给窗口回调函数 DispatchMessage(&stMsg); } // end while // 返回操作系统 return (stMsg.wParam); } // end WinMain
离开电脑,活动一下。
- 应用程序是不会直接访问物理地址的;
- 虚拟内存管理器通过虚拟地址的访问请求,控制所有的物理内存访问;
- 每个应用程序都有相互独立的 4GB 寻址空间,不同应用程序的地址空间是隔离的;
- DLL程序没有自己的“私有”空间,它们总是被映射到其它应用程序的地址空间中,作为其它应用程序的一部分运行。因为如果DLL不和其他程序同属一个空间,应用程序就无法调用它。
传送门:
【原创】OllyDBG 入门系列(一)-认识OllyDBG
【原创】OllyDBG 入门系列(二)-字串参考
【原创】OllyDBG 入门系列(三)-函数参考
【原创】OllyDBG 入门系列(四)-内存断点
【原创】OllyDBG 入门系列(五)-消息断点及 RUN 跟踪
【原创】关于《OllyDBG 入门系列(五)-消息断点及 RUN 跟踪》的补充
【原创】OllyDBG 入门系列(七)-汇编功能
请问 代码的对齐与 缩进,大家是如何编辑的,好像看到好多人是用 一个框框把代码都框起来,就很漂亮,很整齐,如何做到的?
找到一种方法,用luo cong 的代码着色器。http://www.luocong.com/myworks.htm 这里是下载地址
版主可以告诉,那种 整齐的加边框 如何实现
感谢 youstar 相告,方法是:
选择“进入高级模式”--->有一个新的工具像#号一样的---->把 你的代码选中,点这个#号工具,OK。--->和谐了!
第三章
00401000 push 6 00401002 push 5 00401004 call 00401010 ---这里就是函数的调用,先把下条指令的地址即00401009入栈,再跳转到00401010地址执行。 00401009 add esp,8 0040100C xor eax,eax ... 00401010 mov eax,dword ptr [esp+8] 00401014 mov ecx,dword ptr [esp+4] 00401018 add eax,ecx 0040101A retn ---这里就是函数返回,先是从堆栈中取出返回地址,然后跳转到该返回址,不过就是有一点不同的,即为retn,为段内返回。
push ebp ---这是保护ebp mov ebp,esp ---设新ebp指向栈顶 mov eax,dword ptr [ebp+0C] ---调用参数2 mov ebx,dword ptr [ebp+8] ---调用参数1 sub esp,8 ---开辟8Byte栈空间,来存放局量变量。 ... add esp,8 ---函数结束前,要先收回局部变量的栈空间 mov esp,ebp ---还原esp到栈顶 pop ebp ---还原ebp ret 8 ---回收参数占用的栈空间(即平衡堆栈),弹出函数调用的入栈地址,并返回。
enter ****等价于 push ebp mov ebp,esp sub esp,**** 另一个 leave **** 等价于 add esp,**** mov esp,ebp pop ebp
1: #include<stdio.h> 2: int a=2010; 3: void main() 4: { 0040D690 mov eax,[_a (00414a30)] 0040D695 push eax 0040D696 push offset string "%c" (00414a38) 0040D69B call printf (00401040) 0040D6A0 add esp,8 5: printf("%d",a); 6: } 0040D6A3 ret
1: #include<stdio.h> 2: void main() 3: { 0040D690 sub esp,8 0040D693 push esi 4: int i; 5: char a[5]={'p','e','d','i','y'}; 0040D694 mov byte ptr [esp+4],70h ---'p' 0040D699 mov byte ptr [esp+5],65h ---'e' 0040D69E mov byte ptr [esp+6],64h ---'d' 0040D6A3 mov byte ptr [esp+7],69h ---'i' 0040D6A8 mov byte ptr [esp+8],79h ---'y' 6: for(i=0;i<5;i++) 0040D6AD xor esi,esi 7: printf("%c",a[i]); 0040D6AF movsx eax,byte ptr [esp+esi+4] ---这里就是“基址+变址”寻址的方式,基址是esp+4,esi是变址。 0040D6B4 push eax 0040D6B5 push offset string "%c" (00414a38) 0040D6BA call printf (00401040) 0040D6BF add esp,8 0040D6C2 inc esi ---变址加1 0040D6C3 cmp esi,5 0040D6C6 jl main+1Fh (0040d6af) 0040D6C8 pop esi 8: } 0040D6C9 add esp,8 0040D6CC ret
4.5 控制语句
if--else 型编译非优化,优化后的才没这么清晰。
1: #include<stdio.h> 2: void main() 3: { 0040F960 push ebp 0040F961 mov ebp,esp 0040F963 push ecx 4: int a=2010; 0040F964 mov dword ptr [a],7DAh 5: if(a==2012) 0040F96B cmp dword ptr [a],7DCh ---比较 0040F972 jne main+23h (0040f983) ---不相等就跳 6: printf("Judgement Year!"); 0040F974 push offset ___decimal_point_length+16Ch (00417328) ---00417328是字符串"Judgement Year!"的首地址。 0040F979 call printf (00401030) 0040F97E add esp,4 7: else 0040F981 jmp main+30h (0040f990) 8: printf("Happy Year!"); 0040F983 push offset ___decimal_point_length+17Ch (00417338) ---00417338是字符串"Happy Year!"的首地址。 0040F988 call printf (00401030) 0040F98D add esp,4 9: } 0040F990 mov esp,ebp 0040F992 pop ebp 0040F993 ret
1: #include<stdio.h> 2: void main() 3: { 0040F94A push ebp 0040F94B mov ebp,esp 0040F94D push ecx 4: int a; 5: scanf("%d",&a); 0040F94E lea eax,[a] 0040F951 push eax 0040F952 push offset string "%d" (00417a50) 0040F957 call scanf (00401130) 6: switch(a){ 0040F95C mov eax,dword ptr [a] ---取变量a的值,送到eax 0040F95F pop ecx 0040F960 sub eax,0 ---与0相减 0040F963 pop ecx 0040F964 je 0040f984 ---若相等,则a=0,跳到case 0 0040F966 dec eax 0040F967 je 0040f97d ---若相等,则a=1,跳到case 1 0040F969 dec eax 0040F96A je 0040f976 ---若相等,则a=2,跳到case 2 0040F96C dec eax 0040F96D jne 0040f98f ---若相等,则a=3,执行case 3,若不相等,则玩蛋。 10: case 3:printf("pediy3");break; 0040F96F push 00417a48 ---"pediy3" 0040F974 jmp 0040f989 9: case 2:printf("pediy2");break; 0040F976 push 00417a40 ---"pediy2" 0040F97B jmp 0040f989 8: case 1:printf("pediy1");break; 0040F97D push 00417a38 ---"pediy1" 0040F982 jmp 0040f989 7: case 0:printf("pediy0");break; 0040F984 push 00417a30 ---"pediy0" 0040F989 call printf (004010b0) 0040F98E pop ecx 11: default:break; 12: } 13: } 0040F98F leave ---leave指令=mov esp,ebp / pop ebp 可以节省代码大小。 0040F990 ret
4.5.3 转移指令机器码计算
用时查书。
4.5.4 条件设置指令
用时查书。
4.5.4 纯算法实现逻辑判断
要求扎实的汇编基础。
4.6 循环语句ecx和 LOOP 组成循环
cmp/test/add sub 和 跳转指令组成循环
loop写时很常见,但反汇编中少见,我只见过几次。
loop循环的个缺点,就是loop要先把ecx减1,再判断是否为0,不为0就循环;为0就结束
特殊之处在于,如果此时ecx为0,那么ecx-1后成了FFFF FFFF,就出错了。
所以我见过的loop循环都作了处理,好像是先全加1.就避免了ecx为0的情况。
好像还有一种是,mov ecx FFFFFFFFh,这也是循环开始的标志吧。
4.7 数学运算符
加:
add是常的,优化的代码中,还有lea也很常见
add eax,edx
add eax,ecx
add eax,78
用lea一句搞定:lea eax,[edx+ecx+78],而且lea只用一个时钟。
减:
注意优化成补码的情况
sub eax,3即add eax,FFFFFFFD
乘:
注意算法中用移位和加法来优化乘法
mov eax,dword ptr [esp]
mov ecx,0B
imul ecx
优化成了
1: #include<stdio.h> 2: void main() 3: { 0040F980 push ecx 4: int a; 5: scanf("%d",&a); 0040F981 lea eax,[esp] 0040F985 push eax 0040F986 push offset string "pediy0" (00417a30) 0040F98B call scanf (00401130) 6: a=a*11; 0040F990 mov eax,dword ptr [esp+8] 0040F994 lea ecx,[eax+eax*4] ---ecx=5*eax 0040F997 lea eax,[eax+ecx*2] ---eax=eax+2*ecx=eax+10*eax=11*eax 7: printf("%d",a); 0040F99A push eax 0040F99B push offset string "pediy0" (00417a30) 0040F9A0 mov dword ptr [esp+10h],eax 0040F9A4 call printf (004010b0) 8: } 0040F9A9 add esp,14h 0040F9AC ret
1: #include<stdio.h> 2: void main() 3: { 0040F980 push ecx 4: int a; 5: scanf("%d",&a); 0040F981 lea eax,[esp] 0040F985 push eax 0040F986 push offset string "pediy0" (00417a30) 0040F98B call scanf (00401130) 6: a=a/11; 0040F990 mov ecx,dword ptr [esp+8] 0040F994 mov eax,2E8BA2E9h ---编译器对代码优化后,产生的常数。 0040F999 imul ecx ---以后看到这种情况,表怕,这是一个除法! 0040F99B sar edx,1 0040F99D mov ecx,edx 0040F99F shr ecx,1Fh ---以下2条也是优化代码,用逻辑算法优化。 0040F9A2 add edx,ecx 7: printf("%d",a); 0040F9A4 push edx 0040F9A5 push offset string "pediy0" (00417a30) 0040F9AA mov dword ptr [esp+10h],edx 0040F9AE call printf (004010b0) 8: } 0040F9B3 add esp,14h 0040F9B6 ret
4.8 文本字符串
4.8.1字符串存储格式
不同编程语言,字符存储格式是不同的。
C 和 DOS 归为一类,它们都是以特殊字符来标识字符串结尾,即终止字符。
C是以'\0'
DOS是以'$' eg:This program cannot be run in DOS mode.....$ 这是我们常常看到的。
Pascal 和Depphi归为一类,它们都把字符串长度放在头部,后面是字符串内容
不同的是Pascal 只用1个字节即8位来表示长度,那么字符串最长为255
而Delphi 增强了这一属性,用2个字节,即16位来表示长度,那么最长为65536
Delphi还支持一种更长的,用4个字节来表示长度,最长字符串可以2的32次方,即4GB。
所以
"PEDIY"C语言
'P','E','D','I','Y','\0'Delphi语言
5,0,'P','E','D','I','Y'如果对Delphi程序改字符串时,要记得要同时修改头部的字符串长度数值。
在计算索引与常量的和时,编译器一般将指针放在第一个位置,而不管它们在程序中的顺序。 eg: mov dword ptr [eax+8],67453201 mov dword ptr [eax+C],EFCDAB89 ---书中的这段话,让我很是费解呀!!!
1: #include<stdio.h> 2: void main() 3: { 00401010 sub esp,0Ch 4: int l; 5: char a[]="lichsword"; 00401013 mov eax,[string "lichsword" (00414a34)] ---eax中存放"lich" 00401018 mov ecx,dword ptr [string "lichsword"+4 (00414a38)] ---ecx中存放"swor" 0040101E mov dx,word ptr [string "lichsword"+8 (00414a3c)] ---dx中存放"d" 00401025 push edi ---本以为会优化成书中的例子,结果编译器看我的字符串不是很长,就切了。倒! 00401026 mov dword ptr [esp+4],eax 0040102A mov dword ptr [esp+8],ecx ---字符串送入栈中 6: l=strlen(a); 0040102E lea edi,[esp+4] 00401032 or ecx,0FFh ---这个标志出现,表示很可能要获得字符串长度了。 00401035 xor eax,eax 00401037 mov word ptr [esp+0Ch],dx 0040103C repne scas byte ptr [edi] ---这里的优化与书中不同 0040103E not ecx 00401040 dec ecx ---额,用逻辑算法优化了。 7: printf("%d",l); 00401041 push ecx 00401042 push offset string "%d" (00414a30) 00401047 call printf (00401080) 0040104C add esp,8 0040104F pop edi 8: } 00401050 add esp,0Ch 00401053 ret
第四章,就学到这里。
00401228 . 68 8E214000 push serial.0040218E ; ASCII "lichsword" 0040122D . E8 4C010000 call serial.0040137E ; 这是处理用户名函数 00401232 . 50 push eax 00401233 . 68 7E214000 push serial.0040217E ; ASCII "123456" 00401238 . E8 9B010000 call serial.004013D8 ; 这是处理注册码函数 0040123D . 83C4 04 add esp, 4 00401240 . 58 pop eax 00401241 . 3BC3 cmp eax, ebx ; 比较 00401243 . 74 07 je short serial.0040124C ; 相等就跳到成功 00401245 . E8 18010000 call serial.00401362 ; 跳了就完蛋 0040124A .^ EB 9A jmp short serial.004011E6 0040124C > E8 FC000000 call serial.0040134D ; 跳了就成功 00401251 .^ EB 93 jmp short serial.004011E6
0040137E /$ 8B7424 04 mov esi, dword ptr [esp+4] 00401382 |. 56 push esi ; ESI="lichsword" 00401383 |> 8A06 /mov al, byte ptr [esi] ; 取一个字符到a[i] 00401385 |. 84C0 |test al, al ; 判断a[i]是否为0,即字符串是否处理完 00401387 |. 74 13 |je short serial.0040139C ; 相等,即处理完毕,就跳到函数结束 00401389 |. 3C 41 |cmp al, 41 0040138B |. 72 1F |jb short serial.004013AC ; 如果a[i]<'A'成立就跳,一跳就完蛋,说明必须是字母,不能为数字或其它字符 0040138D |. 3C 5A |cmp al, 5A 0040138F |. 73 03 |jnb short serial.00401394 ; 如果a[i]>'Z'成立就跳到执行00401383函数 00401391 |. 46 |inc esi 00401392 |.^ EB EF |jmp short serial.00401383 00401394 |> E8 39000000 |call serial.004013D2 ; 小写字母转成大写字母 00401399 |. 46 |inc esi ; 指向下一个字符 0040139A |.^ EB E7 \jmp short serial.00401383 ; 下一循环 0040139C |> 5E pop esi ; EDI="LICHSWORD" 0040139D |. E8 20000000 call serial.004013C2 ; 功能:字符串各字符累加和送EDI 004013A2 |. 81F7 78560000 xor edi, 5678 ; 用户名各字符累加和再与常数5678异或 004013A8 |. 8BC7 mov eax, edi ; 结果送EAX 004013AA |. EB 15 jmp short serial.004013C1 ; 跳转到函数返回 004013AC |> 5E pop esi 004013AD |. 6A 30 push 30 ; /Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL 004013AF |. 68 60214000 push serial.00402160 ; |Title = "Error! " 004013B4 |. 68 69214000 push serial.00402169 ; |Text = "Incorrect!,Try Again" 004013B9 |. FF75 08 push dword ptr [ebp+8] ; |hOwner 004013BC |. E8 79000000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA 004013C1 \> C3 ret
004013D2 /$ 2C 20 sub al, 20 ; a[i]-20h,即小写字母-20h=大写字母 004013D4 |. 8806 mov byte ptr [esi], al ; 覆盖原字符 004013D6 \. C3 ret
int F1(用户名){ char name[]="lichsword"; int i,n=0; for(i=0;a[i]!='\0';i++) { if(a[i]>'Z') a[i]-=0x20; n+=a[i] } n=n^0x5678; return n; }
004013D8 /$ 33C0 xor eax, eax 004013DA |. 33FF xor edi, edi 004013DC |. 33DB xor ebx, ebx 004013DE |. 8B7424 04 mov esi, dword ptr [esp+4] ; ESI="123456" 004013E2 |> B0 0A /mov al, 0A ; j=10 004013E4 |. 8A1E |mov bl, byte ptr [esi] ; 取一个字符送到a[i] 004013E6 |. 84DB |test bl, bl 004013E8 |. 74 0B |je short serial.004013F5 ; 如果a[i]=='\0',即到了字串尾,就结束循环 004013EA |. 80EB 30 |sub bl, 30 ; a[i]-30h,有点像字符转换成数字 004013ED |. 0FAFF8 |imul edi, eax ; n=n*10 004013F0 |. 03FB |add edi, ebx ; n=n+a[i] 004013F2 |. 46 |inc esi ; 指向下一个字符 004013F3 |.^ EB ED \jmp short serial.004013E2 ; 下一循环 004013F5 |> 81F7 34120000 xor edi, 1234 ; n与1234h异或 004013FB |. 8BDF mov ebx, edi ; 把结果n送EBX,返回 004013FD \. C3 ret
int F2(序列号){ char sn[]="123456"; int i,n=0; for(i=0;a[i]!='\0';i++) { a[i]-=0x30; n=n*10+a[i]; } n=n^0x1234; return n; }
小结一下,逆向的关键之处为:
第一要点:找到关键代码。如果连关键代码都找不到,那从哪下断点,从哪开始分析算法呢,逆向更是空谈。
第二要点:读懂汇编代码,理清程序算法流程。这个时候已经可以在心中有个高级语言的算法雏形了。
第三要点:做些合适的处理。如,爆破、SMC、DIY、等等。
5.2 警告(Nag)窗口
首先,我思索着,去掉这个窗口的方法:
1、把这段代码从程序中“去掉”,即,用NOP指令覆盖全部与警告窗口相关的代码。
2、不调用显示警告窗口的子程序。相关于JMP OVER跳过。
第一种方法,不是很好,不知道为啥,好像高手们都没这么做,也许代码之间相关性很强,牵一发而动全身,因此目
前我先用第二种方法。
第二种方法,要点在于,跳转,要跳得合适,既完成了去警告窗口的目的又不改变其它流程。
好,看完书后,我也自己手动分析一番。
首先当然是运行一下程序。运行之前,我先用PEID查一下壳:
晕,如图所示,未知壳类型。再用pe-scan一查,也是无法识别类型。
汗,好吧,先运行看看。
首先就出现了警告窗口,然后我们点OK,主窗口显示。
说明警告窗口是在主窗口之前显示的,那么我们的任务就是在
警告窗口显示的代码中下断。
好,问题变成了,如何定位警告窗口的显示代码。
显示窗口的API有
MessageBoxA(W)---显示消息框
DialogBoxParamA(W)---显示对话框
ShowWindow---显示窗口
CreateWindowExA(W)---创建窗口
我们刚看到的警告窗口中,明显有Static静态文本、按钮等控件,这说明它不是消息框,而是资源定义的
对话框。
好了,我先试下DialogBoxParamA(W)下断点。
Ctrl+N,看到了DialogBoxParamA(W)导入函数,选择“在每个参考上设置断点”中断。
这种情况会在DialogBoxParamA
(W)调用前断下,不会进入内部。
或者用Ctrl+G输入跟随的表达式DialogBoxParamA(W),不过这种方法不好,因为只有DialogBoxParamA(W)函数已经被调
用后,我们才能引发中断,而这时是在系统进程中。有点走偏了的意味。
因为我们的目的是要到警告框显示之前。
0040104D /$ 8B4424 04 mov eax, dword ptr [esp+4] 00401051 6A 00 push 0 00401053 68 C4104000 push Nag.004010C4 ; NEG对话框处理函数指针 00401058 |. 6A 00 push 0 ; |hOwner = NULL 0040105A 6A 79 push 79 ; 79资源ID,即NEG 0040105C |. 50 push eax ; |hInst 0040105D |. A3 9C114000 mov dword ptr [40119C], eax ; | 00401062 |. FF15 10104000 call dword ptr [<&USER32.DialogBoxPar>; \DialogBoxParamA 断在此处 00401068 |. 33C0 xor eax, eax 0040106A \. C2 1000 ret 10
004010C4 8B4424 08 mov eax, dword ptr [esp+8] 004010C8 2D 10010000 sub eax, 110 ; Switch (cases 110..111) 004010CD 74 34 je short Nag_down.00401103 004010CF 48 dec eax 004010D0 75 2D jnz short Nag_down.004010FF 004010D2 8B4424 0C mov eax, dword ptr [esp+C] ; Case 111 of switch 004010C8 004010D6 48 dec eax 004010D7 75 26 jnz short Nag_down.004010FF 004010D9 6A 00 push 0 004010DB FF7424 08 push dword ptr [esp+8] 004010DF FF15 18104000 call dword ptr [<&USER32.EndDialog>] ; USER32.EndDialog 004010E5 6A 00 push 0 004010E7 68 09114000 push Nag_down.00401109 004010EC 6A 00 push 0 004010EE 6A 65 push 65 004010F0 6A 00 push 0 004010F2 FF15 00104000 call dword ptr [<&KERNEL32.GetModuleHa>; kernel32.GetModuleHandleA 004010F8 50 push eax 004010F9 FF15 10104000 call dword ptr [<&USER32.DialogBoxPara>; USER32.DialogBoxParamA 004010FF 33C0 xor eax, eax ; Default case of switch 004010C8 00401101 EB 03 jmp short Nag.00401106 00401103 6A 01 push 1 ; Case 110 of switch 004010C8 00401105 58 pop eax 00401106 C2 1000 ret 10
00401051 6A 00 push 0 00401053 68 C4109090 push 909010C4 ; NEG对话框处理函数 00401058 |. 6A 00 push 0 ; |hOwner = NULL 0040105A |. 6A 79 push 79 ; |79资源ID,即NEG 0040105C |. 50 push eax ; |hInst 0040105D |. A3 9C114000 mov dword ptr [40119C], eax ; | 00401062 |. FF15 10104000 call dword ptr [<&USER32.DialogBoxPar>; \DialogBoxParamA
004010E5 . 6A 00 push 0 ; /lParam = NULL 004010E7 . 68 09114000 push Nag.00401109 ; |DlgProc = Nag.00401109 004010EC . 6A 00 push 0 ; |hOwner = NULL 主对话框处理函数 004010EE . 6A 65 push 65 ; |pTemplate = 65 主对话框资源ID 004010F0 . 6A 00 push 0 ; |/pModule = NULL 004010F2 . FF15 00104000 call dword ptr [<&KERNEL32.GetModuleH>; |\GetModuleHandleA 004010F8 . 50 push eax ; |hInst 004010F9 . FF15 10104000 call dword ptr [<&USER32.DialogBoxPar>; \DialogBoxParamA
0040104D /$ 8B4424 04 mov eax, dword ptr [esp+4] 00401051 |. 6A 00 push 0 ; /lParam = NULL 00401053 |. 68 09114000 push Nag_ok03.00401109 ; |DlgProc = Nag_ok03.00401109 00401058 |. 6A 00 push 0 ; |hOwner = NULL 0040105A |. 6A 65 push 65 ; |pTemplate = 65 0040105C |. 50 push eax ; |hInst 0040105D |. A3 9C114000 mov dword ptr [40119C], eax ; | 00401062 |. FF15 10104000 call dword ptr [<&USER32.DialogBoxPar>; \DialogBoxParamA 00401068 |. 33C0 xor eax, eax 0040106A \. C2 1000 ret 10
5.3 时间限制
《加》书中谈了许多类的时间限制技术,但只给了一个 用SetTimer 来定时的CM。
这里先按书中来,以后有相关的其它时间限制的保护,再来补充
---------------------------------------------------------------------------------------
首先,运行Timer.exe
看到每过1秒,右下方的时间计数框中时间加1(现在是6秒),到20时就关闭了程序。
查下壳,不知道是什么语言写的,类型未知。
从哪里下手呢?
这时得从大脑的信息库中得到与时间相关的资料:
SetTimer---设定一个定时器。
WM_TIMER---定时消息,定义为常量0x113
KillTimer---释放定时器(因为系统的定时器资源是有限的)
GetTickCount---获得从系统启动以来的运行时间。
GetSystemTime
GetLocalTime---上面这2个也是经常用了
GetFileTime
FileTimetoSystemTime
--------------------------------
现在真刀实战开始。
OD载入
如何看到用了什么API呢?
老方法---Ctrl+N,查看导入函数如下图:
与时间直接相关的有:
KillTimer
SetTimer
OK,从SetTimer下手“在每个参考上设置断点”
F9,运行,断下
004010C2 . 8B7424 08 mov esi, dword ptr [esp+8] ; Case 110 (WM_INITDIALOG) of switch 004010A5 004010C6 . 6A 00 push 0 ; /Timerproc = NULL 004010C8 . 68 E8030000 push 3E8 ; |Timeout = 1000. ms 004010CD . 6A 01 push 1 ; |TimerID = 1 004010CF . 56 push esi ; |hWnd 004010D0 . FF15 30204000 call dword ptr [<&USER32.SetTimer>] ; \SetTimer 断在这里
004010C2 . 8B7424 08 mov esi, dword ptr [esp+8] ; Case 110(WM_INITDIALOG) of switch 004010A5 004010C6 . 6A 00 push 0 ; /Timerproc = NULL 004010C8 . 68 E8030000 push 3E8 ; |Timeout = 1000. ms 004010CD . 6A 01 push 1 ; |TimerID = 1 004010CF . 56 push esi ; |hWnd 004010D0 . FF15 30204000 call dword ptr [<&USER32.SetTimer>] ; \SetTimer 断在这里 004010D6 . A1 04304000 mov eax, dword ptr [403004] 004010DB . 6A 70 push 70 ; /RsrcName = 112. 004010DD . 50 push eax ; |hInst => 00400000 004010DE . FF15 2C204000 call dword ptr [<&USER32.LoadIconA>] ; \LoadIconA 004010E4 . 50 push eax ; /lParam 004010E5 . 6A 01 push 1 ; |wParam = 1 004010E7 . 68 80000000 push 80 ; |Message = WM_SETICON 004010EC . 56 push esi ; |hWnd 004010ED . FF15 14204000 call dword ptr [<&USER32.SendMessageA>; \SendMessageA 004010F3 . B8 01000000 mov eax, 1 004010F8 . 5E pop esi 004010F9 . C2 1000 ret 10
004010C6 /EB 0E jmp short Timer.004010D6 004010C8 . |68 E8030000 push 3E8 ; |Timeout = 1000. ms 004010CD . |6A 01 push 1 ; |TimerID = 1 004010CF . |56 push esi ; |hWnd 004010D0 . |FF15 30204000 call dword ptr [<&USER32.SetTimer>] ; \SetTimer 断在这里 004010D6 . \A1 04304000 mov eax, dword ptr [403004] ; 跳过SetTimer到这里,继续运行 004010DB . 6A 70 push 70 ; /RsrcName = 112. 004010DD . 50 push eax ; |hInst => 00400000 004010DE . FF15 2C204000 call dword ptr [<&USER32.LoadIconA>] ; \LoadIconA 004010E4 . 50 push eax ; /lParam 004010E5 . 6A 01 push 1 ; |wParam = 1 004010E7 . 68 80000000 push 80 ; |Message = WM_SETICON 004010EC . 56 push esi ; |hWnd 004010ED . FF15 14204000 call dword ptr [<&USER32.SendMessageA>] ; \SendMessageA 004010F3 . B8 01000000 mov eax, 1 004010F8 . 5E pop esi 004010F9 . C2 1000 ret 10
mov eax,uMsg ;------------------------------------------- .if eax==WM_PAINT invoke BeginPaint,hWnd,addr @stPs mov @hDc,eax invoke GetClientRect,hWnd,addr @stRect invoke DrawText,@hDc,addr szText,-1,addr @stRect,DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd,addr @stPs ;-------------------------------- ;add your code here... ;-------------------------------- .elseif eax==WM_COMMAND ;-------------------------------- .elseif eax==WM_CREATE invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,hIconMain .elseif eax==WM_CLOSE invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif
004010A0 . 8B4424 08 mov eax, dword ptr [esp+8] 004010A4 . 56 push esi 004010A5 . 3D 11010000 cmp eax, 111 ; WM_COMMAND; Switch (cases 10..113) 004010AA . 0F87 C5000000 ja Timer.00401175 ; WM_TIMER=113>111,跳之 004010B0 . 74 67 je short Timer.00401119 004010B2 . 83F8 10 cmp eax, 10 ; WM_CLOSE 004010B5 . 74 45 je short Timer.004010FC 004010B7 . 3D 10010000 cmp eax, 110 ; WM_INITDIALOG 004010BC . 0F85 86000000 jnz Timer.00401148 004010C2 . 8B7424 08 mov esi, dword ptr [esp+8] ; Case 110 (WM_INITDIALOG) of switch 004010A5
00401175 > \3D 13010000 cmp eax, 113 ; 是否为WM_TIMER消息 0040117A .^ 75 CC jnz short Timer.00401148 0040117C . A1 08304000 mov eax, dword ptr [403008] ; 保存计数次数; Case 113 (WM_TIMER) of switch 004010A5 00401181 . 83F8 13 cmp eax, 13 ; 计数是否达19 00401184 .^ 7F B1 jg short Timer.00401137 ; 大于19就跳到摧毁窗口,然后就完蛋 00401186 . 40 inc eax ; 计数+1 00401187 . 8D4C24 0C lea ecx, dword ptr [esp+C] ; /---以下是送文本编辑框显示---\ 0040118B . 50 push eax ; /<%ld> 0040118C . 68 00304000 push Timer.00403000 ; |Format = "%ld" 00401191 . 51 push ecx ; |s 00401192 . A3 08304000 mov dword ptr [403008], eax ; |把当前计数保存于全局变量00403008处 00401197 . FF15 20204000 call dword ptr [<&USER32.wsprintfA>] ; \wsprintfA 0040119D . 8B4424 14 mov eax, dword ptr [esp+14] 004011A1 . 83C4 0C add esp, 0C 004011A4 . 8D5424 0C lea edx, dword ptr [esp+C] 004011A8 . 52 push edx ; /lParam 004011A9 . 6A 00 push 0 ; |wParam = 0 004011AB . 6A 0C push 0C ; |Message = WM_SETTEXT 004011AD . 68 FC030000 push 3FC ; |/ControlID = 3FC (1020.) 004011B2 . 50 push eax ; ||hWnd 004011B3 . FF15 1C204000 call dword ptr [<&USER32.GetDlgItem>] ; |\GetDlgItem 004011B9 . 50 push eax ; |hWnd 004011BA . FF15 14204000 call dword ptr [<&USER32.SendMessageA>] ; \SendMessageA 004011C0 . 33C0 xor eax, eax 004011C2 . 5E pop esi 004011C3 . C2 1000 ret 10 ; \----------------------/
0040117C . A1 08304000 mov eax, dword ptr [403008] ; 保存计数次数; Case 113 (WM_TIMER) of switch 004010A5 00401181 83F8 7F cmp eax, 7F ; 计数是否达7F 00401184 ^ 7F B1 jg short Timer.00401137 ; 大于7F就跳到摧毁窗口,然后就完蛋
5.4 菜单功能限制
如《加》书中所示,一般是DEMO版,菜单灰色,无法使用。
第一种是正式版与试用版完全分开的版本,试用版一些菜单灰化且没有功能实现的代码。
第二种情况是,正式版与试用版都是同一个程序,只是没有注册激活功能而已。从作者的角度来说,最好是用第一种方案来保护,不然你就太低估破解者的实力了。
第二种情况就是下面聊的,也是我们关心的。
以书中的EnableMenu.exe学习吧。
首先查壳
很好,没壳(当然没壳呀,这只是教程参数程序。)
Microsoft Visual C++ 6.0
好,运行看看
File菜单下,有一个Menu的子菜单灰化了,我们的目的就是要激活它。
下面调用相关知识:
EnableMenuItem
BOOL EnableMenuItem(HWND hMenu,UINT uIDEnableItem,UINT uEnable)
参数含义如下:
-----------------------------------------------------
hMenu:菜单句柄
uIDEnable:欲允许或禁止的一个菜单条目的标识符
uEnable:控制标志。有:
MF_ENABLED(允许,0h)
MF_GRAYED(灰化,1h)MF_DISABLED(禁止,2h)
MF_BYCOMMAND(指定菜单项的命令ID号,此为缺省值)
MF_BYPOSITION(指定菜单项的位置)
返回值是菜单以前的状态,如果菜单项不存在,则返回FFFFFFFFh
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
还可以用EnableWindow
允许或禁止指定的窗口
BOOL EnableWindow(HWND hWnd,BOOL bEnable)
参数含义如下:
-----------------------------------------------------
hWnd:窗口句柄
bEnable:TRUE允许、FALSE禁止
返回值0表示失败,非0表示成功
-----------------------------------------------------
好,开始动手,OD载入
Ctrl+N查看导入函数,如下:
名称位于 EnableMe
地址 区段 类型 ( 名称 注释 004040A0 .rdata 输入 ( USER32.DestroyWindow 004040B4 .rdata 输入 ( USER32.DialogBoxParamA 0040409C .rdata 输入 ( USER32.EnableMenuItem 004040B0 .rdata 输入 ( USER32.EndDialog 00404034 .rdata 输入 ( KERNEL32.ExitProcess 00404048 .rdata 输入 ( KERNEL32.FreeEnvironmentStringsA 0040404C .rdata 输入 ( KERNEL32.FreeEnvironmentStringsW 00404080 .rdata 输入 ( KERNEL32.GetACP 0040402C .rdata 输入 ( KERNEL32.GetCommandLineA 0040407C .rdata 输入 ( KERNEL32.GetCPInfo 0040403C .rdata 输入 ( KERNEL32.GetCurrentProcess 004040A4 .rdata 输入 ( USER32.GetDlgItem 00404054 .rdata 输入 ( KERNEL32.GetEnvironmentStrings 00404004 .rdata 输入 ( KERNEL32.GetEnvironmentStringsW 00404060 .rdata 输入 ( KERNEL32.GetFileType 00404098 .rdata 输入 ( USER32.GetMenu 00404044 .rdata 输入 ( KERNEL32.GetModuleFileNameA 00404024 .rdata 输入 ( KERNEL32.GetModuleHandleA 00404084 .rdata 输入 ( KERNEL32.GetOEMCP 00404020 .rdata 输入 ( KERNEL32.GetProcAddress 00404028 .rdata 输入 ( KERNEL32.GetStartupInfoA 0040405C .rdata 输入 ( KERNEL32.GetStdHandle 0040400C .rdata 输入 ( KERNEL32.GetStringTypeA 00404008 .rdata 输入 ( KERNEL32.GetStringTypeW 00404030 .rdata 输入 KERNEL32.GetVersion 00404088 .rdata 输入 ( KERNEL32.HeapAlloc 00404068 .rdata 输入 ( KERNEL32.HeapCreate 00404064 .rdata 输入 ( KERNEL32.HeapDestroy 00404070 .rdata 输入 ( KERNEL32.HeapFree 00404058 .rdata 输入 ( KERNEL32.HeapReAlloc 00404014 .rdata 输入 ( KERNEL32.LCMapStringA 00404010 .rdata 输入 ( KERNEL32.LCMapStringW 00404094 .rdata 输入 ( USER32.LoadIconA 0040401C .rdata 输入 ( KERNEL32.LoadLibraryA 00404018 .rdata 输入 ( KERNEL32.MultiByteToWideChar 004040A8 .rdata 输入 ( USER32.PostMessageA 00404074 .rdata 输入 ( KERNEL32.RtlUnwind 004040AC .rdata 输入 ( USER32.SendMessageA 00404000 .rdata 输入 ( KERNEL32.SetHandleCount 00404038 .rdata 输入 ( KERNEL32.TerminateProcess 00404040 .rdata 输入 ( KERNEL32.UnhandledExceptionFilter 0040408C .rdata 输入 ( KERNEL32.VirtualAlloc 0040406C .rdata 输入 ( KERNEL32.VirtualFree 00404050 .rdata 输入 ( KERNEL32.WideCharToMultiByte 00404078 .rdata 输入 ( KERNEL32.WriteFile 00401210 .text 输出 <模块入口点>
004011E3 6A 01 push 1 004011E5 . 68 459C0000 push 9C45 ; |ItemID = 9C45 (40005.) 004011EA . 50 push eax ; |hMenu 004011EB . FF15 9C404000 call dword ptr [<&USER32.EnableMenuIte>; \EnableMenuItem 断在此处
004011B9 > 8B15 90544000 mov edx, dword ptr [405490] ; EnableMe.00400000; Case 110 (WM_INITDIALOG) of switch 00401124 004011BF . 56 push esi 004011C0 . 6A 70 push 70 ; /RsrcName = 112. 004011C2 . 52 push edx ; |hInst => 00400000 004011C3 . FF15 94404000 call dword ptr [<&USER32.LoadIconA>] ; \LoadIconA 004011C9 . 50 push eax ; /lParam 004011CA . 6A 01 push 1 ; |wParam = 1 004011CC . 8B7424 10 mov esi, dword ptr [esp+10] ; | 004011D0 . 68 80000000 push 80 ; |Message = WM_SETICON 004011D5 . 56 push esi ; |hWnd 004011D6 . FF15 AC404000 call dword ptr [<&USER32.SendMessageA>>; \SendMessageA 004011DC . 56 push esi ; /hWnd 004011DD . FF15 98404000 call dword ptr [<&USER32.GetMenu>] ; \GetMenu 004011E3 6A 01 push 1 004011E5 . 68 459C0000 push 9C45 ; |ItemID = 9C45 (40005.) 004011EA . 50 push eax ; |hMenu 004011EB . FF15 9C404000 call dword ptr [<&USER32.EnableMenuIte>; \EnableMenuItem 断在此处004011F1 . 5E pop esi 004011F2 . B8 01000000 mov eax, 1 004011F7 . C2 1000 ret 10
00401120 . 8B4424 08 mov eax, dword ptr [esp+8] 00401124 . 83E8 10 sub eax, 10 ; Switch (cases 10..111) 00401127 . 0F84 CD000000 je EnableMe.004011FA 0040112D . 2D 00010000 sub eax, 100 00401132 . 0F84 81000000 je EnableMe.004011B9
riusksk:感谢你的关注。
时间只要去挤,总会有的,只是每天加班回来都快10只有1、2个小时,我一定要坚持!!!
---总是觉得从《加》书中的例子过一遍是很快的,但觉得学完例子后,自己的体会和总结才是自己的莫大收获,所以笔记的正文会很“嗦”。
5.5 KeyFile保护
额,这个第一章进展好慢哪!!!要提速。
首先,查壳。
不得不说,PEID比pe-scan要强大,PEID查出来是MASM32 / TASM32汇编写的,无壳。
运行试下,如下图
没有任何反应,特别之处:是这个CM没有任何供输入的地方。
虽然书中已经说了这个是文件保护的,但我在想,如果我面对一个新的CM,我如何得知它是什么保护机制呢?
呵呵,这个问题先保留,继续搞下去,也许就分析明朗了。
----------------------------------------------------------------------
好了,下面我们还推测是否是文件保护。
打开Filemon_fix.7.03.exe(从看雪工具集中下载)
过滤器设置如下图:
进入Filemon的界面后,说下我的使用心得:
注意Filemon会记录与PackMe相关的文件操作,我本以为不会很多,可是360等文件监控程序会不断地查看文件,所以会产生相关多的信息,有时会多到几百条,虽然Filemon的process列表会有程序的图标显示,我们可以看到是哪个程序在跑,但是我觉得还是进行一点技巧,让分析的结果少些(当然,重要的分析不能少)
本例的CM中是按下Check按钮后,才会进行文件保护机制的检查,所以我们可以先运行PackMe.exe。
这时会产生一些分析结果,我们Ctrl+E停止分析,再Ctrl+X清空(因为这些都不是我们要的)
然后再Ctrl+E开始分析,然后迅速点Check按钮,半秒后我们就Ctrl+E停止,呵呵,此时该出来的都出来了,总分析数据也不是很大,如下图:
我们一下就可以定位是哪个文件了。
0.00023467 PackMe.exe:2192 OPEN F:\KwazyWeb.bit NOT FOUND Options: Open Access: Read
上面这行是重要的信息,分析如下,以OPEN方式操作F:\KwazyWeb.bit文件,操作结果是NOT FOUND,权限是Read
感觉就是这个KwazyWeb.bit文件了。
------------------------ 以上是用Filemon.exe来查文件,其实,我现在遇到的好多软件已经相当有意识了,其本查不到个什么了。
下面OD载入。
Ctrl+N,对CreateFileA下参考断点,断下代码
004016D8 . 52 push edx ; |FileName => "KwazyWeb.bit"
004016D9 . E8 1C010000 call <jmp.&KERNEL32.CreateFileA> ; \CreateFileA
004016DE . 83F8 FF cmp eax, -1 ; 断下返回到此
004016E1 . 74 64 je short PackMe.00401747
从004016D8 . 52 push edx ; |FileName => "KwazyWeb.bit"可知,就是这个KwazyWeb,bit文件了。
用C32Asm创建一个KwazyWeb.bit文件,然后我们在16进制文件下写数据123456789,这样的有明显顺序的数据可以方便我们进行分析和识别(当然,正确的注册码不可能是这种数据,一定有点复杂)。
好,重新OD载入。
F9运行,之后点Check按钮,因为Check之后,程序才会执行注册码的判断。
断在了Kernel.dll的领空,这里没我们的事,出去(alt+F9).F8前进,
004016D8 . 52 push edx ; |FileName => "KwazyWeb.bit" 004016D9 . E8 1C010000 call <jmp.&KERNEL32.CreateFileA> ; \CreateFileA 004016DE . 83F8 FF cmp eax, -1 ; 断下返回到此,EAX是文件句柄 004016E1 . 74 64 je short PackMe.00401747 ; 没有注册文件就跳,跳了就完蛋 004016E3 . A3 44344000 mov dword ptr [403444], eax ; 保存文件句柄到全局变量 004016E8 . 6A 00 push 0 ; /pOverlapped = NULL 004016EA . 68 48344000 push PackMe.00403448 ; |pBytesRead = PackMe.00403448 004016EF . 6A 01 push 1 ; |BytesToRead = 1 004016F1 . 68 FA344000 push PackMe.004034FA ; |Buffer = PackMe.004034FA 004016F6 . FF35 44344000 push dword ptr [403444] ; |hFile = NULL 004016FC . E8 11010000 call <jmp.&KERNEL32.ReadFile> ; \ReadFile 从注册文件读取一个字节,送到004034FA 00401701 . 0FB605 FA3440>movzx eax, byte ptr [4034FA] ; 把读来的字节扩展送EAX 00401708 . 85C0 test eax, eax 0040170A . 74 3B je short PackMe.00401747 ; 字符为0 就跳,跳就完蛋 0040170C . 6A 00 push 0 ; /pOverlapped = NULL 0040170E . 68 48344000 push PackMe.00403448 ; |pBytesRead = PackMe.00403448 00401713 . 50 push eax ; |BytesToRead 00401714 . 68 88324000 push PackMe.00403288 ; |Buffer = PackMe.00403288 00401719 . FF35 44344000 push dword ptr [403444] ; |hFile = NULL 0040171F . E8 EE000000 call <jmp.&KERNEL32.ReadFile> ; \ReadFile 再接着读取49个字节,送到00403288 00401724 . E8 D7F8FFFF call PackMe.00401000 ; 以首字节大小为次数,计算后面的字节数据和,取和的低8位 {00401000 /$ 33C0 xor eax, eax 00401002 |. 33D2 xor edx, edx 00401004 |. 33C9 xor ecx, ecx 00401006 |. 8A0D FA344000 mov cl, byte ptr [4034FA] ; 注册文件第一字节送CL 0040100C |. BE 88324000 mov esi, PackMe.00403288 ; ASCII "23456789" 00401011 |> AC /lods byte ptr [esi] 00401012 |. 03D0 |add edx, eax ; 每字节数据累加存于EDX 00401014 |.^ E2 FB \loopd short PackMe.00401011 ; 循环次数是CL=注册文件第一字节数值 00401016 |. 8815 FB344000 mov byte ptr [4034FB], dl ; DL送004034FB保存 0040101C \. C3 ret} 00401729 . 6A 00 push 0 ; /pOverlapped = NULL 0040172B . 68 48344000 push PackMe.00403448 ; |pBytesRead = PackMe.00403448 00401730 . 6A 12 push 12 ; |BytesToRead = 12 (18.) 00401732 . 68 E8344000 push PackMe.004034E8 ; |Buffer = PackMe.004034E8 00401737 . FF35 44344000 push dword ptr [403444] ; |hFile = NULL 0040173D . E8 D0000000 call <jmp.&KERNEL32.ReadFile> ; \ReadFile 再读取18个字节,存于004034E8 00401742 . E8 82F9FFFF call PackMe.004010C9 ; 这里是一个算法,我们跟F7进去 {004010C9处的函数,参见下面的“最核心算法”分析 也就是在这个算法里,我们一步一步观察OD,就可以发现迷宫的数据源: 004010CF |. 68 65334000 push PackMe.00403365 ; /String2 = "****************C*......*...****.*.****...*....*.*..**********.*..*....*...*...**.****.*.*...****.*....*.*******..*.***..*.. ...*.*..***.**.***.*...****....*X..*****************" /-------------------------------------------------------------------------------------------\ 00403365 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A **************** 00403375 43 2A 2E 2E 2E 2E 2E 2E 2A 2E 2E 2E 2A 2A 2A 2A C*......*...**** 00403385 2E 2A 2E 2A 2A 2A 2A 2E 2E 2E 2A 2E 2E 2E 2E 2A .*.****...*....* 00403395 2E 2A 2E 2E 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2E 2A .*..**********.* 004033A5 2E 2E 2A 2E 2E 2E 2E 2A 2E 2E 2E 2A 2E 2E 2E 2A ..*....*...*...* 004033B5 2A 2E 2A 2A 2A 2A 2E 2A 2E 2A 2E 2E 2E 2A 2A 2A *.****.*.*...*** 004033C5 2A 2E 2A 2E 2E 2E 2E 2A 2E 2A 2A 2A 2A 2A 2A 2A *.*....*.******* 004033D5 2E 2E 2A 2E 2A 2A 2A 2E 2E 2A 2E 2E 2E 2E 2E 2A ..*.***..*.....* 004033E5 2E 2A 2E 2E 2A 2A 2A 2E 2A 2A 2E 2A 2A 2A 2E 2A .*..***.**.***.* 004033F5 2E 2E 2E 2A 2A 2A 2A 2E 2E 2E 2E 2A 58 2E 2E 2A ...****....*X..* 00403405 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A **************** \-------------------------------------------------------------------------------------------/} 00401747 > FF35 44344000 push dword ptr [403444] ; /hObject = NULL 0040174D . E8 A2000000 call <jmp.&KERNEL32.CloseHandle> ; \CloseHandle 00401752 > EB 15 jmp short PackMe.00401769 00401754 > FF75 14 push dword ptr [ebp+14] ; /lParam; Default case of switch 004012D8 00401757 . FF75 10 push dword ptr [ebp+10] ; |wParam 0040175A . FF75 0C push dword ptr [ebp+C] ; |Message 0040175D . FF75 08 push dword ptr [ebp+8] ; |hWnd 00401760 . E8 17000000 call <jmp.&USER32.DefWindowProcA> ; \DefWindowProcA 00401765 . C9 leave 00401766 . C2 1000 ret 10 00401769 > 33C0 xor eax, eax 0040176B . C9 leave 0040176C . C2 1000 ret 10
00401033 $ 55 push ebp 00401034 . 8BEC mov ebp, esp 00401036 . 83C4 F8 add esp, -8 00401039 . 8B15 84314000 mov edx, dword ptr [403184] ; 一看就是一个全局变量,用直接寻址,设为lpCursor(因为一会儿就会发现,这是保存当前所在迷宫位置的指针) 0040103F . 8955 FC mov dword ptr [ebp-4], edx ; 一看[EBP-4]就是用局部变量,设为_dwCur,即dwCursor=_dwCur 00401042 . 0AC0 or al, al ; 或运算,就是检查AL是否为0; Switch (cases 0..2) 00401044 . 75 09 jnz short PackMe.0040104F ; AL不为0就跳 00401046 . 832D 84314000>sub dword ptr [403184], 10 ; lpCursor-10h,因为迷宫一行是16个字符,所以可知道是向上移动一步。说明0是向上; Case 0 of switch 00401042 0040104D . EB 1F jmp short PackMe.0040106E ; 移动完毕,跳之 0040104F > 3C 01 cmp al, 1 00401051 . 75 08 jnz short PackMe.0040105B ; AL不为1就跳 00401053 . FF05 84314000 inc dword ptr [403184] ; AL为1就lpCursor+1,即向右移动一位,说明AL=1是向右移动; Case 1 of switch 00401042 00401059 . EB 13 jmp short PackMe.0040106E ; 移动完毕,跳之 0040105B > 3C 02 cmp al, 2 0040105D . 75 09 jnz short PackMe.00401068 ; AL不为2就跳 0040105F . 8305 84314000>add dword ptr [403184], 10 ; lpCursor+10h,一行迷宫是16个字符,所以可知AL=2是向下移动一位; Case 2 of switch 00401042 00401066 . EB 06 jmp short PackMe.0040106E ; 移动完毕,跳之 00401068 > FF0D 84314000 dec dword ptr [403184] ; 因为AL是对3求余,所以取值只能为0~3,运行到这条代码,说明只能AL=3了,说明是向左移动一位,即lpCursor-1; Default case of switch 00401042 0040106E > 8B15 84314000 mov edx, dword ptr [403184] ; 在此F4,运行到此 00401074 . 8A02 mov al, byte ptr [edx] ; 本条和上面一条代码一起看,发现,这不就是取指针嘛!果然明白了,取移动后,当前所以地的值。 00401076 . 3C 2A cmp al, 2A ; 与2Ah即'*'比较 00401078 . 75 06 jnz short PackMe.00401080 ; 撞墙啦,说明没走出迷宫,隔屁了。 0040107A . 33C0 xor eax, eax 0040107C . C9 leave 0040107D . C3 ret ; 隔屁(就是挂了的意思。) 0040107E . EB 33 jmp short PackMe.004010B3 00401080 > 3C 58 cmp al, 58 ; 58h即ASCII的'X',观察迷宫可知道,X多半是表示出口。 00401082 ^\75 90 jnz short PackMe.00401014 ; 不是出口,说明还要继续走(注意,此处可以爆破) 00401084 . 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL 00401086 . 8D15 59334000 lea edx, dword ptr [403359] ; | 0040108C . 52 push edx ; |Title => "Success.." 0040108D . 8D15 EC324000 lea edx, dword ptr [4032EC] ; | 00401093 . 52 push edx ; |Text => "Congratulations!",LF,CR,"Mail me (KwazyWebbit@hotmail.com) how you did it.",LF,CR,"Dont forget to include your keyfile! =]" 00401094 . 6A 00 push 0 ; |hOwner = NULL 00401096 . 8D15 AC174000 lea edx, dword ptr [4017AC] ; | 0040109C . FFD2 call edx ; \MessageBoxA 0040109E . 8D15 7B324000 lea edx, dword ptr [40327B] 004010A4 . 52 push edx ; /Text => "Cracked by : 23456789" 004010A5 . FF35 20344000 push dword ptr [403420] ; |hWnd = 000204C2 ('UNREGISTERED!',class='Edit',parent=000204CA) 004010AB . 8D15 DC174000 lea edx, dword ptr [4017DC] ; | 004010B1 . FFD2 call edx ; \SetWindowTextA 004010B3 > 8B15 84314000 mov edx, dword ptr [403184] ;PackMe.004031DC 004010B9 . C602 43 mov byte ptr [edx], 43 004010BC . 8B55 FC mov edx, dword ptr [ebp-4] 004010BF . C602 20 mov byte ptr [edx], 20 004010C2 . B8 01000000 mov eax, 1 004010C7 . C9 leave 004010C8 . C3 ret
00401080 > \3C 58 cmp al, 58 ; 58h即ASCII的'X',观察迷宫可知道,X多半是表示迷宫出口。 00401082 90 nop ; 不是出口,说明还要继续走(注意,此处可以爆破) 00401083 90 nop 00401084 . 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL 00401086 . 8D15 59334000 lea edx, dword ptr [403359] ; | 0040108C . 52 push edx ; |Title => "Success.." 0040108D . 8D15 EC324000 lea edx, dword ptr [4032EC] ; | 00401093 . 52 push edx ; |Text => "Congratulations!",LF,CR,"Mail me (KwazyWebbit@hotmail.com) how you did it.",LF,CR,"Dont forget to include your keyfile! =]" 00401094 . 6A 00 push 0 ; |hOwner = NULL 00401096 . 8D15 AC174000 lea edx, dword ptr [4017AC] ; | 0040109C . FFD2 call edx ; \MessageBoxA
00401000 /$ 33C0 xor eax, eax 00401002 |. 33D2 xor edx, edx 00401004 |. 33C9 xor ecx, ecx 00401006 |. 8A0D FA344000 mov cl, byte ptr [4034FA] ; 注册文件第一字节送CL 0040100C |. BE 88324000 mov esi, PackMe.00403288 ; ASCII "23456789" 00401011 |> AC /lods byte ptr [esi] 00401012 |. 03D0 |add edx, eax ; 每字节数据累加存于EDX 00401014 |.^ E2 FB \loopd short PackMe.00401011 ; 循环次数是CL=注册文件第一字节数值 00401016 |. 8815 FB344000 mov byte ptr [4034FB], dl ; DL送004034FB保存 0040101C \. C3 ret 0040101D /$ 8A15 FB344000 mov dl, byte ptr [4034FB] ; 取出第1步加密004034FB中的数据,记为m 00401023 |. B9 12000000 mov ecx, 12 ; 循环12h=18次 00401028 |. B8 E8344000 mov eax, PackMe.004034E8 ; char *p=0x004034E8 0040102D |> 3010 /xor byte ptr [eax], dl ; *p=*p与m异或 0040102F |. 40 |inc eax ; p++ 00401030 |.^ E2 FB \loopd short PackMe.0040102D 00401032 \. C3 ret 00401033 $ 55 push ebp 00401034 . 8BEC mov ebp, esp 00401036 . 83C4 F8 add esp, -8 00401039 . 8B15 84314000 mov edx, dword ptr [403184] ; 一看就是一个全局变量,用直接寻址,设为lpCursor(因为一会儿就会发现,这是保存当前所在迷宫位置的指针) 0040103F . 8955 FC mov dword ptr [ebp-4], edx ; 一看[EBP-4]就是用局部变量,设为_dwCur,即dwCursor=_dwCur 00401042 . 0AC0 or al, al ; 或运算,就是检查AL是否为0; Switch (cases 0..2) 00401044 . 75 09 jnz short PackMe.0040104F ; AL不为0就跳 00401046 . 832D 84314000>sub dword ptr [403184], 10 ; lpCursor-10h,因为迷宫一行是16个字符,所以可知道是向上移动一步。说明0是向上; Case 0 of switch 00401042 0040104D . EB 1F jmp short PackMe.0040106E ; 移动完毕,跳之 0040104F > 3C 01 cmp al, 1 00401051 . 75 08 jnz short PackMe.0040105B ; AL不为1就跳 00401053 . FF05 84314000 inc dword ptr [403184] ; AL为1就lpCursor+1,即向右移动一位,说明AL=1是向右移动; Case 1 of switch 00401042 00401059 . EB 13 jmp short PackMe.0040106E ; 移动完毕,跳之 0040105B > 3C 02 cmp al, 2 0040105D . 75 09 jnz short PackMe.00401068 ; AL不为2就跳 0040105F . 8305 84314000>add dword ptr [403184], 10 ; lpCursor+10h,一行迷宫是16个字符,所以可知AL=2是向下移动一位; Case 2 of switch 00401042 00401066 . EB 06 jmp short PackMe.0040106E ; 移动完毕,跳之 00401068 > FF0D 84314000 dec dword ptr [403184] ; 因为AL是对3求余,所以取值只能为0~3,运行到这条代码,说明只能AL=3了,说明是向左移动一位,即lpCursor-1; Default case of switch 00401042 0040106E > 8B15 84314000 mov edx, dword ptr [403184] ; 在此F4,运行到此 00401074 . 8A02 mov al, byte ptr [edx] ; 本条和上面一条代码一起看,发现,这不就是取指针嘛!果然明白了,取移动后,当前所以地的值。 00401076 . 3C 2A cmp al, 2A ; 与2Ah即'*'比较 00401078 . 75 06 jnz short PackMe.00401080 ; 撞墙啦,说明没走出迷宫,隔屁了。 0040107A . 33C0 xor eax, eax 0040107C . C9 leave 0040107D . C3 ret ; 隔屁(就是挂了的意思。) 0040107E . EB 33 jmp short PackMe.004010B3 00401080 > 3C 58 cmp al, 58 ; 58h即ASCII的'X',观察迷宫可知道,X多半是表示出口。 00401082 ^ 75 90 jnz short PackMe.00401014 ; 不是出口,说明还要继续走(注意,此处可以爆破)
//下面开始总结一下,并试着写写注册机。 //首先,我用文字(或者看作是伪代码)说明一下: void main() { char a[100]; char b[100]; int sum; int local len=0; int local d=0; int *ptr_b=&b; int *ptr_local=null; int temp=0; char migong[][]={ ****************, "C*......*...****", ".*.****...*....*", ".*..**********.*", "..*....*...*...*", "*.****.*.*...***", "*.*....*.*******", "..*.***..*.....*", ".*..***.**.***.*", "...****....*X..*", "****************" }; if(exist(KwazyWeb.bit)==true) { int total=Read(KwazyWeb.bit,第1个字节数据) if(total==0) { return false; } else { a[]=Read(KwazyWeb.bit,第2 ~ total位的数据) for(i=0;i<total;i++) { sum+=a[i]; } sum=sum & 0x0ff //取低8位 b[]=Read(KwazyWeb.bit,第total+1 ~ 第total+1+18位的数据) lstrcpy(二维数组map[][],迷宫数组migong[][]); lpCursor=map[1][0]; //即迷宫起点处 //b[]的前18个数据分别与sum异或 for(j=0;j<18;j++) { b[j]=b[j] ^ sum; } len=0; do { d=8; do { d=d-2; len=len+ptr_b; temp=(*ptr_b)>>d; temp=temp & 0x011; *ptr_local=*ptr_b; switch(temp) { case 0:lpCursor=lpCursor-10h;break; //迷宫中向上移动 case 1:lpCursor=lpCursorr+1h;break; //迷宫中向右移动 case 2:lpCursor=lpCursor+10h;break; //迷宫中向下移动 default:lpCursor=lpCursor-1h; //迷宫中向左移动 } if(*lpCursor=='*') { flag=0; } else if(*lpCursor=='X') { MessageBox("成功,逆向出来了!"); } else { *lpCursor='C'; //表示移动后,当前所在地 *ptr_local=' '; //表示移动前,走过的地方为空格 flag=1; } if(flag==0) return false; }while(d!=0); len++; }while(len<18); } } else { return false; } } //算法就是这样,和书中是一回事,只是书中更加简洁明了。我的语言就不是那么直白。 //明天写写注册机吧,晚安。
用C写了一个注册机
说下写的思路
---------------------------------
首先,我们可以得知,这个程序是静态的(,不就是字符数组常量嘛!还整个“静态的”...)
"****************", "C*......*...****", ".*.****...*....*", ".*..**********.*", "..*....*...*...*", "*.****.*.*...***", "*.*....*.*******", "..*.***..*.....*", ".*..***.**.***.*", "...****....*X..*",
#include<stdio.h> voidmain() { unsigned char string[]={"下下下右下下下左下下右右上右上右右右右上上左左左上左上上右右右右右下右右上右右下右右右下下左左下左左上左左下下下左下下右右右上上右右右右下下左左e"}; inti; intlen=0; for(i=0;string[i]!='e';i+=2)//因为一个汉字占2个字节,所以加2 { switch(string[i]){ case0x0c9:printf("0");len++;break;//0x0c9是"上"的高位数据,存放在低地址。 case0x0d3:printf("1");len++;break;//0x0d3是"右"的高位数据,存放在低地址。 case0x0cf:printf("2");len++;break;//0x0cf是"下"的高位数据,存放在低地址。 case0x0d7:printf("3");len++;break;//0x0d7是"左"的高位数据,存放在低地址。 default:break; } if(len%4==0)//4步一组,方便观看 { printf(""); } } }
#include<stdio.h> #include<string.h> void main() { unsigned char name[256]; unsigned int part3[18]; //这是迷宫路径数据 unsigned int key[18]={0xa9,0xab,0xa5,0x10,0x54,0x3f,0x30,0x55,0x65, 0x16,0x56,0xbe,0xf3,0xea,0xe9,0x50,0x55,0xaf}; int i=0,j=0; int len=0; int sum=0; printf("请输入用户名以'#'结束:\n\t"); //输入块2的数据,以'#'号结束 gets(name); //取得块2的长度,即块1的数据值 len=strlen(name); if(len<=15) { printf("0"); } printf("块1的数据为:\t%2X\n",len); printf("块2的数据为:\t"); for(i=0;name[i]!='\0';i++) { if(name[i]<=15) { printf("0"); } printf("%X ",name[i]); } printf("\n"); //块2数据累加,取低8位 for(i=0;name[i]!='\0';i++) { if(name[i]<=15) { printf("0"); } sum+=name[i]; } sum=sum & 0xff; //下面,根据a^b^b=a来算出块3的数据 printf("块3的数据为:\t"); for(i=0;i<18;i++) { part3[i]=sum^key[i]; if(part3[i]<=15) { printf("0"); } printf("%X ",part3[i]); } printf("\n"); printf("----------------------------------------\n"); printf("注册文件的数据流为:\n\t"); //输出块1数据 if(len<=15) { printf("0"); } printf("%X",len); //输出块2数据 for(i=0;name[i]!='\0';i++) { if(name[i]<=15) { printf("0"); } printf("%X",name[i]); } //输出块3数据 for(i=0;i<18;i++) { if(part3[i]<=15) { printf("0"); } part3[i]=sum^key[i]; printf("%X",part3[i]); } printf("\n\t您可以复制出上面的数据,保存为十六进制文件\n命名为:KwazyWeb.bit\n\t则破解文件保护。"); }