吾爱破解2016安全挑战赛-第一题【炒冷饭】
本帖最后由 DCO 于 2018-8-21 23:12 编辑## 寻找关键点
CM部分代码被VM了,但是关键的注册信息校验函数没被V,下断bp GetDlgItemTextA,输入注册信息,跟一会就到(我也不知到我怎么来到这里的,差一点扔回收站)
```
0040286C|.52 push edx ;key
0040286D|.51 push ecx ;user_name
0040286E|.FFD0 call eax ;校验函数,返回1注册成功
```
直接使用PCHunter把这块内存给dump出来,方便后期静态分析
## 算法分析
把输入的key分为两部分
```
seg000:000004A0 loc_4A0: ; CODE XREF: sub_450+55↓j
seg000:000004A0 mov al,
seg000:000004A2 inc esi
seg000:000004A3 test al, al
seg000:000004A5 jnz short loc_4A0
seg000:000004A7 sub esi, ecx ; 计算注册码的长度key_len
seg000:000004A9 lea eax,
seg000:000004AC cmp eax, 31h ; '1'; 这里有这种诡异判断,主要是下面分为两次取key
seg000:000004AC ; 第一次取0x12长度
seg000:000004AC ; 第二处取key的后半部分,限制缓冲区的长度为0x32
seg000:000004AC ; ;
seg000:000004AF ja fail ; ja是比较两个无符号数的大小
seg000:000004AF ; 0x13 <= key_len <= 0x44
seg000:000004AF ; 否则注册失败
seg000:000004B5 push 12h
seg000:000004B7 push edi
seg000:000004B8 lea eax,
seg000:000004BB mov , 0
seg000:000004BF xorps xmm0, xmm0 ;清零
seg000:000004C2 mov word ptr , 0
seg000:000004C8 push 12h
seg000:000004CA push eax
seg000:000004CB movdquxmmword ptr , xmm0 ; 对数组buf_2进行清零
seg000:000004D0 call get_specified_len_key ; 功能:取指定长度的key存入到目标缓冲区
seg000:000004D0 ; 参数:
seg000:000004D0 ; arg3是指定的长度
seg000:000004D0 ; arg2是key
seg000:000004D0 ; arg1猜测是目标缓冲区长度
seg000:000004D0 ; arg0是目标缓冲区
seg000:000004D5 push 32h ; '2'
seg000:000004D7 lea eax,
seg000:000004DA mov , 0
seg000:000004DE push 0
seg000:000004E0 push eax
seg000:000004E1 call my_memset ; 功能:memset
seg000:000004E6 lea eax, ; esi是key_len,esi-12h就是key的剩余部分的长度
seg000:000004E9 push eax
seg000:000004EA lea eax,
seg000:000004ED push eax
seg000:000004EE lea eax,
seg000:000004F1 push 32h ; '2'
seg000:000004F3 push eax
seg000:000004F4 call get_specified_len_key
```
### 矩阵变换验证部分
矩阵结构体:
```
struct tag_matrix{
int row; //矩阵的行
int col; //矩阵的列
float *float_buf;//矩阵
};
```
根据key的前18位初始化矩阵jie:
1. 取user_name各个字符的16进制的和,然后模2,得到一个flag
```
seg000:000004FC mov ecx, ebx
seg000:000004FE call str_hash ; 功能:计算字符串的哈希值(字符串各位相加的和)
seg000:000004FE ; 参数:使用ecx作为参数
seg000:00000503 and eax, 1
seg000:00000506 mov , 3
seg000:00000510 lea ecx,
seg000:00000516 mov , eax ; 根据user_name生成的标志位
```
2. 取key的前18位,每2位生成矩阵的一个元素,正好是3*3矩阵。第一位关系到矩阵元素的正负,第二位就是矩阵元素的值(经过少许转换),还原后的C代码
```
//根据key的前18位,对jie.float_buf进行置位
for (i = 0; i < 3; ++i)
{
for (j = 0; j < 3; ++j)
{
int num_1 = -1;
index = 3 * i + j;
char ch = buf_2;
/*
字符装换为相应的数字,转换规则如下:
1.字符0-9转为数字0-9
2.字符A-F转为数字10-15
3.字符a-f转为数字10-15
4.其他字符转为-1
*/
if ('0' <= ch && ch <= '9')
{
num_1 = ch - '0';
}
else if ('A' <= ch && ch <= 'F')
{
num_1 = ch - '7';
}
else if ('a' <= ch && ch <= 'f')
{
num_1 = ch - 'W';
}
int mod = num_1 % 2; //用作标志
ch = buf_2;
int num_2 = -1;
if ('0' <= ch && ch <= '9')
{
num_2 = ch - '0';
}
else if ('A' <= ch && ch <= 'F')
{
num_2 = ch - '7';
}
else if ('a' <= ch && ch <= 'f')
{
num_2 = ch - 'W';
}
float_num = (float)num_2;
if (mod != flag)
{
float_num = 0 - float_num; //取负
}
index = jie.col * i + j;
jie.float_buf = float_num;
}
}
```
3. 初始化另外两个矩阵hou、zi,初始化后的内容
4. 对两组矩阵进行变换
```
seg000:000007FE push eax ; 第一组
seg000:000007FF lea eax,
seg000:00000805 push eax
seg000:00000806 call transform_a
seg000:0000080B cmp eax, 0FFFFFFFFh
seg000:0000080E jz fail
seg000:00000814 lea eax,
seg000:0000081A push eax
seg000:0000081B lea eax,
seg000:00000821 push eax
seg000:00000822 lea ecx,
seg000:00000828 call transform_b
seg000:0000082D cmp eax, 0FFFFFFFFh
seg000:00000830 jz fail
seg000:00000836 lea eax, ; 第二组
seg000:0000083C push eax
seg000:0000083D lea eax,
seg000:00000843 push eax
seg000:00000844 mov ecx, eax
seg000:00000846 call transform_a
seg000:0000084B cmp eax, 0FFFFFFFFh
seg000:0000084E jz fail
seg000:00000854 cmp , 3
seg000:0000085B jnz fail
seg000:00000861 lea eax,
seg000:00000867 push eax
seg000:00000868 lea eax,
seg000:0000086E push eax
seg000:0000086F lea ecx,
seg000:00000875 call transform_b
```
对应的C代码:
```
//第1组
int ret = transform_a(&jie, &liu, &hou);//变换方式1
if (ret == -1)
{
return 0;
}
ret = transform_b(&zi, &yan, &liu); //变换方式2
if (ret == -1)
{
return 0;
}
//第2组
ret = transform_a(&hou, &liu, &hou);
if (ret == -1 || jie.row != 3)
{
return 0;
}
ret = transform_b(&jie, &ren, &liu);
if (ret == -1)
{
return 0;
}
```
变换方式1,还原后的C代码如下所示:
```
/*
功能:取矩阵a的第i列,与矩阵c的第j行,相对应的每个元素相乘,得到乘积的累加和,填充到矩阵b的第i列第j行元素的位置
参数:
a源矩阵
b目标矩阵
c源矩阵(通过ecx传参)
*/
int transform_a(tag_matrix* a, tag_matrix* b, tag_matrix* c)
{
if (a->col != b->col)
{
return -1;
}
if (c->col != a->row)
{
return -1;
}
if (b->row <= 0)
{
return 1;
}
float float_tmp_1 = 0.0f;
float float_tmp_2 = 0.0f;
int index = 0;
for (int i = 0; i < b->row; ++i)
{
for (int j = 0; j < b->col; ++j)
{
float float_tmp_3 = 0.0f;
for (int k = 0; k < c->col; ++k)
{
if (i >= c->row || k >= c->col)
{
//这里是错误标志,后续原来检查矩阵元素是否错误
float_tmp_1 = g_float_num;
}
else
{
index = c->col * i + k;
float_tmp_1 = c->float_buf;
}
if (k >= a->row || j >= a->col)
{
float_tmp_2 = g_float_num;
}
else
{
index = a->col * k + j;
float_tmp_2 = a->float_buf;
}
//计算累加和
float_tmp_3 += float_tmp_1 * float_tmp_2;
}
if (i < b->row && j < b->col)
{
index = b->col * i + j;
b->float_buf = float_tmp_3;//累加和填充矩阵
}
}
}
return 1;
}
```
变换方式2,还原后的代码:
```
/*
功能:取矩阵a与矩阵c,相同位置元素的和,填充到矩阵b相同的位置
参数:
a源矩阵
b目标矩阵
c源矩阵(通过ecx传参)
*/
int transform_b(tag_matrix* a, tag_matrix* b, tag_matrix* c)
{
if (c->col != a->col)
{
return -1;
}
if (b->row != c->row)
{
return -1;
}
if (b->col != c->col)
{
return -1;
}
if (!b->row)
{
return 1;
}
float float_num_0 = 0.0f;
float float_num_1 = 0.0f;
int index = 0;
for (int i = 0; i < b->row; ++i)
{
for (int j = 0; j < b->col; ++j)
{
//取矩阵c
if (i >= c->row || j >= c->col)
{
float_num_0 = g_float_num;
}
else
{
index = a->col * i + j;
float_num_0 = c->float_buf;
}
//取矩阵a
if (i >= a->row || j >= a->col)
{
float_num_1 = g_float_num;
}
else
{
index = a->col * i + j;
float_num_1 = a->float_buf;
}
if (i < b->row && j < b->col)
{
index = b->col*i + j;
b->float_buf = float_num_0 + float_num_1;
}
}
}
return 1;
}
```
5. 验证两组矩阵变换最终生成的那两个ren、yan
```
seg000:00000890 loc_890:
seg000:00000890 xor ecx, ecx
seg000:00000892
seg000:00000892 loc_892:
seg000:00000892 cmp esi, 9
seg000:00000895 jge fail
seg000:0000089B cmp ecx, 3
seg000:0000089E jge fail
seg000:000008A4 lea edx,
seg000:000008A7 movss xmm1, dword ptr ;movss是将一个单精度数传输到xmm寄存器的低32位
seg000:000008AC ucomiss xmm1, xmm2 ; 比较低位数,并设置eflag
seg000:000008AF lahf ; 用于将标志寄存器的低八位送入AH
seg000:000008AF ; 即将标志寄存器FLAGS中的SF、ZF、AF、PF、CF五个标志位放入ah
seg000:000008B0 test ah, 44h ; 0x44 == 0100 0100也就是ZF=1,PF=1,其它全为0
seg000:000008B0 ;
seg000:000008B0 ; ZF PF AH中对应的位
seg000:000008B0 ; 0 0 不跳
seg000:000008B0 ; 0 1 跳
seg000:000008B0 ; 1 0 跳(两个数相等)
seg000:000008B0 ; 1 1 不跳(不可能出现,因为ZF=1,则PF一定为0)
seg000:000008B3 jnp fail
seg000:000008B9 movss xmm0, dword ptr
seg000:000008BE ucomiss xmm0, xmm2
seg000:000008C1 lahf
seg000:000008C2 test ah, 44h
seg000:000008C5 jnp fail
seg000:000008CB ucomiss xmm1, xmm0
seg000:000008CE lahf
seg000:000008CF test ah, 44h
seg000:000008D2 jp fail ; 这3个判断组合起来就是
seg000:000008D2 ; xmm1 != xmm2 && xmm0 != xmm2 && xmm0 == xmm1
seg000:000008D8 inc ecx
seg000:000008D9 cmp ecx, 3
seg000:000008DC jl short loc_892
seg000:000008DE add esi, 3
seg000:000008E1 cmp esi, 9
seg000:000008E4 jl short loc_890 ;
```
验证成功的条件,C代码如下:
```
/*
验证变换后的矩阵对应元素是否相同,验证成功的条件
1.矩阵的每个元素不能是-31415.0f(错误标志)
2.矩阵相同位置的元素必须相等
*/
index = 0;
for (i = 0; i < 9; i += 3)
{
for (j = 0; j < 3; ++j)
{
index = i + j;
if (yan.float_buf == g_float_num ||
ren.float_buf == g_float_num ||
yan.float_buf != ren.float_buf)
{
return 0;
}
}
}
```
#### 反推原始矩阵
设矩阵jie每个元素如下,另外两个矩阵是已知的
经过两轮变换生成yan、ren,最终下面这两个矩阵完全相等
计算可得,jie对应的矩阵如下所示:
到这里就可以写出第一部分的注册机了(其实也不能算注册机),只要key的前18位满足上面这个求出来的矩阵就可以
1. key的奇数位决定符号
2. key的偶数位是矩阵元素的倍数,但是倍率一样
满足上面两个条件的有很多,根据用户名哈希值的奇偶性随便取两组
```
//计算用户名的hash,并模2得到标志位
int name_hash = str_hash(user_name);
int flag = name_hash % 2;
if (flag)
{
memcpy(key, "121011101310011012", 18);
}
else
{
memcpy(key, "020001000300110002", 18);
}
```
### 八数码验证部分
#### 找规律
被这一部分困了很久,把所有的验证代码都还原了,看C代码看了很久还是没看出来该如何实现这一部分的注册机
数学模型,利用已提供的一组key,对内存变化进行观察
```
Name:360
Serial:32303130333021303214521036741034587634103478
观察内存变化:
//buf的初始值
06683F1007 01 04 00 03 08 02 05 06 00 00 00 00 00 00 00................
//使用name的每个字符对buf进行变换
06683F1007 01 00 04 03 08 02 05 06 00 00 00 02 00 00 00................
06683F1007 01 08 04 03 00 02 05 06 00 00 00 05 00 00 00................
06683F1007 01 00 04 03 08 02 05 06 00 00 00 02 00 00 00................
//使用Serial中18位后面的对buf进行变换
06683F1007 00 01 04 03 08 02 05 06 00 00 00 01 00 00 00................
06683F1007 03 01 04 00 08 02 05 06 00 00 00 04 00 00 00................
06683F1007 03 01 04 08 00 02 05 06 00 00 00 05 00 00 00................
06683F1007 03 00 04 08 01 02 05 06 00 00 00 02 00 00 00................
06683F1007 00 03 04 08 01 02 05 06 00 00 00 01 00 00 00................
06683F1000 07 03 04 08 01 02 05 06 00 00 00 00 00 00 00................
06683F1004 07 03 00 08 01 02 05 06 00 00 00 03 00 00 00................
06683F1004 07 03 02 08 01 00 05 06 00 00 00 06 00 00 00................
06683F1004 07 03 02 08 01 05 00 06 00 00 00 07 00 00 00................
06683F1004 07 03 02 00 01 05 08 06 00 00 00 04 00 00 00................
06683F1004 00 03 02 07 01 05 08 06 00 00 00 01 00 00 00................
06683F1000 04 03 02 07 01 05 08 06 00 00 00 00 00 00 00................
06683F1002 04 03 00 07 01 05 08 06 00 00 00 03 00 00 00................
06683F1002 04 03 07 00 01 05 08 06 00 00 00 04 00 00 00................
06683F1002 04 03 07 01 00 05 08 06 00 00 00 05 00 00 00................
06683F1002 04 03 07 01 06 05 08 00 00 00 00 08 00 00 00................
06683F1002 04 03 07 01 06 05 00 08 00 00 00 07 00 00 00................
06683F1002 04 03 07 01 06 00 05 08 00 00 00 06 00 00 00................
06683F1002 04 03 00 01 06 07 05 08 00 00 00 03 00 00 00................
06683F1002 04 03 01 00 06 07 05 08 00 00 00 04 00 00 00................
06683F1002 00 03 01 04 06 07 05 08 00 00 00 01 00 00 00................
06683F1000 02 03 01 04 06 07 05 08 00 00 00 00 00 00 00................
06683F1001 02 03 00 04 06 07 05 08 00 00 00 03 00 00 00................
06683F1001 02 03 04 00 06 07 05 08 00 00 00 04 00 00 00................
06683F1001 02 03 04 05 06 07 00 08 00 00 00 07 00 00 00................
06683F1001 02 03 04 05 06 07 08 00 00 00 00 08 00 00 00................
```
通过上面的数据变化可以看出,好像是在排序,只要是最终满足递增的顺序就成功了,其实不是的
抽象成数学模型来描述,就是初始化状态是左边矩阵,目标状态是右边矩阵,经过一系列的交换步骤,最终变为右边矩阵,而交换的步骤组成key序列
交换规则:只能是0和挨着的元素作交换
说起来挺惭愧的,数学太差,不知道该如何用代码实现生成key。
就加了很多的什么数学、算法…之类的QQ群,把数学模型发到群里,总有热心的大佬回答的,一般他们都很热情的……
主要让我知道这东西的学名就可以啦,搜了一看,代码实现还真麻烦…,顺便再网上A了一段代码,就实现注册机编写了(不是所有的用户名都有解)
再回过头看看这部分验证代码,主要分为三部分:
1. 根据用户名,初始化八数码数组
2. key的后半部分,作为移动该数组的步骤
3. 验证最终生成的数组,是否符合目标要求
总体流程C代码:
```
/*
算法模型:八数码问题
*/
int buf_3_len = strlen(buf_3);
init_eight_digtal(user_name); //根据用户名初始化8数码数组
for (i = 0; i < buf_3_len; ++i)
{
char ch = buf_3;
if (ch <= '8')
{
ch -= '0';
swap(ch); //元素交换
}
}
/*
验证g_sort.buf是否是按照递增的顺序排列的
*/
for (i = 0; i < 7; ++i)
{
if (g_sort.buf <= g_sort.buf)
{
return 0; //注册失败
}
}
//注册成功
return 1;
```
根据用户名初始化八数码数组
取用户名的按照一定的规则,进行交换,还原后的C代码:
```
/*
功能:根据user_name对八数码数组进行初始化
*/
void init_eight_digtal(char *user_name)
{
//初始化全局数组
int *pbuf = (int*)g_sort.buf;
pbuf = 0x40107; //图省事直接按照反汇编优化的方式来了
pbuf = 0x5020803;
pbuf = 6;
g_sort.index = 3;
int num = 0;
int len = strlen(user_name);
if (!len)
{
return;
}
for (int i = 0; i < len; ++i)
{
//这个switch...case的优化方式可以研究研究
char ch = user_name;
num = g_sort.index; //0 <= g_sort.index <= 9
switch (ch % 4) //如果ch是正数,指定会命中case;若ch为负,不是命中case 0,就是命中default
{
case 0:
if (num > 2)
{
move(UP);
}
break;
case 1:
if (num < 8)
{
move(RIGHT);
}
break;
case 2:
if (num < 6)
{
move(DOWN);
}
break;
case 3:
if (num)
{
move(LEFT);
}
break;
}
}
}
```
根据key进行交换
根据key的每一位,按照一定的规则进行交换
起初被这里坑了很久,一直以为研究这个交换规则就可以直接写出keygen…
```
/*
功能:注册码的每一位作为g_sort.index对g_sort.buf进行一轮一轮的交换
参数:注册码的每一位减去'0',范围是0--8
*/
void swap(int i)
{
int row = i / 3;
int col = i % 3;
if (!g_sort.buf) //本来条件是if (row > 2 || col > 2 || !g_sort.buf),由于前两个不可能实现,所以省略了
{
return;
}
/*
说明:若是g_sort.buf == 0
则index一定等于g_sort.index
*/
if (i > 2 && g_sort.buf == 0)
{
move(DOWN);
}
else if (col < 2 && g_sort.buf == 0)
{
move(LEFT);
}
else if (row < 2 && g_sort.buf == 0)
{
move(UP);
}
else if (col > 0 && g_sort.buf == 0)
{
move(RIGHT);
}
return;
}
```
把手工一行行还原的C代码,和注册机代码都发出来吧
本菜做不到像各位大佬一样肉眼反汇编就能识别出核心算法,以至于笨的还经常把验证流程人肉还原为C后还无法识别核心算法,写不出注册机…
## 说明
1.附件check.cpp是还原了CM的验证流程
2.key_gen.cpp不是所有的用户名都neng能生成key,还有有些key的生成会很慢
## 感谢
1. 那帮QQ群热心的算法牛、数学牛…
2. 还有那个被我A代码的博主,代码地址如下:https://blog.csdn.net/GuangHEultimate/article/details/51377269 spll6 发表于 2018-8-22 20:07
有些术语不懂什么意思
可能是我描述的不确切,有些东西我具体也不知道该怎么称呼!有什么疑惑可以相互交流学习 可以了 继续努力 看的一头雾水。。。看样我还是要努力啊 前排留名 看了一遍并不能看懂,萌新看来只能多看write up来复盘练习了,多谢dalao 厉害厉害{:1_893:} 看了一遍并不能看懂,萌新看来只能多看write up来复盘练习了,多谢dalao 看到看不懂的都要支持一下 学习了~~~~~~~~~~~~~·
大佬支持一下 虽然我看的一头雾水但是依旧不能阻挡我的崇拜