0x00 初探
程序有ASLR,不方便分析,使用FFI去掉。
OD载入后,发现程序退出。
给ExitProcess下断点,然后重新载入程序,成功断下。修改代码为retn 4即可。
此时即可使用OD调试。运行后,[确认]按钮是禁止状态。
0x01 寻找编辑框的输入事件
用彗星探测一下编辑框的句柄
给GetWindowTextW下条件断点。
输入一个字节即可断下。
单步回到004020D9处。
程序首先计算注册码的长度:
然后判断长度是否是16个字符,不是则返回。
构造一个长度是16个字符的key即可通过。我构造的是0123456789ABCDEF。
程序将16个字符的key转换成ANSI字符的形式。
然后调用函数004017C0。
0x02 重点函数004017C0的逆向
函数比较长,我直接放代码。注释已经标记。
004017C0 $ 55 push ebp
004017C1 . 8BEC mov ebp, esp
004017C3 . 8B4D 08 mov ecx, dword ptr [ebp+8] ; Key
004017C6 . 83EC 08 sub esp, 8
004017C9 . 8BC1 mov eax, ecx
004017CB . 56 push esi
004017CC . 8D70 01 lea esi, dword ptr [eax+1]
004017CF . 90 nop
004017D0 > 8A10 mov dl, byte ptr [eax]
004017D2 . 40 inc eax
004017D3 . 84D2 test dl, dl
004017D5 .^ 75 F9 jnz short 004017D0
004017D7 . 2BC6 sub eax, esi ; 再次得到Key的长度
004017D9 . 83F8 10 cmp eax, 10 ; 不是16个字节则触发异常
004017DC . 74 01 je short 004017DF
004017DE FF db FF
004017DF > BA 58145700 mov edx, 00571458
004017E4 . 33F6 xor esi, esi
004017E6 . 2BD1 sub edx, ecx
004017E8 > 8A01 mov al, byte ptr [ecx] ; 取出一个字符 判断是否是数字或者是大写字母
004017EA . 3C 29 cmp al, 29
004017EC . 7E 08 jle short 004017F6
004017EE . 3C 40 cmp al, 40
004017F0 . 7D 06 jge short 004017F8
004017F2 . 2C 30 sub al, 30
004017F4 . EB 0A jmp short 00401800
004017F6 > 3C 40 cmp al, 40
004017F8 > 7E 52 jle short 0040184C
004017FA . 3C 47 cmp al, 47
004017FC . 7D 4E jge short 0040184C ; 如果不是数字或者不是大写字母则返回NULL
004017FE . 2C 37 sub al, 37
00401800 > 88040A mov byte ptr [edx+ecx], al ; 将对应的数字或者大写字母转换成整数保存('0' -> 0;'A' -> A)
00401803 . 46 inc esi
00401804 . 41 inc ecx
00401805 . 83FE 10 cmp esi, 10
00401808 .^ 7C DE jl short 004017E8
0040180A . BA 02000000 mov edx, 2
0040180F . 81EA 59145700 sub edx, 00571459
00401815 . 8955 FC mov dword ptr [ebp-4], edx
00401818 . 33C0 xor eax, eax
0040181A . BA 03000000 mov edx, 3
0040181F . 81EA 59145700 sub edx, 00571459
00401825 . 53 push ebx
00401826 . 8D48 01 lea ecx, dword ptr [eax+1]
00401829 . 8955 F8 mov dword ptr [ebp-8], edx
0040182C . 57 push edi
0040182D . 8D49 00 lea ecx, dword ptr [ecx]
00401830 > 8BF8 mov edi, eax
00401832 . 83E7 01 and edi, 1
00401835 . 75 1C jnz short 00401853
00401837 . 8A90 58145700 mov dl, byte ptr [eax+571458] ; 取出一个字节
0040183D . C0E2 04 shl dl, 4 ; 左移四位
00401840 . 8BF0 mov esi, eax
00401842 . D1EE shr esi, 1
00401844 . 8896 58145700 mov byte ptr [esi+571458], dl ; 保存
0040184A . EB 19 jmp short 00401865
0040184C > 32C0 xor al, al
0040184E . 5E pop esi
0040184F . 8BE5 mov esp, ebp
00401851 . 5D pop ebp
00401852 . C3 retn
00401853 > 8BD0 mov edx, eax
00401855 . D1EA shr edx, 1
00401857 . 8DB2 58145700 lea esi, dword ptr [edx+571458]
0040185D . 8A90 58145700 mov dl, byte ptr [eax+571458]
00401863 . 0016 add byte ptr [esi], dl
00401865 > 8A98 59145700 mov bl, byte ptr [eax+571459] ; 再取出一个字节
0040186B . 8BD1 mov edx, ecx
0040186D . 83E2 01 and edx, 1
00401870 . 8BF1 mov esi, ecx
00401872 . 75 0D jnz short 00401881
00401874 . C0E3 04 shl bl, 4
00401877 . D1EE shr esi, 1
00401879 . 889E 58145700 mov byte ptr [esi+571458], bl
0040187F . EB 0E jmp short 0040188F
00401881 > D1EE shr esi, 1
00401883 . 009E 58145700 add byte ptr [esi+571458], bl ; 和前一次运算后的结果相加
00401889 . 8DB6 58145700 lea esi, dword ptr [esi+571458]
0040188F > 8B75 FC mov esi, dword ptr [ebp-4]
00401892 . 8A98 5A145700 mov bl, byte ptr [eax+57145A]
00401898 . 8DB406 591457>lea esi, dword ptr [esi+eax+571459]
0040189F . 85FF test edi, edi
004018A1 . 75 0D jnz short 004018B0
004018A3 . C0E3 04 shl bl, 4
004018A6 . D1EE shr esi, 1
004018A8 . 889E 58145700 mov byte ptr [esi+571458], bl
004018AE . EB 0E jmp short 004018BE
004018B0 > D1EE shr esi, 1
004018B2 . 009E 58145700 add byte ptr [esi+571458], bl
004018B8 . 8DB6 58145700 lea esi, dword ptr [esi+571458]
004018BE > 85D2 test edx, edx
004018C0 . 75 1D jnz short 004018DF
004018C2 . 8A90 5B145700 mov dl, byte ptr [eax+57145B] ; 再取出一个字节
004018C8 . 8B75 F8 mov esi, dword ptr [ebp-8]
004018CB . C0E2 04 shl dl, 4 ; 左移四位
004018CE . 8DB406 591457>lea esi, dword ptr [esi+eax+571459]
004018D5 . D1EE shr esi, 1
004018D7 . 8896 58145700 mov byte ptr [esi+571458], dl ; 保存
004018DD . EB 1E jmp short 004018FD
004018DF > 8B55 F8 mov edx, dword ptr [ebp-8]
004018E2 . 8DB402 591457>lea esi, dword ptr [edx+eax+571459]
004018E9 . 8A90 5B145700 mov dl, byte ptr [eax+57145B] ; 再取出一个字节
004018EF . D1EE shr esi, 1 ; 右移一位
004018F1 . 0096 58145700 add byte ptr [esi+571458], dl ; 加上前一次运算后的结果
004018F7 . 8DB6 58145700 lea esi, dword ptr [esi+571458]
004018FD > 83C1 04 add ecx, 4
00401900 . 83C0 04 add eax, 4
00401903 . 83F9 11 cmp ecx, 11
00401906 .^ 0F8C 24FFFFFF jl 00401830
0040190C . B8 58145700 mov eax, 00571458
00401911 . 5F pop edi
00401912 . 8D50 01 lea edx, dword ptr [eax+1]
00401915 . 5B pop ebx
00401916 > 8A08 mov cl, byte ptr [eax] ; 取出一个字节
00401918 . 40 inc eax
00401919 . 84C9 test cl, cl ; 判断是否是0
0040191B .^ 75 F9 jnz short 00401916
0040191D . 2BC2 sub eax, edx ; 得到00字节的偏移
0040191F . 83F8 08 cmp eax, 8 ; 和8比较
00401922 . 0f95c0 setne al ; 不相等则返回1 相等返回0
00401925 . 5E pop esi
00401926 . 8BE5 mov esp, ebp
00401928 . 5D pop ebp
00401929 . C3 retn
分析易知,这个函数的作用就是将输入的十六进制文本转换成字节集的形式,然后判断00字节的偏移是否是8,如果是8则返回0。
返回后,程序会判断这个函数的返回值,如果返回1则激活[确定]按钮,返回0则不激活[确定]按钮
0x03 寻找按钮事件
由刚刚分析的十六进制文本转换成字节集的函数可以得知,结果被存放在00571458处。给00571458下硬件访问断点,大小是1。然后点击[确定]按钮。在0040193A处断下。接着这个函数往下分析。
这段函数再判断00字节的偏移是不是8,如果是8则成功,否则触发异常。
这个和之前那个十六进制文本转换字节集的函数的验证结果是矛盾的。
那个函数判断00字节的偏移是否是8,如果是8则不会解锁[确定]按钮。但是在[确定]的按钮事件里又判断这个偏移是否是8,如果是8则成功,不是8则触发异常。
说明这个是一个烟雾弹。不是真正的算法。
0x04 分析真正的算法
虽然按钮事件是烟雾弹,但是我猜测这个字节集绝对会用上,因此我没有删掉这个硬件断点。
再按F9,即可来到真正的算法函数。看来我的猜想是正确的。
来到00401987处,找到函数头00401970即可。
用IDA载入,发现算法函数中有调用一个call,然后用转换成十六进制的Key去解一个方程组,八元一次方程组。
先解这个八元一次方程组,我手解的时候发现后面几个字节是除不开的。。。于是只好写个脚本来跑。
代码如下:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int a[8];//77 33 31 6C 64 30 6E 65
for(a[0] = 0;a[0] <= 255;a[0]++)
{
for(a[1] = 0;a[1] <= 255;a[1]++)
{
for(a[2] = 0;a[2] <= 255;a[2]++)
{
for(a[3] = 0;a[3] <= 255;a[3]++)
{
if((unsigned __int8)(a[3] + a[2] + a[1] + a[0]) != 71)
continue;
if((unsigned __int8)a[0] != (unsigned __int8)a[1] + 68)
continue;
if((unsigned __int8)a[1] != (unsigned __int8)a[2] + 2)
continue;
if((unsigned __int8)a[2] != (unsigned __int8)a[3] - 59)
continue;
//77 33 31 6C
for(int i = 0;i < 4;i++)
printf("%0.02X ",a[i]);
goto NextWhile;
}
}
}
}
NextWhile:
for(a[4] = 0;a[4] <= 255;a[4]++)
{
for(a[5] = 0;a[5] <= 255;a[5]++)
{
for(a[6] = 0;a[6] <= 255;a[6]++)
{
for(a[7] = 0;a[7] <= 255;a[7]++)
{
if((unsigned __int8)(a[7] + a[6] + a[5]) != 3)
continue;
if((unsigned __int8)a[6] != (unsigned __int8)a[4] + 10)
continue;
if((unsigned __int8)a[6] != (unsigned __int8)a[7] + 9)
continue;
if((unsigned __int8)a[4] != (unsigned __int8)a[5] + 52)
continue;
//64 30 6E 65
for(int i = 4;i < 8;i++)
printf("%0.02X ",a[i]);
goto Next;
}
}
}
}
Next:
puts("");
system("pause");
return 0;
}
运行结果如图所示:
由此可以得知,正确Key的十六进制形式应为77 33 31 6C 64 30 6E 65
0x05 重点函数00401970的逆向
函数代码如图所示:
这段算法是Rijndael算法中的S-Box变换。
Flag只有16个字节,每个字节都是从00~FF的,因此我用的枚举。
方式就是一个字节一个字节枚举,和刚刚的解方程的代码的枚举方式差不多。
为了节省时间,我写了一个DLL来做这些工作,然后注入进去枚举Key,得到正确的Key后用信息框提示。
代码如下:
Hex函数的功能就是将十六进制的字节(00~FF)转换成文本。
将这些代码编译成一个DLL,然后注入到CM的进程中即可。得到结果如下:
拿到Flag:C7C536FC625CEFCD
放到原版CM中验证一下,可以通过。
全文完。