攻防世界逆向高手题之crazy

99 篇文章 33 订阅
本文详细介绍了攻防世界逆向高手题‘crazy’的解题过程,包括使用IDA分析64位ELF文件,理解C++函数调用,跟踪代码逻辑,以及通过运行程序观察输出锁定关键位置。通过分析,发现了输入处理、加密逻辑以及验证函数的关键步骤,并给出了解密脚本。最后,总结了逆向工程中的五个重要经验。
摘要由CSDN通过智能技术生成

攻防世界逆向高手题之crazy

继续开启全栈梦想之逆向之旅~
这题是攻防世界逆向高手题的crazy
在这里插入图片描述

下载附件,照例扔入exeinfope中查看信息:
在这里插入图片描述

64位ELF文件,扔入对应IDA中查看信息,有main函数看main函数:
在这里插入图片描述

可以看到,一堆眼花缭乱的系统函数,有些则是用类名调用的普通C++函数。
这里积累第一个经验:(别人博客的一句话)

代码看着很乱,有很多很长的命令,解决办法:依据英文意思去猜测。
找关键变量的方法:从后往前找,看flag和输入关系。复杂代码本质应该是简洁的,这样才叫出题。

但是从后往前找与flag的有关变量还是很麻烦,所以我们用运行程序方法不断查看显示信息,锁定关键位置。(调试的话不知道断点下在那里可能要遍历很长时间)



第一次乱输入:
在这里插入图片描述

.
.
可以看到运行到checking…后显示too short or too long处,还有就是用户输入在字符串输出之前。返回IDA查看代码:
在这里插入图片描述

这里有个cin,也是主函数中在其它字符串之前的,cin是c++的输入函数,从这里积累第二个经验:该程序的长字符传命令中最后一个才是我们要关注的命令,比如这里的cin函数,像下面截图中字符串前面也有cout函数。
在这里插入图片描述

.
.
.
看check…字符串之后的函数:
在这里插入图片描述

可以看到checking到if判断语句之间还是有很多函数的,可以用前面的依据英文意思猜测的方法去看函数,也可以在strings窗口查找too short or too long处的函数位置:(我选择后者)
在这里插入图片描述

这里跟踪到HighTemplar::calculate函数,根据英文名是计算函数,但是我看不懂这里的this+16,凭借意思我推测是我们输入flag的地址,把我们输入的flag经过两个简单的循环异或加密后输出:

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,          // 这里积累第三个经验:这是一个两步操作,v2取的是对应字符的地址,*v2是指在原v2地址上修改。把修改input_flag字符分成了两步做,让不熟悉的我载了跟头。
                    i);
    *v2 = (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                       (char *)this + 16,
                       i) ^ 80)
        + 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) ^ 19)
        + 11;
  }
  return result;
}

.
.
.然后由于HighTemplar::calculate函数的下一个就是if判断函数,所以我们只能跟踪if判断函数的HighTemplar::getSerial了。
在这里插入图片描述

.
.
前面this+16我推测是我们输入flag的地方,但是这个this+80存了什么东西我是真不知道了。双击跟踪堆栈也是未赋值的状态。这里积累第四个经验,逆向中不符合预期带份运算结果基本都是中间做了其它操作,如之前遇到的HOOK,这里很多函数我还没跟踪,那说明的确会有未发现的操作。
.
.
回到一开始cin函数的地方,表黄输入变量,看哪里还引用过该变量:
在这里插入图片描述

可以看到在checking前面还引用了一下,而该函数我们并没有分析,双击跟踪分析:

unsigned __int64 __fastcall HighTemplar::HighTemplar(DarkTemplar *a1, __int64 input_flag)
{
  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,                            // C++函数,basic_string(字符串类模板),不是复制就是比较,这里是复制输入字符串给a1+16开始的地址的数组中
    input_flag);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(
    (char *)a1 + 48,                            // C++函数,basic_string(字符串类模板),不是复制就是比较,这里是复制输入字符串给a1+48开始的地址的数组中,与前面隔了32个字符
    input_flag);
  std::allocator<char>::allocator(&v3);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(
    (char *)a1 + 80,
    "327a6c4304ad5938eaf0efb6cc3e53dc",         // C++函数,basic_string(字符串类模板),不是复制就是比较,这里是复制输入字符串给a1+80开始的地址的数组中,与前面还是隔了32个字符,这个v3不清楚
    &v3);
  std::allocator<char>::~allocator(&v3);
  return __readfsqword(0x28u) ^ v4;
}

这里我们可以看到把输入的flag分别给了this+16和this+48地址处,在this+80地址处给了327a6c4304ad5938eaf0efb6cc3e53dc这个字符串,那么前面对this+80的疑惑就解释得通了。
.
.
回过头去看this+80的那个HighTemplar::calculate函数:

__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,         // this+80地址处是327a6c4304ad5938eaf0efb6cc3e53dc的字符串
                     (int)i);
    if ( v1 != *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                           (char *)this + 16,   // this+80处如果不等于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);
}

.
.
简单的加密逻辑,直接写脚本逆向逻辑即可:

key1="327a6c4304ad5938eaf0efb6cc3e53dc"
flag1=""
flag=""
print(len(key1))
for i in range(len(key1)):
	flag1+=chr((ord(key1[i])-11)^19)
for i in range(len(flag1)):
	flag+=chr((ord(flag1[i])-23)^80)
print(flag)

.
.
结果:(这里积累第5个经验:现在的flag真的是越来越古灵精怪了,还有花括号,我一开始都以为我写错脚本了,现在看来,什么类型的flag都可以!一次不行就修改再交几次)
在这里插入图片描述

.
.
.
最后,下面这三个函数有什么用呢,我判断它是没什么用的,因为参数没有传入我输入的flag,跟踪里面也没有我输入的flag地址,除非是偏移地址间接引用我输入的flag,不过那样的话题就很难了!
在这里插入图片描述
.
.
.
总结:

1:这里积累第一个经验:(别人博客的一句话)
代码看着很乱,有很多很长的命令,解决办法:依据英文意思去猜测。
找关键变量的方法:从后往前找,看flag和输入关系。复杂代码本质应该是简洁的,这样才叫出题。

但是从后往前找与flag的有关变量还是很麻烦,所以我们用运行程序方法不断查看显示信息,锁定关键位置。(调试的话不知道断点下在那里可能要遍历很长时间)

2:
这里积累第二个经验:该程序的长字符传命令中最后一个才是我们要关注的命令,比如这里的cin函数,像下面截图中字符串前面也有cout函数。

3:
这里积累第三个经验:这是一个两步操作,v2取的是对应字符的地址,*v2是指在原v2地址上修改。把修改input_flag字符分成了两步做,让不熟悉的我载了跟头。

4:
这里积累第四个经验,逆向中不符合预期带份运算结果基本都是中间做了其它操作,如之前遇到的HOOK,这里很多函数我还没跟踪,那说明的确会有未发现的操作。

5:
这里积累第5个经验:现在的flag真的是越来越古灵精怪了,还有花括号,我一开始都以为我写错脚本了,现在看来,什么类型的flag都可以!一次不行就修改再交几次

解毕!敬礼!

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐一 · 林

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值