ADworld reverse wp - crazy

前言

忙里偷闲做一道攻防世界的C++逆向, 最近刷了一遍pipixia师傅的blog, 感觉作为一个binaryer, reverse和pwn不应该割裂开来, 而且之前做了半年逆向, 发现不知不觉间代码能力提高了(最近写代码的时候才发现的), 估计能够读懂反编译的伪码对码力也有训练作用吧, 而且入坑安全1年了, 逐渐体会到逆向是一件很有趣的事情, 以前做复杂逆向的时候总是很急躁, 现在才知道搞技术, 最重要的还是耐心.

分析过程

直接拖进IDA64 (IDA32弹框提示ELFx86-64, 并且应该养成习惯IDA之前先查壳
看到main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  __int64 v12; // rax
  __int64 v13; // rax
  __int64 v14; // rax
  __int64 v15; // rax
  __int64 v16; // rax
  char v18[32]; // [rsp+10h] [rbp-130h] BYREF
  char v19[32]; // [rsp+30h] [rbp-110h] BYREF
  char v20[32]; // [rsp+50h] [rbp-F0h] BYREF
  char v21[32]; // [rsp+70h] [rbp-D0h] BYREF
  char v22[32]; // [rsp+90h] [rbp-B0h] BYREF
  char v23[120]; // [rsp+B0h] [rbp-90h] BYREF
  unsigned __int64 v24; // [rsp+128h] [rbp-18h]

  v24 = __readfsqword(0x28u);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v18, argv, envp);
  std::operator>><char>(&std::cin, v18);
  v3 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------");
  std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
  v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Quote from people's champ");
  std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
  v5 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------");
  std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
  v6 = std::operator<<<std::char_traits<char>>(
         &std::cout,
         "*My goal was never to be the loudest or the craziest. It was to be the most entertaining.");
  std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
  v7 = std::operator<<<std::char_traits<char>>(&std::cout, "*Wrestling was like stand-up comedy for me.");
  std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
  v8 = std::operator<<<std::char_traits<char>>(
         &std::cout,
         "*I like to use the hard times in the past to motivate me today.");
  std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
  v9 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------");
  std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
  HighTemplar::HighTemplar((DarkTemplar *)v23, (__int64)v18);
  v10 = std::operator<<<std::char_traits<char>>(&std::cout, "Checking....");
  std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v19, v18);
  func1(v20, v19);
  func2(v21, v20);
  func3(v21, 0LL);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v21);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v20);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v19);
  HighTemplar::calculate((HighTemplar *)v23);
  if ( !(unsigned int)HighTemplar::getSerial((HighTemplar *)v23) )
  {
    v11 = std::operator<<<std::char_traits<char>>(&std::cout, "/");
    std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>);
    v12 = std::operator<<<std::char_traits<char>>(&std::cout, "Do not be angry. Happy Hacking :)");
    std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);
    v13 = std::operator<<<std::char_traits<char>>(&std::cout, "/");
    std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
    HighTemplar::getFlag[abi:cxx11](v22, v23);
    v14 = std::operator<<<std::char_traits<char>>(&std::cout, "flag{");
    v15 = std::operator<<<char>(v14, v22);
    v16 = std::operator<<<std::char_traits<char>>(v15, "}");
    std::ostream::operator<<(v16, &std::endl<char,std::char_traits<char>>);
    std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v22);
  }
  HighTemplar::~HighTemplar((HighTemplar *)v23);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v18);
  return 0;
}

从flag字符串倒着分析, 有getSerial, calculate, HighTemplar等可能的关键函数, 然后定位到
std::operator>><char>(&std::cin, v18);知道v18是输入的字符串
HighTemplar::HighTemplar((DarkTemplar *)v23, (__int64)v18);这里处理了输入字符串

分析一下HighTemplar

