首页
社区
课程
招聘
[原创]第四题 密界寻踪 WriteUp
2018-6-22 18:28 3232

[原创]第四题 密界寻踪 WriteUp

2018-6-22 18:28
3232

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

Flag

520iamahandsomeguyhaha1

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞1
打赏
分享
最新回复 (2)
雪    币: 2688
活跃值: (173)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
鬼爵 2018-11-22 21:15
2
0
大佬带带我
雪    币: 102
活跃值: (452)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
liucq 2018-11-22 21:42
3
0
为什么我有一种公司招人做测试的感觉
游客
登录 | 注册 方可回帖
返回