名词注释
System breakpoint:系统断点,OllyDbg用CreateProcessA加载DEBUG_ONLY_THIS_PROCESS参数执行,程序运行之后会触发一个INT13,在系统空间里。
Entry point of main module:主模块的入口点,即文件的入口点。
WinMain:程序的WinMain()函数入口点
OD的设置中-选项-事件中设置
OD快捷键熟悉
1、F2 下断点,
2、Alt+b 打开断点编辑器,可编辑所有下过的断点
3、空格键 可快速切换断点状态。
4、Ctrl+F9.当位于某个CALL中,这时想返回到调用这个CALL的地方时,可以按“Ctrl+F9”快捷键执行返回功能。这样OD就会停在遇到的第一个返回命令(如RET、RETF或IRET)。
5、Alt+F9 如果跟进系统DLL提供的API函数中,此时想返回到应用程序领空里,可以按快捷键“Alt+F9”执行返回到用户代码命令。
6、Ctrl+G 跳转到API、地址的位置
逆向与开发的知识是成正比关系,只有对开发特别熟悉,逆向一个程序才能猜测到该用哪个关键的API才能快速定位到程序的数据处理。
通过PEID查看程序特征,根据程序语言用IDE生成特征或者熟悉开发的API函数。就可以更方便地让我们定位到获取edit值的函数。
【GetDlgItemText 函数】 用于获取对话框中指定控件的标题或文本。
‘使用OD的快捷键【Ctrl+G】跳转到API的位置下断点。
F2设置断点,当【GetDlgItemTextA】这个函数被调用OD就会中断
【ALT+B】快捷键可以打开断点窗口查看,在断点位置按【空格键】可以激活与禁用断点。
按快捷键【Ctrl+F9】可以回到调用函数的尾部ret处。
算法逆向
F7跟进004011E5地址内的函数,进入子程序call 00401340,特别值得注意的是00401359是跳转到4013680的,相关汇编代码注释如下:
加密函数汇编注释如下:
00401340 push ebp ; ebp入栈 00401341 mov ebp,dword ptr ss:[esp+0xC] ; 将用户名移动到ebp中 00401345 push esi ; esi入栈 00401346 push edi ; edi入栈 00401347 mov edi,dword ptr ss:[esp+0x18] ; 将参数从堆栈中传给edi(用户名长度值) edi = 5 0040134B mov ecx,0x3 ; ecx = 3 00401350 xor esi,esi ; esi 清 0 00401352 xor eax,eax ; eax 清0 00401354 cmp edi,ecx 00401356 jle XTraceMe.00401379 ; edi<ecx条件成立时跳转,i<len 00401358 push ebx 00401359 /cmp eax,0x7 ; 比较eax与7的值 0040135C jle XTraceMe.00401360 ; 当等于7,ZF=1短跳转 0040135E |xor eax,eax 00401360 |xor edx,edx ; edx清0 00401362 |xor ebx,ebx 00401364 |mov dl,byte ptr ds:[ecx+ebp] ; 地址低8位的一个字节,dl是存储一个字节的寄存器,ecx = 3, [ecx+ebp] = d 00401367 |mov bl,byte ptr ds:[eax+0x405030] ; 00405030 0C 0A 13 09 0C 0B 0A 08 0040136D |imul edx,ebx ; edx * ebx 赋值给 edx, dl为高8位,存储1字节数 00401370 |add esi,edx ; edx+esi,把值赋予给esi ,esi = 4B0 + 3F2 00401372 |inc ecx ; 递增指令,ecx 由 3 -> 4 -> 5 00401373 |inc eax ; eax = 1,eax++ 00401374 |cmp ecx,edi 00401376 \jl XTraceMe.00401359 ; ecx 是否大于等于5(用户名长度), jl指令大于不等于满足时跳转 00401378 pop ebx 00401379 push esi ; /<%ld> 0040137A push TraceMe.00405078 ; |Format = "%ld" 0040137F push ebp ; |s 00401380 call dword ptr ds:[<&USER32.wsprintfA>] ; \wsprintfA 00401386 mov eax,dword ptr ss:[esp+0x1C] 0040138A add esp,0xC 0040138D push ebp ; /String2 0040138E push eax ; |String1 0040138F call dword ptr ds:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA
程序原先输入的用户名:abcde、序列号:123456。在堆栈窗口看到的是d、e,也就是
name[3]=‘d’, name[4]=‘e’
根据00401367 地址处判断,密码数组索引第3位之后的值逐位取出与固定地址的值比对。然后在0040137A 会输出密钥的值2201。
仔细逆推一遍:
edx = 64 * 0C = 4B0 edx = 65 * 0A = 3F2 4B0 + 3F2 = 8A2 8A2对应的十进制为2210
逆向结论:
abcde转换为十六进制从索引值第3位开始逐位取值,64:d、65:e ,然后与00405030 0C 0A 13 09 0C 0B 0A 08对应的值进行相乘然后累加。得出的8A2转换成十进制2210就是密码的值。
把加密函数取出来就成了算号注册机,下面是加密函数反汇编转换来的C代码:
#include "stdafx.h" #include <string.h> //char name[65] = "abcdexxxx"; char name[65]; char table[8] = { 0xC ,0xA ,0x13 ,0x09 ,0x0C ,0x0B ,0x0A ,0x08 }; int main() { printf(" 输入key:\n "); scanf_s("%s",name,65); //会用到一个固定地址的值 //会用到姓名里的后两位 //eax = i int user_len = strlen(name); int key_code= 0; //esi int count_ecx = 3; //esi int eax = 0; //eax for (; count_ecx<user_len;) { if (eax>7) eax = 0; int ebx = 0; int edx = 0; edx = name[count_ecx]; ebx = table[eax]; ebx = edx * ebx; key_code += ebx; count_ecx++; eax++; } printf("key_code: %d", key_code); return 0; }
暴力破解
在定位到GetDlgItemText这个API处,F8单步步过向下跟随到有test判断的地方,注意观察数据堆栈区的位置。
汇编指令注释如下:
0040119C mov esi,dword ptr ss:[esp+0x100] ; Case 3F5 of switch 0040115E 004011A3 mov edi,dword ptr ds:[<&USER32.GetDlgIte>; user32.GetDlgItemTextA 004011A9 push ebx 004011AA lea eax,dword ptr ss:[esp+0x4C] 004011AE push 0x51 ; /Count = 51 (81.) 004011B0 push eax ; |Buffer 004011B1 push 0x6E ; |ControlID = 6E (110.) 004011B3 push esi ; |hWnd 004011B4 call edi ; \GetDlgItemTextA 004011B6 lea ecx,dword ptr ss:[esp+0x9C] 004011BD push 0x65 ; /最大字符数 004011BF push ecx ; |文本缓冲区指针 004011C0 push 0x3E8 ; |控件标识 004011C5 push esi ; |对话框句柄 004011C6 mov ebx,eax ; |将用户名的长度转到ebx中 004011C8 call edi ; \GetDlgItemTextA 004011CA mov al,byte ptr ss:[esp+0x4C] ; 将用户名的第一个字节给al 004011CE test al,al ; 检查有没有输入用户名 004011D0 je XTraceMe.00401248 ; 如果没有输入用户名跳走,告知输入的字符太少,zf=0跳转 004011D2 cmp ebx,0x5 004011D5 jl XTraceMe.00401248 ; 如果用户名不大于5那么就跳转到错误提示处 004011D7 lea edx,dword ptr ss:[esp+0x4C] ; 用户名地址放到edx中 004011DB push ebx ; 用户名长度 004011DC lea eax,dword ptr ss:[esp+0xA0] ; 密码地址放到eax 004011E3 push edx ; 用户名地址入栈 004011E4 push eax ; 密码地址入栈 004011E5 call TraceMe.00401340 ; 调用函数,相当于a("123456",abcde,5) 004011EA mov edi,dword ptr ds:[<&USER32.GetDlgIte>; user32.GetDlgItem 004011F0 add esp,0xC ; 平衡堆栈 004011F3 test eax,eax ; 函数返回值都是在eax里面的,eax=0注册失败,eax=1注册成功 004011F5 nop ; zf标志位为0,满足条件时跳转
第一个test指令首先对比用户名是否大于5,不大于5就跳转到弹出错误提示的地方。否则继续执行。F8单步步过进入【GetDlgItemTextA】后面的调用查看相关的代码。004011E5地址处call调用一个函数,并且在之前push了三个参数。
第二处test指令下面那条je跳转指令用nop填充掉。
然后右键【复制到可执行文件】-【所有修改】
选择全部复制
选择【保存文件】,暴力破解就完成了
成功后输入任何密码都提示成功弹框,截图如下:
样本引用
《加密与解密》这本书里的附带小程序TraceMe.exe。