unsigned __int64 __fastcall HighTemplar::HighTemplar(DarkTemplar *a1, __int64 a2)
{
  char v3; // [rsp+17h] [rbp-19h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  DarkTemplar::DarkTemplar(a1);
  *(_QWORD *)a1 = &off_401EA0;
  *((_DWORD *)a1 + 3) = 0;
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string((char *)a1 + 16, a2);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string((char *)a1 + 48, a2);
  std::allocator<char>::allocator(&v3);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(
    (char *)a1 + 80,
    "327a6c4304ad5938eaf0efb6cc3e53dc",
    &v3);
  std::allocator<char>::~allocator(&v3);
  return __readfsqword(0x28u) ^ v4;
}

猜测327a6c4304ad5938eaf0efb6cc3e53dc是一串关键字符串, 虽然直接看还不清楚这个函数的作用, 但是先往下分析
在这里插入图片描述
发现v23出现在了所有可能是关键函数的参数位置, 接着分析calculate

bool __fastcall HighTemplar::calculate(HighTemplar *this)
{
  __int64 v1; // rax
  _BYTE *v2; // rbx
  bool result; // al
  _BYTE *v4; // rbx
  int i; // [rsp+18h] [rbp-18h]
  int j; // [rsp+1Ch] [rbp-14h]

  if ( std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16) != 32 )
  {
    v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Too short or too long");
    std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
    exit(-1);
  }
  for ( i = 0;
        i <= (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16);
        ++i )
  {
    v2 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                    (char *)this + 16,
                    i);
    *v2 = (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                       (char *)this + 16,
                       i) ^ 0x50)
        + 23;
  }
  for ( j = 0; ; ++j )
  {
    result = j <= (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16);
    if ( !result )
      break;
    v4 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                    (char *)this + 16,
                    j);
    *v4 = (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                       (char *)this + 16,
                       j) ^ 0x13)
        + 11;
  }
  return result;
}

这里伪码很丑, 不过大概就是一个if和两个for, 要求字符串长度==32, 然后对每个字符^0x50 + 23, 然后^0x13 + 11

然后看看getserial

__int64 __fastcall HighTemplar::getSerial(HighTemplar *this)
{
  char v1; // bl
  __int64 v2; // rax
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  unsigned int i; // [rsp+1Ch] [rbp-14h]

  for ( i = 0;
        (int)i < (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16);
        ++i )
  {
    v1 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                     (char *)this + 80,
                     (int)i);
    if ( v1 != *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                           (char *)this + 16,
                           (int)i) )
    {
      v4 = std::operator<<<std::char_traits<char>>(&std::cout, "You did not pass ");
      v5 = std::ostream::operator<<(v4, i);
      std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
      *((_DWORD *)this + 3) = 1;
      return *((unsigned int *)this + 3);
    }
    v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Pass ");
    v3 = std::ostream::operator<<(v2, i);
    std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
  }
  return *((unsigned int *)this + 3);
}

将上面的计算结果this + 16的字符串和this + 80的作比较

解题脚本

其实不需要分析得太清楚, 已经可以猜到程序的脉路了, 总体来说还是比较简单的
exp

serial = "327a6c4304ad5938eaf0efb6cc3e53dc"
flag = ""
for ch in serial:
    flag += chr((ord(ch) - 11 ^ 0x13) - 23 ^ 0x50)
    
print("flag{" + flag + "}")

总结

cpp的反编译伪码非常丑陋, 不过不需要在意一长串函数名, 直接看关键位置就行, 比如cin, cout等
逆向还是更偏向于分析, 解题脚本往往比pwn的简单不少
逆向更多是自顶向下的分析, 从宏观把握程序逻辑, 忽略繁琐的代码细节, 猜测一个思路然后验证, 以加快逆向速度
当然除非是打pwn, pwn的逆向分析是要抠清楚漏洞点的所有细节的
(说一堆大道理其实都莫得用, 多刷题, 干就完了

最近实在太多事, 忙完这段我会继续更新BUU-pwn的wp, 并且开个BUU-reverse的新坑叭
关于打比赛的事…明年再说, 真没一大段时间肝比赛, 只能碎片时间里刷刷题维持生计的样子(

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值