下载地址:http://www.crackmes.de/users/toshimi/serialme
使用工具:
反编译及调试工具:OllyDbg,IDA,Resource Hacker,LordPE,ImportREC,FixRes
程序开发包:MASM32
1. 脱壳及修复资源
这个Crackme加了UPX的壳,如果只是为了破解,本来可以带壳调试,但是不脱壳则没有
办法提取它的资源,也就无法构建源代码。
脱壳的基本过程可以参考脱壳版FAQ:
http://bbs.pediy.com/showthread.php?s=&threadid=20366
这里就不再详解。脱了壳之后的程序虽然能正常运行,但是用ResHacker无法解析资源,必
须再用FixRes修复一遍以后才行。
2. 逆向
用IDA载入可执行文件。这个程序的入口点有些模仿C语言的WinMain()函数,这里把它
命名为_CMain。这个子程序中进行注册窗口类、创建窗口、显示窗口和建立消息循环的工
作,根据窗口类成员lpfnWndProc的值可以找到主窗口过程。
进入主窗口程序可以发现,主窗口上的编辑框以及三个按钮都是用CreateWindowEx即时
创建的,这也是资源脚本中没有定义控件的一个原因。在创建控件的时候需要记录hMenu也
就是CreateWindowEx的第十个参数,这个参数相当于对话框程序中的所谓控件ID,在处理
WM_COMMAND消息的时候要用到。WM_PAINT分支的代码看起来比较多,但其功能说白了只有一
个,那就是把资源中唯一的图片贴到窗口的背景上去。
我们最为关注的部分是WM_COMMAND分支,这个分支中主要对付的就是由三个按钮发送的
消息,而“About”和“Clean”两个按钮引发的事件又相对比较简单,主要的是“Enter”
按钮引发的事件。在“Enter”按钮事件分支里,程序在GetDlgItemText函数取得序列号输
入框的内容后调用一个过程sub_401373(这里把它重命名为_CheckSerial),其内容如下:
===================== 以下是代码 =========================
UPX0:00401373 _CheckSerial proc near ; CODE XREF: _ProcWinMain+176p
UPX0:00401373 push eax
UPX0:00401374 xor eax, eax
UPX0:00401376 xor edx, edx
UPX0:00401378 lea esi, szSerial
UPX0:0040137E
UPX0:0040137E loc_40137E: ; CODE XREF: _CheckSerial+1Ej
UPX0:0040137E ; _CheckSerial+25j
UPX0:0040137E mov al, [edx+esi] ; 取用户名的第i个字符
UPX0:00401381 test al, al
UPX0:00401383 jz short loc_40139A
UPX0:00401385 mov cl, 1
UPX0:00401387 inc edx
UPX0:00401388 inc cl
UPX0:0040138A div cl ; 除以2
UPX0:0040138C cmp ah, 0
UPX0:0040138F jz short loc_401393 ; bh = 序列号中偶字符的个数
UPX0:00401391 jmp short loc_40137E
UPX0:00401393 loc_401393: ; CODE XREF: _CheckSerial+1Cj
UPX0:00401393
UPX0:00401393 inc bh ; bh = 序列号中偶字符的个数
UPX0:00401395 sub cl, 2
UPX0:00401398 jmp short loc_40137E
UPX0:0040139A
UPX0:0040139A loc_40139A: ; CODE XREF: _CheckSerial+10j
UPX0:0040139A push eax
UPX0:0040139B rol ebx, 8 ; 将上面得到的bh值存入ebx的高16位
UPX0:0040139E push edx ; 现dl=序列号串长
UPX0:0040139F mov ah, 0
UPX0:004013A1 mov al, 1 ; mov eax, 1
UPX0:004013A3 mov cl, 2
UPX0:004013A5
UPX0:004013A5 loc_4013A5: ; CODE XREF: _CheckSerial+3Fj
UPX0:004013A5 mov bl, [eax+esi] ; 取szSerial的第2, 5, 9, 14等字符累加
到dl
UPX0:004013A8 test bl, bl
UPX0:004013AA jz short loc_4013B4
UPX0:004013AC add dl, bl
UPX0:004013AE inc cl
UPX0:004013B0 add al, cl ; 1+3+4+5+...
UPX0:004013B2 jmp short loc_4013A5 ; 取szSerial的第2, 5, 9, 14等字符累加
到dl
UPX0:004013B4
UPX0:004013B4 loc_4013B4: ; CODE XREF: _CheckSerial+37j
UPX0:004013B4 mov ax, 0FFFFh
UPX0:004013B8 cmp dl, 0ABh ; 如相等,下面调用sub_40145D,否则调用
sub_4013D9
UPX0:004013BB jnz short loc_4013C8
UPX0:004013BD inc ax
UPX0:004013BF jmp short loc_4013CC
UPX0:004013C1
UPX0:004013C1 loc_4013C1: ; CODE XREF: _CheckSerial:loc_4013CCj
UPX0:004013C1 ; _CheckSerial+60j
UPX0:004013C1 call sub_4013D9
UPX0:004013C6 jmp short loc_4013D5
UPX0:004013C8
UPX0:004013C8 loc_4013C8: ; CODE XREF: _CheckSerial+48j
UPX0:004013C8 add ax, 1
UPX0:004013CC
UPX0:004013CC loc_4013CC: ; CODE XREF: _CheckSerial+4Cj
UPX0:004013CC jb short loc_4013C1
UPX0:004013CE call sub_40145D ; 关键是有否把ecx清零
UPX0:004013D3 jmp short loc_4013C1
UPX0:004013D5
UPX0:004013D5 loc_4013D5: ; CODE XREF: _CheckSerial+53j
UPX0:004013D5 pop edx
UPX0:004013D6 pop eax
UPX0:004013D7 pop eax
UPX0:004013D8 retn
UPX0:004013D8 _CheckSerial endp
===================== 以上是代码 =========================
这段代码中有一些无用的语句,关键的地方是从loc_4013A5开始的,在这个标号之前dl的值
也就是edx的值等于序列号的串长,而loc_4013A5开始的循环是取序列号中的一些字符累加
到dl上,具体计算一下可以知道它取的字符依次是第2,5,9,14……个(由于下文会提到
合法字符串有长度上的要求,实际取的仅仅是第2,5,9个字符)。然后看dl是否等于
0ABh,如果等则先调用sub_40145D再调用sub_4013D9,否则直接调用sub_4013D9(注意inc
指令和加1的add指令在标志影响方面的区别)。那么sub_40145D有什么特别之处呢?跟进去
看一下:
===================== 以下是代码 =========================
UPX0:0040145D sub_40145D proc near ; CODE XREF: _CheckSerial+5Bp
UPX0:0040145D push ebx
UPX0:0040145E cmp byte ptr [esi+5], '3' ; 第6个字符必须是'3'
UPX0:00401462 jnz short loc_401496
UPX0:00401464 cmp byte ptr [esi+2], '-' ; 第3个字符必须是'-'
UPX0:00401468 jnz short loc_401496
UPX0:0040146A cmp byte ptr [esi+0Ah], '0'
UPX0:0040146E jbe short loc_401496
UPX0:00401470 cmp byte ptr [esi+0Bh], '1' ; 第12个字符必须是'1'
UPX0:00401474 jnz short loc_401496
UPX0:00401476 cmp byte ptr [esi+3], '1' ; 第4个字符必须是'1'
UPX0:0040147A jnz short loc_401496
UPX0:0040147C cmp byte ptr [esi+9], '0' ; 第10个字符必须是'0'
UPX0:00401480 jnz short loc_401496
UPX0:00401482 cmp byte ptr [esi+6], '9' ; 第7个字符必须是'9'
UPX0:00401486 jnz short loc_401496
UPX0:00401488 cmp byte ptr [esi+0Ah], '9' ; 第11个字符必须是数字且不为0
UPX0:0040148C ja short loc_401496
UPX0:0040148E cmp byte ptr [esi+7], '3' ; 第8个字符必须是'3'
UPX0:00401492 jnz short loc_401496
UPX0:00401494 xor ecx, ecx
UPX0:00401496
UPX0:00401496 loc_401496: ; CODE XREF: sub_40145D+5j
UPX0:00401496 ; sub_40145D+Bj
UPX0:00401496 ; sub_40145D+11j
UPX0:00401496 ; sub_40145D+17j
UPX0:00401496 ; sub_40145D+1Dj
UPX0:00401496 ; sub_40145D+23j
UPX0:00401496 ; sub_40145D+29j
UPX0:00401496 ; sub_40145D+2Fj
UPX0:00401496 ; sub_40145D+35j
UPX0:00401496 pop ebx
UPX0:00401497 retn
UPX0:00401497 sub_40145D endp
===================== 以上是代码 =========================
这个过程归结起来就是根据一些条件决定是否将ecx清零,当然现在我们暂时还不知道其目
的是什么。跟进另外一个过程sub_4013D9看看:
===================== 以下是代码 =========================
UPX0:004013D9 sub_4013D9 proc near ; CODE XREF: _CheckSerial:loc_4013C1p
UPX0:004013D9 mov dh, bh ; bh = 0
UPX0:004013DB lea edi, szInfo
UPX0:004013E1 xor ebx, ebx
UPX0:004013E3 mov dl, al
UPX0:004013E5 add esp, 4 ; 废除返回地址,现[esp] = 序列号长度+1
UPX0:004013E8 jnb short loc_401426 ; 绝对跳转
UPX0:004013EA
UPX0:004013EA loc_4013EA: ; CODE XREF: sub_4013D9+2Cj
UPX0:004013EA ; sub_4013D9+31j
UPX0:004013EA ; sub_4013D9+6Fj
UPX0:004013EA ; sub_4013D9+7Aj
UPX0:004013EA ; sub_4013D9+7Ej
UPX0:004013EA mov [ebx+edi], dl
UPX0:004013ED inc ebx
UPX0:004013EE cmp dl, 'o'
UPX0:004013F1 jz short loc_401407
UPX0:004013F3 cmp dl, 'r'
UPX0:004013F6 jz short loc_40140C
UPX0:004013F8 cmp dl, 'I'
UPX0:004013FB jz short loc_401455
UPX0:004013FD cmp dl, 'n'
UPX0:00401400 jz short loc_401446
UPX0:00401402 add dl, 2Ch
UPX0:00401405 jmp short loc_4013EA
UPX0:00401407
UPX0:00401407 loc_401407: ; CODE XREF: sub_4013D9+18j
UPX0:00401407 add dl, 3
UPX0:0040140A jmp short loc_4013EA
UPX0:0040140C
UPX0:0040140C loc_40140C: ; CODE XREF: sub_4013D9+1Dj
UPX0:0040140C mov byte ptr [ebx+edi], 72h
UPX0:00401410 inc ebx
UPX0:00401411 mov byte ptr [ebx+edi], 65h
UPX0:00401415 inc ebx
UPX0:00401416 mov byte ptr [ebx+edi], 63h
UPX0:0040141A inc ebx
UPX0:0040141B mov byte ptr [ebx+edi], 74h
UPX0:0040141F inc ebx
UPX0:00401420 mov byte ptr [ebx+edi], 21h
UPX0:00401424 jmp short loc_401459
UPX0:00401426
UPX0:00401426 loc_401426: ; CODE XREF: sub_4013D9+Fj
UPX0:00401426 pop eax
UPX0:00401427 xor eax, 0Bh
UPX0:0040142A cmp eax, 7 ; 7 xor B = C
UPX0:0040142D jnz short loc_40144A ; 注册失败
UPX0:0040142F cmp ecx, 0 ; 关键在于前面有否把ecx清零
UPX0:00401432 jnz short loc_40144A ; 注册失败
UPX0:00401434 xor dh, 0Ch
UPX0:00401437 cmp dh, 48h ; 48 xor C = 44('D')
UPX0:0040143A jnz short loc_40144A ; 注册失败
UPX0:0040143C mov byte ptr [eax+edi+1], 0
UPX0:00401441 mov byte ptr [eax+edi+2], 0
UPX0:00401446
UPX0:00401446 loc_401446: ; CODE XREF: sub_4013D9+27j
UPX0:00401446 mov dl, 43h
UPX0:00401448 jmp short loc_4013EA
UPX0:0040144A
UPX0:0040144A loc_40144A: ; CODE XREF: sub_4013D9+54j
UPX0:0040144A ; sub_4013D9+59j
UPX0:0040144A ; sub_4013D9+61j
UPX0:0040144A xor ebx, ebx ; 注册失败
UPX0:0040144C mov dl, 49h
UPX0:0040144E mov eax, 7
UPX0:00401453 jmp short loc_4013EA
UPX0:00401455
UPX0:00401455 loc_401455: ; CODE XREF: sub_4013D9+22j
UPX0:00401455 mov dl, 6Eh
UPX0:00401457 jmp short loc_4013EA
UPX0:00401459
UPX0:00401459 loc_401459: ; CODE XREF: sub_4013D9+4Bj
UPX0:00401459 sub esp, 8
UPX0:0040145C retn
UPX0:0040145C sub_4013D9 endp ; sp = -4
===================== 以上是代码 =========================
从loc_401426这里开始,前面的答案就一一揭晓了:首先弹出到eax的序列号串长(前面在
40139E处推入的edx)与0Bh相xor后的值必须是7,换言之串长必须是7 xor 0Bh = 0Ch,否
则注册失败;其次ecx的值必须是0,而上面知道只有执行过sub_40145D并且满足其中所有
条件后,ecx才会被清零。
但第三个条件就令人困惑了,它要求的是dh的值等于48h xor 0Ch = 44h,而4013D9处
的指令表明dh的值来自bh,而bh的值可以追溯到40139B处的rol ebx,8指令,此句后的bh值
等于此句前的bl值,但此句前就再也找不到可见的语句修改ebx的值了,用调试器跟踪可以
发现这时的bl值是零,也就是说,这个条件根本不可能达成!这一来,无论输什么号都不会
注册成功。所谓Bug就在这里!
在逆向出的代码中我放弃了这个判断条件,这一来,合法序列号的要求应该是:
(1)长为12字符;
(2)第3、4、6、7、8、10、12个字符分别为'-'、'1'、'3'、'9'、'3'、'0'、'1',
第11个字符在'1'到'9'之间;
(3)第2、5、9个字符之和应等于(0ABh - 0Ch = )9Fh。
另外在WM_COMMAND分支里不论是按下哪个按钮最终都会调用一个LoadBitmap,多次按下
就会多次调用,显得多余。个人认为这应该放到WM_CREATE分支里完成。
上传内容(在MASMv9下编译通过)
1.asm 汇编源文件
1.rc 资源脚本
Icon_2.ico 图标
Bitmap_1.bmp 位图
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)