x64dbg-算法分析一
9unk Lv5

目标程序

程序下载:CRACKME

解压密码:9unk

任务目标:分析程序算法部分,并写出注册机程序。

观察程序

45.jpg

算法分析

首先加载程序到入口点

1.jpg

“Ctrl+N” 查看程序的 API,并对 GetDlgItemTextA 设置断点

2.jpg

我们继续运行程序,此时程序停在断点处

3.jpg

我们再回头回顾一下,GetDlgItemTextA 函数的参数值和返回值。

5.jpg
6.jpg

“Ctrl+F9” 运行程序到返回,看到此时程序获取了 name “9unk”。我们可以注意到 eax 的返回值是 4

4.jpg

“F8” 单步步过,查看程序对 name 是如何处理的。

7.jpg


1
2
3
cmp eax,1   // eax - 1
mov dword ptr ss:[ebp+0x10], 0x3EB // 将 3EB 传送到 [ebp+10]
jb 0x004012A1 // 如果 eax < 1 就跳转

很明显上面这段就是在判断你有没有输入 Name,如果没有就跳转报错。

我们继续按 “F9” 运行到下一个断点,然后再按 “Ctrl+F9” 运行到返回。此时可以看到这里获取了 password,eax 返回值为 6

8.jpg

从程序的角度出发,程序会获取用户输入的错误的序列号与正确的序列号进行比较,所以我们可以对错误的序列号设置内存访问断点,看看程序哪些地方使用了。
9.jpg

继续 “F9” 运行程序,此时程序停在了如下位置

我们开始分析程序

1
mov al, byte ptr ds:[esi]   // 把 esi 的第一个字节传给 al

11.jpg

1
2
test al, al     // 逻辑 and 运算,判断 al 是否等于零。也就是判断输入值是否为空
je 0x0040139C // 值为空就跳出循环

可以看到如值为空,程序就会跳转到如下 call 指令处

12.jpg

1
2
3
4
cmp al, 0x41    // al的值 和 0x41(字母 A)进行比较
jb 0x004013AC // al的值 低于 0x41 就跳转
cmp al, 0x5A // al的值 和 0x5A(字母 Z)进行比较
jae 0x00401394 // al的值 高于等于 0x5A 就跳转

可以看到值小于字母 A 就会跳转到如下位置。可以看到这是一个 MessageBox 错误弹窗

13.jpg

如果值大于等于字母 Z 就会跳到如下位置。可以看到这是一个正常的循环。

14.jpg

从上面的这两个跳转可以判断,我们输入的 name 必须是小写的字母。

这里可以看到我输入的 9(0x39)肯定小于 0x41,所以会跳转报错,如下图示所示

15.jpg

我们重新输入,再进行测试。按照之前的步骤,单步步过到 call 指令处

16.jpg

按 “F7” 步入 call 指令,可以看到这个 call 指令只进行了两个操作。

17.jpg

1
cmp al,20   // al - 20

可以看到 al 寄存器的值变成了 0x4A(字母 J)

18.jpg

1
mov byte ptr ds:[esi], al   // 把原来 al 的值替换成 0x4A

可以看到这里把小写字母 j,转换成大写字母 J

20.jpg

我们继续执行,可以看到这里第一个 name 字符已经结束循环了

21.jpg

我们继续运行直到所有字符循环结束,跳到如下位置

22.jpg

继续 “F7” 步入,查看 call 指令

23.jpg

1
2
xor edi, edi    // edi 置零
xor ebx, ebx // ebx 置零

24.jpg

1
2
3
mov bl, byte ptr ds:[esi]   // 将 esi 存入 bl 中
test bl, bl // 测试 bl 是否等于零
je 0x004013D1 // 如果等于零就跳出循环

可以看到此时的 bl 肯定不会等于零,所以就不会跳转

25.jpg

1
2
3
add edi, ebx    // edi = ebx + edi,此时 edi = 0x4A
inc esi // esi + 1
jmp 0x004013C6 // 继续循环

26.jpg

继续单步执行,可以看到 esi 存储着 “JUNK” 的累积值 0x138

27.jpg

把上面的 call 指令运行结束后,我们继续运行程序

29.jpg

1
2
3
xor edi, 0x5678     // edi 异或 0x5678
mov eax, edi // eax = edi
jmp 0x004013C1 // 无条件跳转

30.jpg

我们继续运行程序到入如下位置

31.jpg

继续步入到 call 指令

32.jpg

1
2
3
4
// 置零操作
xor eax, eax
xor edi, edi
xor ebx, ebx

33.jpg

1
2
3
4
5
mov esi, dword ptr ss:[esp+0x4]     // 将错误的序列号传到 esi
mov al, 0xA // 将 0xA 传入 al 寄存器
mov bl, byte ptr ds:[esi] // 将 esi 的第一个字符传到 bl 寄存器
test bl, bl // 测试 bl 是否等于零
je 0x004013F5 // bl 等于零就跳出循环

34.jpg

1
2
3
sub bl, 0x30    // bl - 30
imul edi, eax // edi = edi * eax(值存储在edi,之前的注释写错了);此时 edi 刚初始化等于零;edi = 0 * 0xA = 0
add edi, ebx // edi = edi + ebx;edi = 0 + 1 = 1

35.jpg

36.jpg

37.jpg

1
2
3
// 结束第一个循环
inc esi
jmp 0x004013E2

我们继续第二次循环查看结果

1
2
3
// 原 edi=1,eax 为固定值 A
imul edi, eax // edi = 0x1 * 0xA = 0xA
add edi, ebx // edi = 2 + 0xA = 0xc

38.jpg

39.jpg

循环结束后查看 edi 的值

40.jpg

概括来说上面的计算就是:把十六进制 “123456” 序列号转换成十进制 “123456”

下一条指令是 EDI 异或 1234,然后再传给 ebx

41.jpg

可以看到下面有个比较和跳转指令,这里是把 name xor 5678 和 serial xor 1234 的值进行比较。

42.jpg

所以我们现在以 junk 这个 name 先计算出它的 serial

1
2
3
5740 = serial xor 1234  // 这是十六进制运算
serial = 5740 xor 1234
serial 再转换成十进制

43.jpg

44.jpg

注册机编写

  1. name - 0x20;然后再一次把每个字符串的值相加;最后再 xor 0x5678
  2. serial = name xor 0x1234
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
程序名:CrackMe.c,此程序是 CrackMe v1.0 的注册机
作者:9unk 日期:20201226
*/

#include <stdio.h>
#include <string.h>
#define size 21 // 定义一个 size 常量
int main()
{
char str[size];
int i,n=0,sum=0,name=0,serial=0;

memset(str,0,sizeof(str)); // 定义初始化数据

printf("输入用户名:");
scanf("%s",&str); // 获取用户输入

for (i=0;i<strlen(str);i++)
{
n=str[i]; // 将数组转换成十进制
n=n-0x20; // 将数值值减 0x20(十六进制)
sum=sum+n; // 将值存到 sum(十进制) 中
}
name=sum ^ 0x5678; // name xor 0x5678
serial= name ^ 0x1234; // serial xor 0x1234
printf("你的 serial 是:%d\n",serial);
return 0;
}

编程比较烂,就先这样,能用就行。

reference

《使用 Ollydbg 从零开始 Cracking》

  • 本文标题:x64dbg-算法分析一
  • 本文作者:9unk
  • 创建时间:2020-12-27 15:26:28
  • 本文链接:https://9unkk.github.io/2020/12/27/x64dbg-suan-fa-fen-xi-yi/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!