AVALON ddctf ddctf2019 obfuscating macros write up

ddctf2019 obfuscating macros write up

环境配置

系统 : Windows 10 \ Linux kali 4.6.0-kali1-amd64
程序 : obfuscating_macros.out
要求 : 输入口令
使用工具 :ida

开始分析

静态分析

使用ida载入程序,使用WELL DONE!字符串定位到关键代码:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char v3; // al
  char v4; // al
  bool v5; // al
  __int64 v6; // rax
  char v8; // [rsp+0h] [rbp-40h]
  unsigned __int64 v9; // [rsp+28h] [rbp-18h]

  v9 = __readfsqword(0x28u);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v8, a2, a3);
  std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &v8);
  sub_4069D6((__int64)&v8);
  v5 = 0;
  if ( v3 )
  {
    sub_4013E6((__int64)&v8, 10LL);
    if ( v4 )
      v5 = 1;
  }
  if ( v5 )
    v6 = std::operator<<<std::char_traits<char>>(&std::cout, "WELL DONE!");
  else
    v6 = std::operator<<<std::char_traits<char>>(&std::cout, "wrong answer");
  std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v8);
  return 0LL;
}

这里,通过两个函数的检验,就算夺旗成功。

第一个函数

进入第一个函数,经过ida处理生成的伪c代码竟有五百行,这里这里对参数a1使用交叉参考:

Xref    Line    Column  Pseudocode line
r   71  95      if ( (std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(a1) & 1) != 0 )
r   124 107     v2 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 / 2);
r   126 110         * (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15)
r   160 108     v3 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) > '/'
r   161 108       && *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) <= '9';
r   206 107     v4 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 / 2);
r   208 110         * (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15)
r   249 108     v5 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) > '@'
r   250 108       && *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) <= 'F';
r   326 107     v6 = *(char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 + 1)
r   328 107     v7 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 / 2);
r   362 108     v8 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 + 1) > '/'
r   363 108       && *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 + 1) <= '9';
r   408 107     v9 = *(char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 + 1)
r   410 108     v10 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 / 2);
r   452 22                        a1,
r   455 22                        a1,
r   529 104         if ( v12 < std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(a1) )
r   538 103             v13 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(a1);
r   539 97              std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::resize(a1, v13 >> 1);

就是在做一些格式控制,没有太值得注意的。

第二个函数:

进入第二个函数,经过ida处理生成的伪c代码竟有一千多行,这里就不放代码了,这种经过膨胀/混淆的代码,直接使用静态污点分析技术来跟踪关键指令就好。查看参数一的引用:
r 65 101 v24 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(mystr);
查看v24的引用:

Xref    Line    Column  Pseudocode line
w   65  2     v24 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(mystr);
m   296 13          v4 = v24++;
r   413 14          if ( *v24 )
r   462 22                *v49 = *v24 == 0;

查看v4的引用:

Xref    Line    Column  Pseudocode line
w   296 8           v4 = v24++;
r   297 17          *v26 -= *v4;

查看v26的引用:

Xref    Line    Column  Pseudocode line
w   243 8           v26 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(&v56);
r   270 9           *v26 ^= *v25;
r   283 16          v25 += *v26 & 1;
r   297 9           *v26 -= *v4;
r   324 14          if ( *v26 )
m   336 10          ++v26;
r   369 14          if ( *v26 )

可以猜出,程序在按照一定规律从v25这个表中取值,然后与固定值异或。

动态调试

下断点将关键数据的变化记下来,以下是我设置的断点列表:

Type    Location    Pass count  Hardware    Condition   Actions Comment Group
Abs 0x4061C6                Break       Default
Src $4013E6:270             Break       Default
Src $4013E6:283             Break       Default
Src $4013E6:453             Break       Default
Src $4013E6:1716                Break       Default
Src $4069D6:71              Break       Default
Src $408175:12              Break       Default
Src $408175:17              Break       Default

编写程序

将记录下的数据和表的数据进行异或运算:

my_str_y = "LMaloQLlMManlp"
xor_table =  [0x35, 0x0D, 0x8A, 0xBF, 0x75, 0x83, 0xE8, 0x02,  0x5D, 0xC3, 0x55, 0x48, 0x89, 0xE5, 0x48, 0x89]

res = 0
index = 0
flag = ""
for ch in my_str_y:
    res = ord(ch) ^ xor_table[index]
    flag += str(hex(res))[2:4]
    index += res & 1

print flag.upper()

夺旗成功

用计算得出的flag作为输入,提示成功:

➜  playground python test.py           
79406C61E5EEF319CECEE2ED8498

参考链接

  1. 看雪&京东合办 ctf_2018 第十二题 破解之道(吓人的纸老虎) http://dreamcracker.today/2018/11/05/%e7%9c%8b%e9%9b%aa%e4%ba%ac%e4%b8%9c%e5%90%88%e5%8a%9e-ctf_2018-%e7%ac%ac%e5%8d%81%e4%ba%8c%e9%a2%98-%e7%a0%b4%e8%a7%a3%e4%b9%8b%e9%81%93%ef%bc%88%e5%90%93%e4%ba%ba%e7%9a%84%e7%ba%b8%e8%80%81/