-
-
[原创] 看雪.安恒2020 KCTF春季赛 第二题:子鼠开天 by 心学
-
发表于: 2020-4-18 00:39 5054
-
发现有字符串“Enter your name:”
(1)、处理sn生成Hash,同时会进行返回值验证
1、工具:IDA、Python、RSATool2v17.exe、BigInt.exe
2、密码学知识:RSA、AES、MD5、SHA512
二、软件验证思路
2、密码学知识:RSA、AES、MD5、SHA512
二、软件验证思路
假设用户输入name、sn
nameHash = MD5(SHA512(name+'EFBEEDDE'))
nameHash =
MD5(SHA512(
nameHash
+'9E3779B9'))
【
EFBEEDDE 、
9E3779B9 实际运算需要转置】【摘要长度0x10】
middleHash = AES(
sn
)
snHash = RSA(
middleHash
)
【对称密钥的解密办法就在程序里,修改参数即可获取】
【RSA是256bit,必须进行暴力破解,大概20分钟以内】
【摘要长度0x10】
以上是软件验证思路的简要过程。里面涉及到细节会在下面两个章节中进行阐述。
那么逆向软件验证思路,即可构造出合法的用户名及序列号
使用IDA加载软件,View->Open subviews->Strings,或者Shift+F12,打开String视图
Ctrl+F搜索上述发现的字符串,双击定位变量处,按X键跳转到代码处,按F5键,即可生成伪代码
int __cdecl main(int argc, const char **argv, const char **envp) { time_t v3; // eax unsigned int lenName; // kr04_4 int result; // eax char Name; // [esp+8h] [ebp-12Ch] char v7; // [esp+9h] [ebp-12Bh] __int16 v8; // [esp+69h] [ebp-CBh] char v9; // [esp+6Bh] [ebp-C9h] char Sn; // [esp+6Ch] [ebp-C8h] char v11; // [esp+6Dh] [ebp-C7h] __int16 v12; // [esp+131h] [ebp-3h] char v13; // [esp+133h] [ebp-1h] Name = 0; Sn = 0; memset(&v7, 0, 0x60u); v8 = 0; v9 = 0; memset(&v11, 0, 0xC4u); v12 = 0; v13 = 0; v3 = time(0); sub_411AD8(v3); sub_411A90(aEnterYourName); scanf(aS, &Name); lenName = strlen(&Name) + 1; if ( lenName - 1 < 3 || lenName - 1 > 0x14 ) // 用户名长度在3-20之间 { sub_411A90(aBadName); result = -1; } else { sub_411A90(aEnterYourSn); scanf(aS, &Sn); if ( strlen(&Sn) == 64 ) // 序列号是64位 { sub_401380((int)&Name, lenName - 1, (int)&Sn, 0x40);// 关键验证代码 result = 0; } else { sub_411A90(aBadSn); result = -1; } } return result; }
从中找到关键方法:sub_401380,双击进入
int __cdecl main(int argc, const char **argv, const char **envp) { time_t v3; // eax unsigned int lenName; // kr04_4 int result; // eax char Name; // [esp+8h] [ebp-12Ch] char v7; // [esp+9h] [ebp-12Bh] __int16 v8; // [esp+69h] [ebp-CBh] char v9; // [esp+6Bh] [ebp-C9h] char Sn; // [esp+6Ch] [ebp-C8h] char v11; // [esp+6Dh] [ebp-C7h] __int16 v12; // [esp+131h] [ebp-3h] char v13; // [esp+133h] [ebp-1h] Name = 0; Sn = 0; memset(&v7, 0, 0x60u); v8 = 0; v9 = 0; memset(&v11, 0, 0xC4u); v12 = 0; v13 = 0; v3 = time(0); sub_411AD8(v3); sub_411A90(aEnterYourName); scanf(aS, &Name); lenName = strlen(&Name) + 1; if ( lenName - 1 < 3 || lenName - 1 > 0x14 ) // 用户名长度在3-20之间 { sub_411A90(aBadName); result = -1; } else { sub_411A90(aEnterYourSn); scanf(aS, &Sn); if ( strlen(&Sn) == 64 ) // 序列号是64位 { sub_401380((int)&Name, lenName - 1, (int)&Sn, 0x40);// 关键验证代码 result = 0; } else { sub_411A90(aBadSn); result = -1; } } return result; }
从中找到关键方法:sub_401380,双击进入
void __cdecl sub_401380(int name, unsigned int lenName, int sn, int num64) { char nameHash; // [esp+4h] [ebp-70h] char returnValueB; // [esp+14h] [ebp-60h] char v6; // [esp+15h] [ebp-5Fh] char v7; // [esp+23h] [ebp-51h] char snHash; // [esp+24h] [ebp-50h] char intSN; // [esp+34h] [ebp-40h] char returnValueA; // [esp+54h] [ebp-20h] if ( lenName >= 3 && lenName <= 0x14 && num64 == 64 ) { if ( myAtoi((char *)sn, 64, &intSN) != 0x20 // 字符串转16进制整数:即'123456'会转为0x123456 || (sub_4010F0( (int)&intSN, 0x20, (int)&returnValueA, (int)&unk_4190D0, 0x80, 0), // 序列号:对称加密方法 sub_401210( (int)&returnValueA, 32, (int)&returnValueB), // 序列号:RSA加密方法 returnValueB) || v6 != 2 || v7 ) { sub_411A90(aBadSn); // 错误 } else { sub_401190((const void *)name, lenName, (int)&nameHash);// 用户名:生成摘要 if ( !memcmp(&nameHash, &snHash, 0x10u) ) // 比较16个字节 sub_411A90(aCongratulation); // 正确 } } }
3.2、验证分为三个步骤:
(1)、处理sn生成Hash,同时会进行返回值验证
void __cdecl sub_401380(int name, unsigned int lenName, int sn, int num64) { char nameHash; // [esp+4h] [ebp-70h] char returnValueB; // [esp+14h] [ebp-60h] char v6; // [esp+15h] [ebp-5Fh] char v7; // [esp+23h] [ebp-51h] char snHash; // [esp+24h] [ebp-50h] char intSN; // [esp+34h] [ebp-40h] char returnValueA; // [esp+54h] [ebp-20h] if ( lenName >= 3 && lenName <= 0x14 && num64 == 64 ) { if ( myAtoi((char *)sn, 64, &intSN) != 0x20 // 字符串转16进制整数:即'123456'会转为0x123456 || (sub_4010F0( (int)&intSN, 0x20, (int)&returnValueA, (int)&unk_4190D0, 0x80, 0), // 序列号:对称加密方法 sub_401210( (int)&returnValueA, 32, (int)&returnValueB), // 序列号:RSA加密方法 returnValueB) || v6 != 2 || v7 ) { sub_411A90(aBadSn); // 错误 } else { sub_401190((const void *)name, lenName, (int)&nameHash);// 用户名:生成摘要 if ( !memcmp(&nameHash, &snHash, 0x10u) ) // 比较16个字节 sub_411A90(aCongratulation); // 正确 } } }
myAtoi:将sn字符串转成对应的十六进制大整数,输出为256位整数A
sub_4010F0:实施对称加密,输出为256位整数B
sub_401210:实施RSA加密,输出为256位整数C,其中后128位,即0x10个字节为snHash值
但是对C有三个判断语句:
第0个字节等于00:对应于 returnValueB
第1个字节等于02:对应于 v6 != 2
第F个字节等于00:对应于 v7
C的结构如:0002【13个字节,随机】00 + snHash
群里大佬提到的多解,估计问题出在这儿,作者应该锁定13个字节,或者再加一道判断语句,基本上就能限定解的唯一性。
sn--->Hash:如果破解了RSA,基本上就能进行逆操作。Hash--->sn
以下是该步骤的相关代码,以及判断的思路、验证操作等
myAtoi: 相对简单,可以在调试时进行验证
int __cdecl myAtoi(char *sn, int num64, char *a3) { char *v3; // ebp int v4; // esi int result; // eax char *v6; // edi signed int i; // edx unsigned __int8 v8; // cl char v9; // cl if ( !sn ) return 0; if ( num64 % 2 ) return 0; v3 = a3; if ( !a3 ) return 0; v4 = 0; result = num64 / 2; if ( num64 / 2 > 0 ) { v6 = sn; do { LOWORD(sn) = *(_WORD *)v6; i = 0; do { v8 = *((_BYTE *)&sn + i); if ( v8 < 0x30u || v8 > 0x39u ) // 非数字 { if ( v8 < 0x61u || v8 > 0x66u ) // 非小写字母 { if ( v8 < 0x41u || v8 > 0x46u ) // 非大写字母 return 0; v9 = v8 - 0x37; // 大写字母:转16进制 } else { v9 = v8 - 0x57; // 小写字母:转16进制 } } else { v9 = v8 - 0x30; // 数字转16进制 } *((_BYTE *)&sn + i++) = v9; } while ( i < 2 ); // 一次处理两个字符 v6 += 2; v3[v4++] = BYTE1(sn) & 0xF | 16 * (_BYTE)sn; } while ( v4 < result ); } return result; }
sub_4010F0:实施对称加密,输出为256位整数B ,这个相对麻烦
int __cdecl myAtoi(char *sn, int num64, char *a3) { char *v3; // ebp int v4; // esi int result; // eax char *v6; // edi signed int i; // edx unsigned __int8 v8; // cl char v9; // cl if ( !sn ) return 0; if ( num64 % 2 ) return 0; v3 = a3; if ( !a3 ) return 0; v4 = 0; result = num64 / 2; if ( num64 / 2 > 0 ) { v6 = sn; do { LOWORD(sn) = *(_WORD *)v6; i = 0; do { v8 = *((_BYTE *)&sn + i); if ( v8 < 0x30u || v8 > 0x39u ) // 非数字 { if ( v8 < 0x61u || v8 > 0x66u ) // 非小写字母 { if ( v8 < 0x41u || v8 > 0x46u ) // 非大写字母 return 0; v9 = v8 - 0x37; // 大写字母:转16进制 } else { v9 = v8 - 0x57; // 小写字母:转16进制 } } else { v9 = v8 - 0x30; // 数字转16进制 } *((_BYTE *)&sn + i++) = v9; } while ( i < 2 ); // 一次处理两个字符 v6 += 2; v3[v4++] = BYTE1(sn) & 0xF | 16 * (_BYTE)sn; } while ( v4 < result ); } return result; }
sub_4010F0:实施对称加密,输出为256位整数B ,这个相对麻烦
首先观察两处疑点:
最右边的参数可以取值0或1,从而进入不同的处理方法内,在sub_404FB0里也会进行判断,极度怀疑是加密函数和解密函数
0x80h在AES解密环节中很多,有时候也会出现在hash运算中。
验证操作:
先记录经过sub_4010F0处理后:intSN和returnValue的数值是多少,他们都是0x20字节长,等长,初步验证是对称加密
然后将
returnValue作为用户验证码输入,在
sub_4010F0处暂停,修改其调用函数处的参数值 int0:0改为1
执行完成之后,观察返回值,经过对比,验证了对称加密,也可以再次实施逆运算。
int __cdecl sub_4010F0(int intSN, int int20h, int returnValue, int constValue, int int80h, int int0) { int intSNCopy; // esi int v7; // ebx char middleValue; // [esp+4h] [ebp-F4h] if ( int0 == 1 ) sub_404FE0((_DWORD *)constValue, int80h, (unsigned int *)&middleValue); else sub_4053A0(constValue, int80h, (int)&middleValue);// 执行 if ( int20h / 16 <= 0 ) return int20h; intSNCopy = intSN; v7 = int20h / 16; do { sub_404FB0(intSNCopy, returnValue - intSN + intSNCopy, (int)&middleValue, int0); intSNCopy += 16; --v7; } while ( v7 ); return int20h; }
int __cdecl sub_4010F0(int intSN, int int20h, int returnValue, int constValue, int int80h, int int0) { int intSNCopy; // esi int v7; // ebx char middleValue; // [esp+4h] [ebp-F4h] if ( int0 == 1 ) sub_404FE0((_DWORD *)constValue, int80h, (unsigned int *)&middleValue); else sub_4053A0(constValue, int80h, (int)&middleValue);// 执行 if ( int20h / 16 <= 0 ) return int20h; intSNCopy = intSN; v7 = int20h / 16; do { sub_404FB0(intSNCopy, returnValue - intSN + intSNCopy, (int)&middleValue, int0); intSNCopy += 16; --v7; } while ( v7 ); return int20h; }
sub_401210:实施RSA加密,输出为256位整数C,其中后128位,即0x10个字节为snHash值 ;这个处理起来比较麻烦
进入函数之内:
里面有: sub_4068D0((int)v5, 0x10001);0x10001这是明显的RSA中的e值,很多RSA中的e都使用0x10001,极度怀疑这是一个RSA算法
那么如何来判断,那个是n了?既然是RSA算法,那么里面肯定有一个n,这个不可能由用户来带入,恰好V10是0x20字节长度,基本确定是一个RSA256算法,RSA256算法的加密强度太低,可以通过因式分解获取pq、进而得出ψ(n),再算出d,那么就可以做出解密算法来。
signed int __cdecl sub_401210(int a1, int int20h, int a3) { bignum_st *v4; // esi bignum_st *v5; // edi bignum_st *v6; // ebx bignum_st *v7; // ebp signed int v8; // eax _DWORD *lpMem; // [esp+0h] [ebp-24h] char v10[32]; // [esp+4h] [ebp-20h] _BYTE *int20ha; // [esp+2Ch] [ebp+8h] v10[0] = 0x69; v10[8] = 0x39; v10[31] = 0x39; v10[1] = 0x82u; v10[2] = 0x30; v10[3] = 0x28; v10[4] = 0x57; v10[5] = 0x74; v10[6] = 0x65; v10[7] = 0xABu; v10[9] = 0x91u; v10[10] = 0xDFu; v10[11] = 4; v10[12] = 0x51; v10[13] = 0x46; v10[14] = 0xF9u; v10[15] = 0x1D; v10[16] = 0x55; v10[17] = 0x6D; v10[18] = 0xEEu; v10[19] = 0x88u; v10[20] = 0x70; v10[21] = 0x84u; v10[22] = 0x5D; v10[23] = 0x8Eu; v10[24] = 0xE1u; v10[25] = 0xCDu; v10[26] = 0x3C; v10[27] = 0xF7u; v10[28] = 0x7E; v10[29] = 0x4A; v10[30] = 0xC; if ( int20h != 0x20 ) return 0; lpMem = sub_406170(); v4 = sub_406650(); // 初始化 v5 = sub_406650(); v6 = sub_406650(); v7 = sub_406650(); sub_406930((unsigned __int8 *)a1, 0x20, (LPVOID *)v7);// a1转换成大整数v7 sub_4068D0((int)v5, 0x10001); // 0x10001转换成大整数v5 sub_406930((unsigned __int8 *)v10, 0x20, (LPVOID *)v4);// v10转换成大整数v4 sub_406D30(v6, v7, v5, v4, (int)lpMem); // v6存放结果:这是RSA加解密函数 v8 = 0; int20ha = (_BYTE *)(v6->d + 0x1F); do *(_BYTE *)(++v8 + a3 - 1) = *int20ha--; while ( v8 < 0x20 ); // 将计算的结果倒序存放 sub_4065F0(v4); // 复原 sub_4065F0(v5); sub_4065F0(v6); sub_4065F0(v7); sub_4061D0(lpMem); return 0x20; }
RSA算法验证中:
signed int __cdecl sub_401210(int a1, int int20h, int a3) { bignum_st *v4; // esi bignum_st *v5; // edi bignum_st *v6; // ebx bignum_st *v7; // ebp signed int v8; // eax _DWORD *lpMem; // [esp+0h] [ebp-24h] char v10[32]; // [esp+4h] [ebp-20h] _BYTE *int20ha; // [esp+2Ch] [ebp+8h] v10[0] = 0x69; v10[8] = 0x39; v10[31] = 0x39; v10[1] = 0x82u; v10[2] = 0x30; v10[3] = 0x28; v10[4] = 0x57; v10[5] = 0x74; v10[6] = 0x65; v10[7] = 0xABu; v10[9] = 0x91u; v10[10] = 0xDFu; v10[11] = 4; v10[12] = 0x51; v10[13] = 0x46; v10[14] = 0xF9u; v10[15] = 0x1D; v10[16] = 0x55; v10[17] = 0x6D; v10[18] = 0xEEu; v10[19] = 0x88u; v10[20] = 0x70; v10[21] = 0x84u; v10[22] = 0x5D; v10[23] = 0x8Eu; v10[24] = 0xE1u; v10[25] = 0xCDu; v10[26] = 0x3C; v10[27] = 0xF7u; v10[28] = 0x7E; v10[29] = 0x4A; v10[30] = 0xC; if ( int20h != 0x20 ) return 0; lpMem = sub_406170(); v4 = sub_406650(); // 初始化 v5 = sub_406650(); v6 = sub_406650(); v7 = sub_406650(); sub_406930((unsigned __int8 *)a1, 0x20, (LPVOID *)v7);// a1转换成大整数v7 sub_4068D0((int)v5, 0x10001); // 0x10001转换成大整数v5 sub_406930((unsigned __int8 *)v10, 0x20, (LPVOID *)v4);// v10转换成大整数v4 sub_406D30(v6, v7, v5, v4, (int)lpMem); // v6存放结果:这是RSA加解密函数 v8 = 0; int20ha = (_BYTE *)(v6->d + 0x1F); do *(_BYTE *)(++v8 + a3 - 1) = *int20ha--; while ( v8 < 0x20 ); // 将计算的结果倒序存放 sub_4065F0(v4); // 复原 sub_4065F0(v5); sub_4065F0(v6); sub_4065F0(v7); sub_4061D0(lpMem); return 0x20; }
RSA算法验证中:
m = 0x13981882F9F8B823387C6EF9D5009E9F01675D5A3719A44696336E639FCD2778 e = 0x10001 n = 0x69823028577465AB3991DF045146F91D556DEE8870845D8EE1CD3CF77E4A0C39 c = m ** e % n print('%02X'%c)
计算结果与调试结果一直:25D343CED2E5A3CD5FE94CEA15700B8D7725F7715A6A547A2BF0E8373705E
m = 0x13981882F9F8B823387C6EF9D5009E9F01675D5A3719A44696336E639FCD2778 e = 0x10001 n = 0x69823028577465AB3991DF045146F91D556DEE8870845D8EE1CD3CF77E4A0C39 c = m ** e % n print('%02X'%c)
计算结果与调试结果一直:25D343CED2E5A3CD5FE94CEA15700B8D7725F7715A6A547A2BF0E8373705E
还需要补充000,即为:00025D343CED2E5A3CD5FE94CEA15700B8D7725F7715A6A547A2BF0E8373705E
也可以通过看雪中的Big Integer Calculator v1.13 带入计算,感谢readyu大神
RSA算法破解中:使用RSATool2v17.exe
对n进行因式分解
n = 0x69823028577465AB3991DF045146F91D556DEE8870845D8EE1CD3CF77E4A0C39
大约20分钟以内,分解结果:
p = 0x979BE0C9EECE7426C9FD28C2D6E7772B
q = 0xB22831D15714EB91CD83340B4837182B
计算d
d = 0x390A684CB713378FFD5CCE8C4000B5D6A2BB9F29B63D395E6BE6E9DD941527BD
【有大佬提d到 http://www.factordb.com/,经过测试,不成功。
https://www.wolframalpha.com/ 也不成功,老老实实分解中】
(2)、处理name生成Hash
signed int __cdecl sub_401190(const void *name, unsigned int lenName, int returnMD5) { char v4; // [esp+8h] [ebp-DCh] int v5; // [esp+18h] [ebp-CCh] char v6; // [esp+1Ch] [ebp-C8h] qmemcpy(&v6, name, lenName); *(int *)((char *)&v5 + lenName + 4) = dword_41D038;// v5在v6前面4个字节,v5+4相当于定位到v6,再加上lenName,也就是在用户字符串后面。实际上在用户字符串后面加上4个字节 sub_4010B0((int)&v6, lenName + 4, (int)&v4); // 结果存在v4里面,实际上是 SHA512+MD5计算摘要 v5 = dword_41D030; // v5在v4的后面,也就是在v4后面追加了4个字节 sub_4010B0((int)&v4, 20, returnMD5); return 16; }
首先在用户名后面追加4个字节,进行hash组合计算
signed int __cdecl sub_401190(const void *name, unsigned int lenName, int returnMD5) { char v4; // [esp+8h] [ebp-DCh] int v5; // [esp+18h] [ebp-CCh] char v6; // [esp+1Ch] [ebp-C8h] qmemcpy(&v6, name, lenName); *(int *)((char *)&v5 + lenName + 4) = dword_41D038;// v5在v6前面4个字节,v5+4相当于定位到v6,再加上lenName,也就是在用户字符串后面。实际上在用户字符串后面加上4个字节 sub_4010B0((int)&v6, lenName + 4, (int)&v4); // 结果存在v4里面,实际上是 SHA512+MD5计算摘要 v5 = dword_41D030; // v5在v4的后面,也就是在v4后面追加了4个字节 sub_4010B0((int)&v4, 20, returnMD5); return 16; }
首先在用户名后面追加4个字节,进行hash组合计算
然后在上面的结果后面再追加4个字节,进行hash组合计算
那么如何知道是hash组合了?我们接着深入到sub_4010B0
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2020-4-19 11:19
被htg编辑
,原因: 晚上python最终代码
赞赏
他的文章
看原图
赞赏
雪币:
留言: