本帖最后由 pk8900 于 2017-12-10 17:30 编辑
周末,继续研究 【适合破解新手的160个crackme练手】,本文内容为第24个CrackMe: Chafe.2.exe ,收获不少。
这个和第23个是一个作者所写,有了分析第23个Crackme的经验(帖子:https://www.52pojie.cn/thread-670504-1-1.html),一点也不敢轻视,分析过程很顺利,但到了编写注册步骤就用的时间多了,主要是C++刚学的原因,加上我把VS2010换了2013,总会出一些莫名的错误及警告,昨晚才完成了注册机的编写,今天有时间,就发贴把我分析的过程和注册机编写和大家一起分享。
之前论坛里有一篇关于这个CrackME的帖子,我写完注册机后搜索论坛的帖子对照了一下,帖子地址:https://www.52pojie.cn/thread-601284-1-1.html,过程写的比较细,但也有未尽之处,大家可以一并参考。
【crackme简介】
下载地址:http://pan.baidu.com/share/link?shareid=541269&uk=4146939145
MASM32 / TASM32编写,无壳,是一个用户名+序列号验证方式,界面第三个文本框。显示:Your serial is not valid.成功后会显示成功字样。
分析工具:X64dbg
【crackme截图】
【算法分析过程】
有了分析chafe.1的经验,这个很快就找到了关键算法部分,代码很简短,算法部分下面跟着就是判断及提示成功或失败的信息。通过找到API调用,<user32.GetDlgItemInt>、<user32.GetWindowTextA>分别用来获取输入的用户名及注册码。
[Asm] 纯文本查看 复制代码 0040127B | FF 35 50 31 40 00 | push dword ptr ds:[403150] |
00401281 | E8 BC 01 00 00 | call <chafe.2.GetDlgItemInt> | 获取序列号
00401286 | 83 7D FC 00 | cmp dword ptr ss:[ebp-4], 0 |
0040128A | 74 5F | je chafe.2.4012EB |
0040128C | 50 | push eax |
0040128D | 6A 14 | push 14 |
0040128F | 68 6C 31 40 00 | push chafe.2.40316C |
00401294 | FF 35 54 31 40 00 | push dword ptr ds:[403154] |
0040129A | E8 AF 01 00 00 | call <chafe.2.GetWindowTextA> | 获取用户名
0040129F | 85 C0 | test eax, eax |
004012A1 | 74 48 | je chafe.2.4012EB |
004012A3 | A1 0B 30 40 00 | mov eax, dword ptr ds:[40300B] | 0040300B:"CTEX"
004012A8 | BB 6C 31 40 00 | mov ebx, chafe.2.40316C | 用户名:40316C:"pk8900"
004012AD | 03 03 | add eax, dword ptr ds:[ebx] | 用户名前四字节+CTEX
004012AF | 43 | inc ebx |
004012B0 | 81 FB 7C 31 40 00 | cmp ebx, chafe.2.40317C |
004012B6 | 75 F5 | jne chafe.2.4012AD | 循环加到末尾 eax
004012B8 | 5B | pop ebx | ebx 12345678 假码
004012B9 | 03 C3 | add eax, ebx | 再加上序列号
004012BB | 31 05 D9 12 40 00 | xor dword ptr ds:[4012D9], eax | 与 "TEX" 异或,改变004012D9
004012C1 | C1 E8 10 | shr eax, 10 | EAX 右移位 0x10,相当于取高位
004012C4 | 66 29 05 D9 12 40 00 | sub word ptr ds:[4012D9], ax | 4012D9 减去取的高位
004012CB | BE EC 11 40 00 | mov esi, chafe.2.4011EC | ??压入取异或代码首地址
004012D0 | B9 3E 00 00 00 | mov ecx, 3E | 次数为: 0x3E
004012D5 | 33 DB | xor ebx, ebx | 清空EBX (ebx用于累计异或结果)
004012D7 | EB 04 | jmp chafe.2.4012DD |
004012D9 | 54 | db 54 | ‘四字节为关键异或位置,跟据用户名及序列号变动
004012DA | 45 | db 45 | 原始代码:54 45 58 00 即“TEX”
004012DB | 58 | db 58 |
004012DC | 00 | db 0 |
004012DD | AD | lodsd | 取得双字节
004012DE | 33 D8 | xor ebx, eax | ebx与其异或
004012E0 | 49 | dec ecx |
004012E1 | 75 FA | jne chafe.2.4012DD | 循环判断 3E递减为0
004012E3 | 81 FB FB CF FC AF | cmp ebx, AFFCCFFB | 关键比较,条件:EBX==0xAFFCCFFB
004012E9 | 74 EE | je chafe.2.4012D9 |
004012EB | 68 59 30 40 00 | push chafe.2.403059 | 403059:"Your serial is not valid."
004012F0 | FF 35 5C 31 40 00 | push dword ptr ds:[40315C] |
004012F6 | E8 7D 01 00 00 | call <chafe.2.SetWindowTextA> |
004012FB | 33 C0 | xor eax, eax |
004012FD | C9 | leave |
004012FE | C2 10 00 | ret 10 |
00401301 | 68 73 30 40 00 | push chafe.2.403073 | 403073:"YES! You found your serial!!"
00401306 | FF 35 5C 31 40 00 | push dword ptr ds:[40315C] |
0040130C | E8 67 01 00 00 | call <chafe.2.SetWindowTextA> |
00401311 | 33 C0 | xor eax, eax |
00401313 | C9 | leave |
00401314 | C2 10 00 | ret 10 |
通过对以上代码下断并分析,程序的注册验证流程为:
0x01:GetDlgItemInt函数获取序列号,存于堆栈中
0x02:GetWindowTextA函数获取用户名,存于40316C
0x03:对用户名和序列号进行累加:初始值为:"CTEX"字节值:0x58455443,从用户名字串取字节值,每次偏移一个字节,取双字值进行累加,不论用户名实际长度,一直到40317C,也就是0x10(16)个,用户名变量空间是0x14(20)个。加完用户名后,出栈序列号,并与之累加(结果在EAX中)。
0x04:累加值EAX与关键较验数据[4012D9]异或,写入[4012D9],然后取EAX高位WORD值,加到[4012D9]里,到此[4012D9]被改动了2次。
0x05:将EBX清空,然后从内存地址[4011EC]开始,第次读入四个字节与EBX异或,EBX累加异或值,一直至内存地址[4012E4],这段正好是关键算法部分,也就是至代码004012E9 | 74 EE | je chafe.2.4012D9之前的这一段,所以进行异或累加过程中不能在范围内下F2断点,调试器会更换中断地址首字节为CC,这将有可能改变真正异或的结果,断点可以选在:004012E9,或下硬件执行断点调试(我也是上了当后才想明白的)。
0x06:将异或结果与固定值:0xAFFCCFFB比较,相同的话跳至关键检验字节,再跳到注册成功。
算法部分已分析明白,接下来就是想法做出注册机。
【制作注册机过程】
【难点】通过分析,最后比较的字符串来自对程序248字节的代码进行异或运算,得出异或和,与固定的字符进行对比,其中[4012D9]地址4字节是根据用户名和序列号进行一系列累加,异或,高低加至低位的运算得出,也就是说用户名或序列号每变动任意一个字符都会影响最终写入[4012D9]的值。还是给大家画个流程图吧,要不还真解释不清楚。
【第一步】
通过流程图,我们可以看出,只要[4012D9]地址被写入的值对,最终异或结果就会等于0xAFFCCFFB,正确是流程是EBX最初为0,分别与4011EC开始的0x3E个地址求异或和,也包括[4012D9]地址,最终等于0xAFFCCFFB,因为异或的可逆性,可以用0xAFFCCFFB分别与以上地址求异或和,去除[4012D9]地址,或将[4012D9]地址置0,得出的结果就应该是[4012D9]地址正确的值,经计算 实际应为0x585426EB)这样我们的第一步完成了。
第二步:跟据程序写入[4012D9]值的过程,推算出写之前EAX的值。
【第二步】
根据流程是:eax与[4012D9]"TEX"0x00584554异或,后将EAX的高位加到[4012D9]低位上,这部分是难点,我们可以先从高位推起,因为异或是按位操作,低位异或结果不会影响高位的,所以反推高位:0x585426EB取出高位:0x5854 与 对应的"TEX"0x00584554的高位0x0058异或,得出之前EAX高位是0x580C,接下来推算低位:取出26EB,应该加高位即0x580C,再和"TEX"0x00584554的低位异或,即算出EAX的低位,即(3BA3)。此时连接高低位:0x580C3BA3就是EAX的值了。
【第三步】
第三步相对简单了,从EAX中减去固定字串"CTEX"(0x58455443)与用户名移位累加的值,剩下的就是序列号的值了(此部分可以参照CrackME 023,异或改为累加)。
根据结果,用C++做注册机完整代码如下:
[C++] 纯文本查看 复制代码 #include<iostream>
using namespace std;
unsigned char * buff; //字符串缓冲区
unsigned long key; //保存代码校验部分可以通过的值
unsigned long nameValue;
unsigned long keyHight; //高位
unsigned long keyLow;
char * youname; //用户名
unsigned long serial_No; //序列号
unsigned char data[0x3E*4+1] = { 0x55, 0x8B, 0xEC, 0x83, 0xC4, 0xFC, 0x8B, 0x45, 0x0C, 0x83, 0xF8, 0x10, 0x75, 0x0D, 0x6A, 0x00, 0xE8, 0x6B, 0x02, 0x00, 0x00, 0x33, 0xC0, 0xC9,
0xC2, 0x10, 0x00, 0x83, 0xF8, 0x0F, 0x75, 0x0E, 0x8B, 0x45, 0x08, 0xE8, 0x18, 0x01, 0x00, 0x00, 0x33, 0xC0, 0xC9, 0xC2, 0x10, 0x00, 0x83, 0xF8,
0x01, 0x75, 0x06, 0x33, 0xC0, 0xC9, 0xC2, 0x10, 0x00, 0x3D, 0x11, 0x01, 0x00, 0x00, 0x0F, 0x85, 0xE7, 0x00, 0x00, 0x00, 0x8B, 0x45, 0x14, 0x3B,
0x05, 0x60, 0x31, 0x40, 0x00, 0x75, 0x1A, 0x6A, 0x00, 0x68, 0x96, 0x30, 0x40, 0x00, 0x68, 0xA7, 0x30, 0x40, 0x00, 0xFF, 0x75, 0x08, 0xE8, 0x17,
0x02, 0x00, 0x00, 0x33, 0xC0, 0xC9, 0xC2, 0x10, 0x00, 0x3B, 0x05, 0x58, 0x31, 0x40, 0x00, 0x74, 0x0C, 0x3B, 0x05, 0x54, 0x31, 0x40, 0x00, 0x0F,
0x85, 0xAE, 0x00, 0x00, 0x00, 0xC7, 0x05, 0xD9, 0x12, 0x40, 0x00, 0x54, 0x45, 0x58, 0x00, 0x6A, 0x00, 0x8D, 0x45, 0xFC, 0x50, 0x6A, 0x64, 0xFF,
0x35, 0x50, 0x31, 0x40, 0x00, 0xE8, 0xBC, 0x01, 0x00, 0x00, 0x83, 0x7D, 0xFC, 0x00, 0x74, 0x5F, 0x50, 0x6A, 0x14, 0x68, 0x6C, 0x31, 0x40, 0x00,
0xFF, 0x35, 0x54, 0x31, 0x40, 0x00, 0xE8, 0xAF, 0x01, 0x00, 0x00, 0x85, 0xC0, 0x74, 0x48, 0xA1, 0x0B, 0x30, 0x40, 0x00, 0xBB, 0x6C, 0x31, 0x40,
0x00, 0x03, 0x03, 0x43, 0x81, 0xFB, 0x7C, 0x31, 0x40, 0x00, 0x75, 0xF5, 0x5B, 0x03, 0xC3, 0x31, 0x05, 0xD9, 0x12, 0x40, 0x00, 0xC1, 0xE8, 0x10,
0x66, 0x29, 0x05, 0xD9, 0x12, 0x40, 0x00, 0xBE, 0xEC, 0x11, 0x40, 0x00, 0xB9, 0x3E, 0x00, 0x00, 0x00, 0x33, 0xDB, 0xEB, 0x04, 0x00, 0x00, 0x00,
0x00, 0xAD, 0x33, 0xD8, 0x49, 0x75, 0xFA, 0x81,0 };
void main024_1()
{
key = 0xAFFCCFFB; //初始化为最终判断值 0xAFFCCFFB
buff = data;
serial_No = 0;
youname = new char[250];
memset(youname, 0, 250);
for (int x = 0; x < 0x3E * 4; x += 4) //循环读入代码并与最终校验值异或累计
{
key = key ^ *(unsigned long *)&buff[x];
//cout << 0x3E - x / 4 << " key is:" << hex << key << " (" << *(unsigned long *)&buff[x] << ")" << endl;
}
key = (key << 24) + (key >> 8); //调整字节顺序
cout << "最终校验值:" << hex << key << endl;
cout << "enter name:";
gets(youname);
nameValue = 0x58455443; // 计算name值累加 0x58455443;//"CTEX";
unsigned long *t1=0;
for (int x =0 ; x < 0x10; x++)
{
t1 = (unsigned long *)&youname[x];
nameValue = nameValue + *t1;
}
keyHight = key >> 16; //取出高位
keyLow = key & 0x0000ffff; //取出低位
keyLow = keyLow +( keyHight^0x58);
key = (keyHight << 16) + keyLow;
key = key ^ 0x584554; //584554("CTE")
serial_No = key - (nameValue);
cout << "you key is:" << dec << serial_No << endl;
buff = NULL;
if (youname != NULL)
delete[] youname;
youname = NULL;
system("pause");
通过算法分析过程,可以发现EAX那个0x580C3BA3是固定值,因此注册机可以精简为如下代码:
[C++] 纯文本查看 复制代码 #include<iostream>
using namespace std;
unsigned long key; //保存代码校验部分可以通过的值
unsigned long nameValue;
char * youname; //用户名
unsigned long serial_No; //序列号
void main()
{
key = 0x580C3BA3; //初始化为最终判断值 0xAFFCCFFB
serial_No = 0;
youname = new char[250];
memset(youname, 0, 250);
cout << "enter name:";
gets(youname);
nameValue = 0x58455443; // 计算name值累加 0x58455443;//"CTEX";
unsigned long *t1 = 0;
for (int x = 0; x < 0x10; x++)
{
t1 = (unsigned long *)&youname[x];
nameValue = nameValue + *t1;
}
serial_No = key - (nameValue);
cout << "you key is:" << dec << serial_No << endl;
system("pause");
}
提供一组KEY,name:52pojie.cn serial:1454218211
整个分析及注册机编写流程这是这样了,也不知道大家能不能看明白,也不确定自己分析的准不准确,反正注册机算出的序列号是能用的。CrackMe有个小BUG,第一次输个长用户名,第二次输短的,只会在第一次输入的地址前部改写,不会清0后边的,所以这种情况判断的结果就未知了。
欢迎大家回贴交流,共同探讨。让我们共同从新手成为高手,也有可以和大牛一拼的实力。 |