1. 用 IDA 反汇编 QSP Level 2.EXE , IDA 分析完后, 按 CTRL + E, 来到程序入口.
.code:00402000 start proc near
.code:00402000 push 0 ; lpModuleName
.code:00402002 call ds:GetModuleHandleA
.code:00402008 push 0 ; dwInitParam
.code:0040200A push offset DialogFunc ; lpDialogFunc
.code:0040200F push 0 ; hWndParent
.code:00402011 push 25h ; lpTemplateName
.code:00402013 push eax ; hInstance
.code:00402014 call ds:DialogBoxParamA ; Create a modal dialog box from a
.code:00402014 ; dialog box template resource
.code:0040201A push 0 ; uExitCode
.code:0040201C call ds:ExitProcess
.code:0040201C start endp
分析: 可以从 push offset DialogFunc ; lpDialogFunc 看出函数 DialogFunc 处理对话框的消息, 双击它来到以下:
2.
.code:00402022 push ebp
.code:00402023 mov ebp, esp
.code:00402025 push ebx
.code:00402026 push esi ; nMaxCount
.code:00402027 push edi ; hDlg
.code:00402028 cmp [ebp+Msg], WM_INITDIALOG
.code:0040202F jz loc_402146
.code:00402035 cmp [ebp+Msg], WM_COMMAND
.code:0040203C jz short WM_COMMAND ; 跳到处理 WM_COMMAND 消息的地方
.code:0040203E cmp [ebp+Msg], WM_CLOSE
.code:00402042 jz Close_Dialog
.code:00402048 xor eax, eax
.code:0040204A jmp Do_Nothing
.code:0040204F WM_COMMAND: ; CODE XREF: DialogFunc+1Aj
.code:0040204F cmp [ebp+WPARAM], 66h ; "I give up" 按钮的ID
.code:00402053 jz Leave
.code:00402059 cmp [ebp+WPARAM], 65h ; "Test" 按钮的ID
.code:0040205D jnz loc_402146 ; 如不相等,就不处理
.code:00402063 push 13h ; nMaxCount
.code:00402065 push offset Name ; lpString
.code:0040206A push 0C9h ; nIDDlgItem, "Name" 的 ID
.code:0040206F push [ebp+hDlg] ; hDlg
.code:00402072 call ds:GetDlgItemTextA
.code:00402078 push 9 ; nMaxCount
.code:0040207A push offset Serial_Part1 ; lpString
.code:0040207F push 0CAh ; nIDDlgItem, "Serial" 的 ID
.code:00402084 push [ebp+hDlg] ; hDlg
.code:00402087 call ds:GetDlgItemTextA
.code:0040208D mov eax, Serial_Part1
.code:00402092 sub eax, 41414141h ; AAAA
.code:00402097 mov ebx, Serial_Part2; "Serial_Part1" 的位移 +4
.code:0040209D sub ebx, 41414141h ; AAAA
.code:004020A3 shl ebx, 4
.code:004020A6 or eax, ebx
.code:004020A8 mov Temp, eax
.code:004020AD mov esi, offset Name
.code:004020B2 sub edx, edx ;edx 清零
分析: 从 push 9; nMaxCount 可看出它只把 Serial 的前九位放进内存,但是后 0040208D ~ 0040209D , 可看出它只取 Serial 的前八位计算
为了方便,我们首先定义 0040208D ~ 004020A8 为 F(Serial), 如果我们计算到 temp 的值如何找到 Serial 呢?即是要 F(Serial) 的逆运算,但是 004020A6 的 or eax, ebx 是不可逆的,因为这个 or 令到一个正确的 temp 可导致很多正确的 Serial. 我们唯有做些取巧的工作,我们假设在执行004020A6时 eax 是这样形式的: 0X0X0X0X ;
ebx 是这样形式的: X0X0X0X0 。这里X是一个整数,并且 0 <= X <= F。这样如我们找到正确的 temp 值, 就可以以下的代码找回 Serial,可以看出这样求出的 Serial 是八个A-P字符来的
mov eax, temp
mov ebx, eax
and ebx, 0F0F0F0Fh
and eax, 0F0F0F0F0h
mov edx, offset Serial
add edx, 41414141h
mov [edx], ecx
shr eax, 4
add eax, 41414141h
mov [edx+4], eax
invoke SetDlgItemText, hWin, IDC_SERIAL,offset Serial
3. 继续看下去:
.code:004020B4 loc_4020B4: ; CODE XREF: DialogFunc+B3j
.code:004020B4 lodsb ; Load byte ptr[ESI] into al
.code:004020B5 or al, al ; 是 "Null" 吗? 处理完了吗
.code:004020B7 jz short Main_Calculation ; 如处理完,就跳
.code:004020B9 mov ecx, 8
.code:004020BE
.code:004020BE loc_4020BE: ; CODE XREF: DialogFunc+B1j
.code:004020BE shr al, 1 ; 放 al 最右的位元进 C flag
.code:004020C0 jb short loc_4020CB ; 如果 C flag, 即是 al 最右的位元是一就跳
.code:004020C2 xor edx, 8F7BA832h
.code:004020C8 rol edx, 7
.code:004020CB
.code:004020CB loc_4020CB: ; CODE XREF: DialogFunc+9Ej
.code:004020CB add edx, 9AB832ADh
.code:004020D1 bswap edx
.code:004020D3 loop loc_4020BE
.code:004020D5 jmp short loc_4020B4 ; 下一个字符
分析: 这里是处理 Name 的地方, 我们定义004020B4 ~ 004020D5 为 G(Name)。因为我们的 Keygen 是用 Name 求出 Serial 的,所以不用逆 G(Name)
4.我们来到 Main_Calculation:
.code:004020D7 mov eax, Temp
.code:004020DC pusha
.code:004020DD call Calculation
.code:004020E2 xchg eax, [esp+1Ch] ; [esp+1c] 是经过 pusha,储存原本 EAX 的地方
.code:004020E2 ; 所以 xchg 和popa 后,
.code:004020E2 ; EAX 仍然是 Calculation 的结果, 否则会被覆盖
.code:004020E6 popa
.code:004020E7 cmp edx, eax ; G(Name) == H(F(Serial))?
.code:004020E9 mov eax, offset aVictory ; "Victory!"
.code:004020EE mov ebx, offset aYouWonPlayAgai ; "You won! Play again? ;)"
.code:004020F3 jz short Sucess
.code:004020F5 mov eax, offset Caption ; "Nope."
.code:004020FA mov ebx, offset Text ; "Sorry, thats not it."
分析: 如果各位想爆破的,可以修改 004020F3 的 jz short Sucess 为 jmp short Sucess;如要写 Keygen ,就要写出 Calculation 的逆运算了,用枚举法是不行的,因为 Calculation 的指令太多了, 如用枚举法大概会当机的了。
5.进入 Calculation 你会发现这是一个 1541 行指令的函数。我发现后我立即放弃了。几天后,我比较有精神,才来处理它。
我们首先清理混乱码。这样我们就要从函数的后面开始分析:
.code:004032CF or esi, ecx ; Mov
.code:004032D1 and esi, ecx
.code:004032D3 adc edi, edi ; No use
.code:004032D5 xor esi, 22h
.code:004032D8 ror edi, 1Eh ; No use
.code:004032DB add esi, 0FFFFFF97h
.code:004032DE not esi
.code:004032E0 not edi ; No use
.code:004032E2 bswap esi
.code:004032E4 sal ebp, 19h ; No use
.code:004032E7 not esi
.code:004032E9 inc ecx ;No Use
.code:004032EA ror esi, 1
.code:004032EC and ebx, 0FFFFFFA0h ; No Use
.code:004032EF neg esi
.code:004032F1 xor esi, 0FFFFFFE2h
.code:004032F4 std ; No use
.code:004032F5 mov eax, esi
.code:004032F7 retn
分析: 明显影响结果的是 ESI 的值,4032F4, 4032EC 等不会影响 ESI 的值的就是混乱码。
注意: 这个函数有几个变形 mov, 就像处理壳时有变形 jmp 一样。以下是一些变形 mov
1. or esi, ecx; and esi, ecx 即是 mov esi, ecx
2. push ecx; pop esi 即是 mov esi, ecx
3. xchg ecx, esi 也是
这样 ESI 在只会在004032CF时被 ECX 影响, 其他的是混乱码,把它们全清除。并把所有变形 mov, 改为 mov 指令。在清理完后,Calculation 大概会剩下 1047 行令, 当然其中还包括混乱码,但是所有这些指令都是可逆的了。即是 Calculation 只有 mov, add, sub, ror, rol, xchg, dec, inc, bswap, xor 指令。 如有其他的,大概是混乱码了。
清理完后的函数Calculation, clean.asm 我已加入在以下的keygen_qsplevel2.zip 了
6.最后就是会把这些指令的顺序调换一下,即是把第 1047 行的指令调到第 1 行等等。我制作了一个在 Ultra-Edit 中运行的宏(已包含在后面的那个ZIP里)来完成这个动作。
并把所有add变成sub, sub变成add, ror变成rol, rol变成ror, mov 和 变形 mov 变成 xchg, inc变成dec, dec 变成inc (注意有个数字包令 DEC 三个字), inc 变成 dec, 把第一行指令xchg eax, esi 改成 xchg edx, esi, 因为G(name)的值在 EDX里
这时,我们已把Calculation逆了,要求出Serial只要 F'(H'(G(Name))), F'() 是 F 的逆运算, H' 是Calculation的逆运算。
这是关于这对这 crackme 有帮助的档案:
http://maxputer.tripod.com/pediy/keygen_qsplevel2.zip
那keygen的来源档建议用winasm来编译, winasm 是 MASM 的一个前端 IDE, 功能强大, 介面十分友善。