Pediy CTF 2018 - 密界寻踪 Writeup
目录
分析用IDA载程序,发现程序中存在一些跳来跳去的花指令。手动让IDA正确识别指令之后,发现所有的自定义函数都在内部做了call导致反编译只能识别出JUMPOUT。这里我使用一种比较邪门的patch方法,在IDA数据库中将CALL改为JMP,但是不将补丁应用到二进制文件 ,打断点时避免打到这些数据库中和二进制中不一样的地址,这样可以保证程序的正常运行,又可以使用IDA的源码级调试。
main函数:
memset((void *)(a1 - 124), 0xCCu, 0x7Cu);
*(_DWORD *)(a1 - 4) = 0;
*(_DWORD *)(a1 - 8) = 0;
do
{
v2 = __rdtsc();
v3 = v2;
v4 = __rdtsc();
}
while ( (unsigned int)(v4 - v3) > 0xFFF );
*(_DWORD *)(a1 - 20) = 1734375282;
*(_DWORD *)(a1 - 16) = 7632224;
*(_WORD *)(a1 - 12) = 0;
*(_DWORD *)(a1 - 32) = 1802596452;
*(_WORD *)(a1 - 28) = 119;
*(_DWORD *)(a1 - 26) = 0;
*(_BYTE *)(a1 - 56) = 0;
*(_DWORD *)(a1 - 55) = 0;
*(_DWORD *)(a1 - 51) = 0;
*(_DWORD *)(a1 - 47) = 0;
*(_DWORD *)(a1 - 43) = 0;
*(_DWORD *)(a1 - 39) = 0;
*(_WORD *)(a1 - 35) = 0;
*(_BYTE *)(a1 - 33) = 0;
*(_BYTE *)(a1 - 60) = 0;
*(_WORD *)(a1 - 59) = 0;
sub_4011E0(); // banner
sub_40100A(); // antidbg
xor_1toN(a1 - 20);
xor_1toN(a1 - 32);
scanf("%s", a1 - 56, 24);
if ( strlen((const char *)(a1 - 56)) > 0x17 )
{
printf((const char *)(a1 - 32));
exit(0);
}
v5 = strlen((const char *)(a1 - 53));
strtohex_frombyte3(a1 - 53, (int)&hex_flag, v5);
RSA(a1);
*(_DWORD *)(a1 - 4) = v6;
memcpy((void *)(a1 - 60), (const void *)(a1 - 56), 3u);
if ( isDigit(a1 - 60) )
{
*(_DWORD *)(a1 - 8) = AES(a1, a1 - 60);
if ( *(_DWORD *)(a1 - 8) + *(_DWORD *)(a1 - 4) == 2 )
printf((const char *)(a1 - 20));
else
printf((const char *)(a1 - 32));
system("pause");
}
else
{
printf((const char *)(a1 - 32));
}
程序似乎应该是有一个反调试的,如果挂上调试器的话得到的结果应该会有问题。不过在我测试的时候,似乎……并没有用?= =
程序中栈上的很多字符串都是利用异或1到n动态解密的,这个调试一下就能拿到。
程序使用了MIRACL密码库,还原函数名搞得我脑壳疼 = = MIRACL这个库比较老,官方只给出了编译成静态Lib的方法,并不能编译成DLL。用VS编译搞出来obj文件扔进bindiff,发现函数的识别率特别低。程序查一下链接器,这里要膜一下出题人,发现居然是用VC++6.0写的……正好做题的时候遇上上机实验,到学校机房的VC++6.0环境编译MIRACL,搞出几十个obj文件拷回去,一个一个建IDB,bindiff载……搞了一个多小时,终于还原了大部分函数。
程序中用到的两个最关键的算法是RSA和AES密码算法,Flag前3位给AES函数,后面的给RSA函数。所以这道题叫“密”界寻踪啊2333333
RSA函数:
/* 一大堆初始化 */
memset((void *)(a1 - 740), 0, 0x88u);
xor_1toN(a1 - 204); // 208CBB7CD6ECC64516D07D978F5F0681F534EAD235D5C49ADD72D2DB840D5304
xor_1toN(a1 - 804); // 7da39de66016477b1afc3dc8e309dc429b5de855f0d616d225b570b68b88a585
*(_DWORD *)(*(_DWORD *)(a1 - 4) + 564) = 16;
*(_DWORD *)(a1 - 808) = mirvar(0);
*(_DWORD *)(a1 - 812) = mirvar(0);
*(_DWORD *)(a1 - 820) = mirvar(0);
*(_DWORD *)(a1 - 816) = mirvar(0);
load_hex(*(_DWORD *)(a1 - 820), (int)&hex_flag);// c
load_hex(*(_DWORD *)(a1 - 808), a1 - 804); // n
load_hex(*(_DWORD *)(a1 - 812), (int)"3e9");// e
if ( mr_compare(*(_DWORD *)(a1 - 820), *(_DWORD *)(a1 - 808)) == -1 )
{
powmod(*(_DWORD *)(a1 - 820), *(_DWORD *)(a1 - 812), *(_DWORD *)(a1 - 808), *(_DWORD *)(a1 - 816));
big_to_bytes(0, *(_DWORD *)(a1 - 816), (_BYTE *)(a1 - 404), 0);
mirkill(*(_DWORD *)(a1 - 808));
mirkill(*(_DWORD *)(a1 - 812));
mirkill(*(_DWORD *)(a1 - 820));
mirkill(*(_DWORD *)(a1 - 816));
sub_409CC0();
v3 = strlen((const char *)(a1 - 404));
bytes_to_hex(a1 - 404, a1 - 604, v3);
v4 = strcmp((const char *)(a1 - 204), (const char *)(a1 - 604)) == 0;
}
可以看到是一个非常正常的powmod算法,也正是RSA算法的核心,传入的Flag是以十六进制形式,n、e和密文都可以提取出来。
AES算法:
memset(&v4, 0xCCu, 0x570u);
v24 = 0;
memset(&v25, 0, 0xC4u);
v26 = 0;
v27 = 0;
qmemcpy(&v20, "831GD47;?K2M=8:&U#$V#T\"-+\\*)*X'e", 0x21u); // 912CA2036A9A0656D17B6B552F157F8E
memset(&v21, 0, 0xA4u);
v22 = 0;
v23 = 0;
v12 = 'p';
v13 = 'e';
v14 = 'd';
v15 = 'i';
v16 = 'y';
memset(&v17, 0, 0xC0u);
v18 = 0;
v19 = 0;
v8 = 0;
memset(&v9, 0, 0xC4u);
v10 = 0;
v11 = 0;
v7 = 0;
memcpy(&v5, "123567389:;<=>?", 0xFu); //XXX1314000000000
BYTE2(v7) = 32;
xor_1toN(&v20);
xor_1toN(&v5);
v5 = *(_BYTE *)a2;
LOBYTE(v6) = *(_BYTE *)(a2 + 1);
BYTE1(v6) = dword_495728 + *(_BYTE *)(a2 + 2);
memset(&v28, 0, 0x1FCu);
aes_init((int)&v28, 0, 16, (int)&v5, 0);
aes_encrypt(&v28, &v12);
v2 = strlen(&v12);
bytes_to_hex((int)&v12, (int)&v24, v2);
return strcmp(&v20, &v24) == 0;
}
使用了MIRACL库内置的AES算法,密文可以提取,明文是pediy加上11个零字节,密钥为输入的Flag的前三位(其中最后一位+1)(要求都是数字)拼上1314000000000. // 似乎原本如果挂调试器的话这里的+1会变成+2导致结果不正确?
计算 RSA作为N的数字并不是很大,到factordb.com这个网站上寻找因子分解,得到N= 208096057845685678782766058500526476379 × 273086345401562743300402731618892888991,即为两个大素数p × q,那么可以求得私钥d为e对于(p-1)×(q-1)的逆元,再利用解密规则p = c ^ d mod n求得明文。
AES程序要寻找一个正确的AES密钥,没什么方法,只能爆破。一共只有3位数字,计算量不大。
解题脚本import gmpy2
from Crypto.Cipher import AES
# RSA
n = 0x7da39de66016477b1afc3dc8e309dc429b5de855f0d616d225b570b68b88a585
p = 208096057845685678782766058500526476379
q = n / p
e = 0x3e9
c = 0x208CBB7CD6ECC64516D07D978F5F0681F534EAD235D5C49ADD72D2DB840D5304
d = gmpy2.invert(e, (p-1) * (q-1))
p = gmpy2.powmod(c, d, n)
flag2 = hex(p)[2:].decode('hex')
# AES
plain = "pediy" + '\x00' * 11
cipher = "912CA2036A9A0656D17B6B552F157F8E".decode("hex")
for x1 in range(48, 58):
for x2 in range(48, 58):
for x3 in range(48, 58):
key = chr(x1) + chr(x2) + chr(x3 + 1) + "1314000000000"
aes = AES.new(key, AES.MODE_ECB)
if (aes.encrypt(plain) == cipher):
flag1 = chr(x1) + chr(x2) + chr(x3)
break
print flag1+flag2
Flag520iamahandsomeguyhaha1
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。