160CM-024
1. 爆破
由于能搜索到关键字符串,爆破还是比较简单的,将4012E9的代码“je short 004012D9”改成“jmp short 00401301”就可以了。
2. 算法分析
由于这是汇编语言编写的程序,IDA分析只能看到三个用户函数,看不出算法函数,只能汇编代码硬杠了。
4012DD到4012E3部分的代码是对4011EC到4012E3地址范围内的数据每四个字节进行异或计算,最后和0xAFFCCFFB进行比较是否相等。由于4011EC到4012E3为代码段,正常计算结果应该是固定的,但是程序里面有一处对0x4012D9进行的写入操作,该部分写入的数据会影响最后的异或计算结果。
00401278 . 50 push eax ; |pSuccess = 00002274
00401279 . 6A 64 push 0x64 ; |ControlID = 64 (100.)
0040127B . FF35 50314000 push dword ptr ds:[0x403150] ; |hWnd = 000B07C0 ('TEXme v2.0',class='CTEX')
00401281 . E8 BC010000 call <jmp.&USER32.GetDlgItemInt> ; 获取数字serial,存入eax
00401286 . 837D FC 00 cmp dword ptr ss:[ebp-0x4],0x0
0040128A . 74 5F je short Chafe_2.004012EB
0040128C . 50 push eax
0040128D . 6A 14 push 0x14 ; /Count = 14 (20.)
0040128F . 68 6C314000 push Chafe_2.0040316C ; |Buffer = Chafe_2.0040316C
00401294 . FF35 54314000 push dword ptr ds:[0x403154] ; |hWnd = 003A07CA (class='Edit',parent=000B07C0)
0040129A . E8 AF010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
0040129F . 85C0 test eax,eax
004012A1 . 74 48 je short Chafe_2.004012EB
004012A3 . A1 0B304000 mov eax,dword ptr ds:[0x40300B] ; 算法开始 0x58455443
004012A8 . BB 6C314000 mov ebx,Chafe_2.0040316C ; ASCII "abcde" :name
004012AD > 0303 add eax,dword ptr ds:[ebx] ; 依次移位4字节相加
004012AF . 43 inc ebx
004012B0 . 81FB 7C314000 cmp ebx,Chafe_2.0040317C ; 一直加到字符串存储空间尾部(20-4次)
004012B6 .^ 75 F5 jnz short Chafe_2.004012AD
004012B8 . 5B pop ebx
004012B9 . 03C3 add eax,ebx ;ebx为入栈的serial数值
004012BB . 3105 D9124000 xor dword ptr ds:[0x4012D9],eax ; 与4012D9异或,结果存入4012D9,4012D9的初始值为00584554
004012C1 . C1E8 10 shr eax,0x10 ;右移位16bit,取高16位
004012C4 . 66:2905 D9124>sub word ptr ds:[0x4012D9],ax ;减去ax(高16位的数值)
004012CB . BE EC114000 mov esi,Chafe_2.004011EC ;esi起始地址,到40122A,每4字节异或
004012D0 . B9 3E000000 mov ecx,0x3E
004012D5 . 33DB xor ebx,ebx
004012D7 . EB 04 jmp short Chafe_2.004012DD
004012D9 > CC int3
004012DA 2E db 2E ; CHAR '.'
004012DB 2B db 2B ; CHAR '+'
004012DC 22 db 22 ; CHAR '"'
004012DD > AD lods dword ptr ds:[esi] ;获取后esi+4
004012DE . 33D8 xor ebx,eax
004012E0 . 49 dec ecx
004012E1 .^ 75 FA jnz short Chafe_2.004012DD
004012E3 . 81FB FBCFFCAF cmp ebx,0xAFFCCFFB
004012E9 ^ 74 EE je short Chafe_2.004012D9
name输入abcde,serial输入123456,在4012E9处下断(这里需要特别注意,计算异或数据结果时不能在4011EC到4012E3地址范围内设置断点,因为设置断点后调试器会更换中断地址首字节为CC,这将影响计算结果。我就是在这里被坑了差不多一天,算完的serial在调试环境下有时可以,有时不对,在4012D9内容相同的情况下有时最终的异或结果是AFFCCFFB,有时又不是),可以得到4011EC到4012E3所有数据异或后的结果为==D7BA9681==。由于4012D9的数值为222C60B2,按四字节对齐,其影响的数据包括两个WORD,分别为2C60B204和D833AD22。由于A与B的异或结果和B与A的异或结果是一样的,因此2C60B204和D833AD22的异或结果也等于2C60B222(可变部分)和D833AD04(不变部分)的异或结果。
假设:
A=D7BA9681;
B=2C60B222;
C=D833AD04;
D=AFFCCFFB;
4011EC到4012E3程序段的其余部分的异或结果为F;
则有F ^ B ^ C =A,所以F=A ^ B ^ C。
假设可变部分的正确结果为G,则D=F ^G ^ C,
因此G=D ^ C ^ F
=D ^C ^(A ^ B ^ C)
=D ^ A ^ B
因此可变部分的正确结果
G=AFFCCFFB ^ D7BA9681 ^2C60B222 =5426EB58
因此,正确的4012D9的数值应为585426EB。
在4012D7处下断点,断下后,将4012D9的数据修改成585426EB,可以看到4012D9代号行的跳转调到yes!提示字符位置了,然后取消4012D7处的断点(不取消计算结果会不对),选中4012E9这一行,按F4,可以看到ebx的数值与0xAFFCCFFB相等,满足跳转到4012D9行的条件。
接下来就要分析4012D9地址数据的计算算法了,算法首先用一个初始值0x58455443与输入的name字符串依次取4字节的数字进行相加,然后地址加1,再继续相加,一直到20(和上一题类似),假设计算结果为temp2,则
0x585426EB=(temp2 + sn) ^ 0x00584554 - ((temp2 + sn) >> 16)
假设key=temp2 + sn,则
0x585426EB=key ^ 0x00584554 - key>>16
假设key的高16位为key_high,低16位为key_low,则
0x5854 0x10000 + 0x26EB = (key_high ^ 0x58) 0x10000 + key_low ^ 0x4554 - key_high
因此上述等式也可以拆分成两个等式,即
0x5854 = key_high ^ 0x58 和 0x26EB = key_low ^ 0x4554 - key_high
所以 key_high = 0x5854 ^ 0x58 = 0x580C
key_low = ( 0x26EB + 0x580C ) ^ 0x4554 = 3BA3
最终可得,key= 0x580C3BA3
所以,sn = 0x580C3BA3 - temp2
编写算法程序如下:
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <math.h>
int _tmain(int argc, _TCHAR* argv[])
{
char name[20];
int temp,temp2,key,length;
int i;
UINT32 code;
printf("请输入用户名(长度20以内):");
scanf_s("%s", name, 20);
length = strlen(name);
temp2 = 0x58455443;
key = 0x580C3BA3;
for (i = 0; i < 19; i++)
{
if (i >= length)
{
name[i] = 0;
}
}
for (i = 0; i < 16; i++)
{
temp = name[i] + name[i + 1] * 0x100 + name[i + 2] * 0x10000 + name[i + 3] * 0x1000000;
temp2 = temp2 + temp;
//printf("16进制temp2 %d为:%x\n", i, temp2);
}
code = key-temp2;
printf("序列号为:%u\n", code);
system("pause");
return 0;
}
运行,输入用户名abcde,计算得到序列号如下
将用户名和序列号输入程序中,验证成功。
3. 总结
这是一道有一些难度的算法题,需要对异或运算的特点有比较清晰的理解。另外,这个程序里面用lods装载指令内容进行校验,校验成功才能正常跳转,这算是程序自校验的一种方法。这里面有一个比较大的坑就是设置断点会影响lods装载的内容,即“设置断点后调试器会更换中断地址首字节为CC”,这个是看了PK大佬的教程才明白的。