用PEID查一下,发现是TASM / MASM写的程序
用OD打开可以发现基本上都有注释,而且代码很短
状态栏显示Your serial is not valid.
运行程序,填入名字liuyufanny,密码123456789
程序只有一个About按钮,所以我们查找一下字符串,很容易就可以找到
004012B6 /74 16 je short Chafe_1.004012CE
004012B8 |. 68 65304000 push Chafe_1.00403065 ; /Your serial is not valid.
004012BD |. FF35 7C314000 push dword ptr ds:[0x40317C] ; |hWnd = 0052017C ('Your serial is not valid.',class='Edit',parent=005801E6)
004012C3 |. E8 66020000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
004012C8 |. 33C0 xor eax,eax
004012CA |. C9 leave
004012CB |. C2 1000 retn 0x10
004012CE |> 68 7F304000 push Chafe_1.0040307F ; /YES! You found your serial!!
004012D3 |. FF35 7C314000 push dword ptr ds:[0x40317C] ; |hWnd = 0052017C ('Your serial is not valid.',class='Edit',parent=005801E6)
004012D9 |. E8 50020000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
看到第一行je short Chafe_1.004012CE直接跳到正确的选项,所以在这一行断一个点,断到以后运行,还会继续断到此处,可以确定程序是有循环判断输入的密码是否正确。
再次断到这一行,把je改成jmp并取消断点,发现状态栏变成了YES! You found your serial!!
程序爆破成功。
这个程序的爆破很简单,但我们还需要找出他的密码,才算圆满的变成任务。
先把程序Ctrl+F12重新启动,并运行程序,填入用户名和密码。
下面找到读取输入用户名和密码的代码:
00401475 . 6A 00 push 0x0 ; /IsSigned = FALSE
00401477 . 8D45 FC lea eax,dword ptr ss:[ebp-0x4] ; |
0040147A . 50 push eax ; |pSuccess = 00BC614E
0040147B . 6A 64 push 0x64 ; |ControlID = 64 (100.)
0040147D . FF35 70314000 push dword ptr ds:[0x403170] ; |hWnd = 00750276 ('TEXme v1.0',class='TEXcls')
00401483 . E8 64000000 call <jmp.&USER32.GetDlgItemInt> ; \GetDlgItemInt
00401488 . A3 88314000 mov dword ptr ds:[0x403188],eax ; eax保存密码的十六进行数据,并保存到0x403188
0040148D . 837D FC 00 cmp dword ptr ss:[ebp-0x4],0x0 ; 如果填入了纯数字,则[ebp-0x4]为1,否则为0
00401491 74 07 je short Chafe_1.0040149A
00401493 . 8005 66314000>add byte ptr ds:[0x403166],0x4 ; 0x403166内存数据加上0x4
0040149A > C9 leave
0040149B . C3 retn
00401069 . 6A 14 push 0x14 ; /Count = 14 (20.)
0040106B . 68 8C314000 push Chafe_1.0040318C ; |Buffer = Chafe_1.0040318C
00401070 . FF35 74314000 push dword ptr ds:[0x403174] ; |hWnd = 007501F8 (class='Edit',parent=00750276)
00401076 . E8 7D040000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
0040107B . B9 14000000 mov ecx,0x14
00401080 . 2BC8 sub ecx,eax
00401082 . 8DB8 8C314000 lea edi,dword ptr ds:[eax+0x40318C]
00401088 > C607 00 mov byte ptr ds:[edi],0x0 ; 这个循环是保证名字字符串长度为0x14,不足0x14的部分用0填充
0040108B . 47 inc edi ; Chafe_1.00403196
0040108C . 49 dec ecx
0040108D .^ 75 F9 jnz short Chafe_1.00401088
0040108F . 85C0 test eax,eax
00401091 . 74 10 je short Chafe_1.004010A3
00401093 . 8005 66314000>add byte ptr ds:[0x403166],0x4 ; 0x403166内存加上0x4
0040109A . C605 68314000>mov byte ptr ds:[0x403168],0x0 ; 0x403168设置为0x0
004010A1 . EB 06 jmp short Chafe_1.004010A9
004010A3 > 8825 66314000 mov byte ptr ds:[0x403166],ah
004010A9 > C9 leave
004010AA . C3 retn
这两个读取名字和密码的地方,有两个特殊的地方,就是对0x403166和0x403168两个地址进行了修改。特别是读取名字以后,把0x403168改成0了。我们先断到读取名字并修改0x403168的下面,地址是0x4010A1。这个时候查看内存地址0x403168,值为0。可以注意到,修改之前,值是为0x10的,所以我们在这里设置一个内存写入断点,然后运行程序,断到了0x401370。
记得把内存断点删了。
00401361 . 8D3D 8C314000 lea edi,dword ptr ds:[0x40318C] ; 保存着用户名的地址
00401367 . 0FBE05 683140>movsx eax,byte ptr ds:[0x403168] ; 循环记数i
0040136E . 03F8 add edi,eax ; 取名字的第i个字符 name[i]
00401370 . FE05 68314000 inc byte ptr ds:[0x403168] ; ++i
00401376 . A1 88314000 mov eax,dword ptr ds:[0x403188] ; 密码十六进制 num
0040137B . 8B25 A0314000 mov esp,dword ptr ds:[0x4031A0]
00401381 . 40 inc eax ; num += 1
00401382 . FF05 88314000 inc dword ptr ds:[0x403188] ; num += 1 这个和上面不同之处在于上面是修改寄存器,下面是修改内存,其实是同一个操作
00401388 . 3307 xor eax,dword ptr ds:[edi] ; num ^= name 这个xor是从name+i内存开始,取4个字节转换成整形
0040138A . A3 88314000 mov dword ptr ds:[0x403188],eax
0040138F . 803D 68314000>cmp byte ptr ds:[0x403168],0x10
00401396 . 75 07 jnz short Chafe_1.0040139F
00401398 . 8005 66314000>add byte ptr ds:[0x403166],0x4 ; 0x403166内存加上0x4
0040139F > C9 leave
004013A0 . C3 retn
这里就是通过用户名计算密码的部分代码,为什么说部分呢,下面继续分析。
00401299 |. 0FBE05 663140>movsx eax,byte ptr ds:[0x403166]
004012A0 |. 3A05 67314000 cmp al,byte ptr ds:[0x403167]
004012A6 |. 75 06 jnz short Chafe_1.004012AE
004012A8 |. 33C0 xor eax,eax
004012AA |. C9 leave
004012AB |. C2 1000 retn 0x10
004012AE |> A2 67314000 mov byte ptr ds:[0x403167],al
004012B3 |. 83F8 10 cmp eax,0x10
004012B6 |. 74 16 je short Chafe_1.004012CE
最后一个就是跳到显示正确的关键跳,前面可以看出,取的是0x403166的值,然后与0x10比较,你运行并断点到这几次,你可以发现,0x403166的值从0x4到0x8到0xC就是到不了0x10。看看上面的代码,读取名字的时候加了4,读取密码的时候加了4,计算部分密码的时候加了4,所以可以判断比较密码的时候还会加4。
0040149C . A1 88314000 mov eax,dword ptr ds:[0x403188] ; 计算出来的部分密码
004014A1 . 05 78241109 add eax,0x9112478 ; 部分密码加上0x9122478
004014A6 . 85C0 test eax,eax
004014A8 . 75 09 jnz short Chafe_1.004014B3 ; eax为0时,就成功了!
004014AA . 8005 66314000>add byte ptr ds:[0x403166],0x4 ; 最后一个加4
004014B1 . EB 07 jmp short Chafe_1.004014BA
004014B3 > C605 66314000>mov byte ptr ds:[0x403166],0x0
004014BA > 8B25 A0314000 mov esp,dword ptr ds:[0x4031A0]
004014C0 . C9 leave
004014C1 . C3 retn
所以算法就出来了。总结一下算法如下:
取密码的十六进制,从名字的第一个字节开始,每次强制取名字内存四个字节强制转换成uint类型,密码的值加上1,然后与密码xor,并保存为新的密码。重复16次。得到新的密码加上0x9112478最终结果为0x0则正确。
最后得出密码为:2229908868
C++代码如下:
//Chafe.1.exe
int main()
{
char name[20];
memset(name, 0, 20);
std::cout << "input name:";
std::cin >> name;
unsigned int answer = 0x00000000 - 0x9112478;
for (int i = 15; i >= 0; --i)
{
unsigned int temp = *reinterpret_cast<unsigned int*>(name + i);
answer ^= temp;
answer -= 1;
}
std::cout << answer << std::endl;
return 0;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)