ciaoly
Easy_math Writeup

access_time
brush 997个字
whatshot 63 ℃

Easy_math Writeup

题目来源: DASCTF Sept X 浙江工业大学秋季挑战赛

题目链接: BUUCTF在线评测 (buuoj.cn)

题目附件


运行程序, 发现只有一个"Input:". 输入非数字字符程序会崩溃, 连续输入5行数字能够得到错误提示"arr1 wrong!"

使用Ghidra打开文件, 同时用x32dbg开启动态调试. 方便起见, x32dbg附加到调试器后, 修改Ghidra的镜像基址为实际地址. (参考: Tutorial - How to rebase a module in Ghidra & IDA Pro | Guided Hacking )

Ghidra中搜索字符串"Input:"(注意: 只有在Listing窗口才可以搜索字符串), 跳转到该字符串交叉引用的代码处, 此时可以在反汇编窗口中得到主函数的伪代码, 抽取出其中的关键逻辑如下:

// .....  
  do {
    std::basic_istream<char,struct_std::char_traits<char>_>::operator>>
              ((basic_istream<char,struct_std::char_traits<char>_> *)cin_exref,local_38);
    uVar6 = __RTC_CheckEsp(extraout_ECX_00,extraout_EDX_00);
    local_1a0 = (int *)uVar6;
    std::ios_base::operator_bool((ios_base *)((int)local_1a0 + *(int *)(*local_1a0 + 4)));
    uVar7 = __RTC_CheckEsp(extraout_ECX_01,extraout_EDX_01);
    uVar10 = (undefined)in_stack_fffffde8;
    if ((uVar7 & 0xff) == 0) break;
    thunk_FUN_005ddac0(local_38);
    uVar6 = thunk_FUN_005dde80();
    uVar10 = (undefined)in_stack_fffffde8;
  } while ((int)uVar6 != 5);
  uVar6 = thunk_FUN_005d58a0(1);
  local_48 = *(uint *)uVar6;
  local_44 = ((uint *)uVar6)[1];
  uVar6 = thunk_FUN_005d58a0(2);
  local_58 = *(uint *)uVar6;
  local_54 = ((uint *)uVar6)[1];
  uVar6 = thunk_FUN_005d58a0(3);
  local_68 = *(uint *)uVar6;
  local_64 = ((uint *)uVar6)[1];
  uVar6 = thunk_FUN_005d58a0(4);
  uVar1 = *(uint *)uVar6;
  // local_78 = uVar1 + 0x939e98ab; // 这里是Ghidra自动生成的伪代码, 结合汇编码, 我进行
  // local_74 = (((uint *)uVar6)[1] - 0x66) - (uint)(uVar1 < 0x6c616755); // 了修改
  local_78 = *uVar1 - 0x6c616755;  // 这里是修改过的代码
  local_74 = uVar1[1] - 0x66; // 
  local_88 = thunk_FUN_005dcc90(local_78,local_74);
  uVar6 = thunk_FUN_005dde80();
  if ((int)uVar6 == 5) {
    iVar4 = local_48 - (uint)local_88;
    if ((iVar4 == 0x6369217d) &&
       ((local_44 - local_88._4_4_) - (uint)(local_48 < (uint)local_88) == 0x6153)) {
      iVar4 = local_58 - (uint)local_88;
      if ((iVar4 == 0x6531316f) &&
         ((local_54 - local_88._4_4_) - (uint)(local_58 < (uint)local_88) == 0x58)) {
        iVar4 = local_68 - (uint)local_88;
        if ((iVar4 == 0x31626f4e) &&
           ((local_64 - local_88._4_4_) - (uint)(local_68 < (uint)local_88) == 0x5f36)) {
          lVar2 = local_88 +
                  CONCAT44(local_44 + local_54 + (uint)CARRY4(local_48,local_58) + local_64 +
                           (uint)CARRY4(local_48 + local_58,local_68),local_48 + local_58 + local_68
                          );
          uVar3 = (undefined4)lVar2;
          uVar10 = (undefined)lVar2;
          if (lVar2 == 0xc121f9fcc23a) {
            puts("You are right!");
//.....

可以看到, 在程序的第3行会读取用户输入并在后续由函数thunk_FUN_005ddac0进行处理. 循环终止条件是变量uVar6的值等于5. 结合程序运行时的表现情况, 猜测该变量应该是输入的行数. 进一步推断函数thunk_FUN_005ddac0应该是存储每行输入.

继续分析, 第15行的函数thunk_FUN_005d58a0的返回值是程序正常运行的关键. 观察它的调用方式, 结合变量uVar6, 猜测该函数应该是对输入的数字按行进行处理.

第26行发现, 输入的第五行数字会经过单独的算法处理, 并会影响函数thunk_FUN_005dcc90的返回值local_88, 该变量对后续的程序逻辑也会产生影响.

静态分析发现挺复杂的, 遂决定直接用调试器调试. 首先分析下述代码:

  uVar6 = thunk_FUN_005d58a0(1);
  local_48 = *(uint *)uVar6;
  local_44 = ((uint *)uVar6)[1];

x32dbg中为语句MOV ECX,dword ptr [EAX]设置断点并运行, 随便输入五行数字, 程序将会断在断点处. 逐语句执行, 确定函数thunk_FUN_005d58a0确实会对输入的数字进行处理, 它会将输入的数字字符串转换成数字.

当输入123时, local_48会被赋值为123, local_44会赋值为0. 联想到程序为32位程序, 且后续代码中频繁出现"同样的运算有规律性的连续进行两次"的情况, 再结合后续出现的带借位减法指令sbb. 猜测local_44可能是存储的输入数字的高32位, local_48存储低32位. 重新运行程序并输入一个大于0xffffffff的数字测试, 发现该猜测属实.

Ghidra中重命名函数thunk_FUN_005d58a0parseLong. 继续分析函数thunk_FUN_005dcc90.

CALL       thunk_FUN_005dcc90                     # undefined8 thunk_FUN_005dcc90(ui
ADD        ESP ,0x8
MOV        dword ptr [EBP  + local_88 ],EAX
MOV        dword ptr [EBP  + local_84 ],EDX

如上述汇编代码所示, 函数thunk_FUN_005dcc90的返回值存储于寄存器EAXEDX中. 根据前述分析, 猜测这一组返回值应当对应某个数字的高/低32位.

根据Ghidra给出的伪代码并结合汇编代码, 整理得到如下代码片段:

  input5_low = input5[0] - 0x6c616755;  // input5 是输入的第五个数, [0]表示取低32位
  input5_hi = input5[1] - 0x66;          // [1]表示取高32位
  m_low = thunk_FUN_005dcc90(input5_low,input5_hi)[0];
  m_hi = thunk_FUN_005dcc90(input5_low,input5_hi)[0];
  len = get_input_length();
  if ((int)len == 5) {
    if ((input2_low - m_low == 0x6369217d) && ((input2_hi - m_hi) == 0x6153)) {
      if ((input3_low - m_low == 0x6531316f) && ((input3_hi - m_hi) == 0x58)) {
        if ((input4_low - m_low == 0x31626f4e) && ((input4_hi - m_hi) == 0x5f36)) {
          //...... you are right.

在上述代码的第1,2行可以看到flag的字样(0x666c6167). 猜测输入5可能为字符串"flag{"的十进制值. 根据代码构造input5为: 0x66,6c61677b, 十进制: 439904987003, ASCII: flag{ .

x32dbg重新运行程序, 设置必要断点, 输入input5, 可以得到m_low == 0; m_hi == 0x10.

因此根据上述伪代码, 可以得到正确的输入如下:

输入变量 高32位 低32位 输入值(10进制) ASCII
input1 任意 任意 任意 任意
input2 0x6153 + 0x10 0x6369217d 107079497490813 acci!}
input3 0x58 + 0x10 0x6531316f 448374321519 he11o
input4 0x5f36 + 0x10 0x31626f4e 104755080884046 _F1boN
input5 0x66 0x6c61677b 439904987003 flag{

正常运行程序, 输入上述值, 可验证正确.

image-20210926161012310

得到flag{he11o_F1boNacci!}


对函数thunk_FUN_005dcc90的分析如下所示:

正是因为对这个函数过于执着, 我比赛时间内根本没把这道题解出来...


Ghidra中重命名函数thunk_FUN_005d58a0parseLong. 继续分析函数thunk_FUN_005dcc90.

CALL       thunk_FUN_005dcc90                     # undefined8 thunk_FUN_005dcc90(ui
ADD        ESP ,0x8
MOV        dword ptr [EBP  + local_88 ],EAX
MOV        dword ptr [EBP  + local_84 ],EDX

如上述汇编代码所示, 函数thunk_FUN_005dcc90的返回值存储于寄存器EAXEDX中. 根据前述分析, 猜测这一组返回值应当对应某个数字的高/低32位. 跳转到函数thunk_FUN_005dcc90的汇编码, 从ret指令开始向上审计, 发现如下指令段实现了对上述两个寄存器赋值的操作:

MOV        EAX ,dword ptr [EBP + local_20 ]
MOV        EDX ,dword ptr [EBP + local_20 +0x4 ]
PUSH       EDX
MOV        ECX ,EBP
PUSH       EAX
LEA        EDX ,[DAT_005dce58 ]                        # = 01h
CALL       @_RTC_CheckStackVars@8                      # undefined @_RTC_CheckStackVars@8
POP        EAX
POP        EDX

可以看出, 变量local_20即为返回值, 在Ghidra中重命名该变量为ret.

该函数的伪代码主要部分如下所示:

//..... 
  if ((param_2 < 0) ||
     ((((param_2 < 1 && (param_1 < 2)) || (0 < param_2)) || ((-1 < param_2 && (200 < param_1)))))) {
    ret = 0;
  }
  else {
    thunk_FUN_005dcbb0(local_58,0x10);
    thunk_FUN_005d7440();
    thunk_FUN_005d5350(param_1);
    local_8 = 0;
    uVar10 = parseLong(0);
    *(undefined4 *)uVar10 = 1;
    ((undefined4 *)uVar10)[1] = 0;
    uVar10 = parseLong(1);
    *(undefined4 *)uVar10 = 1;
    ((undefined4 *)uVar10)[1] = 0;
    local_30 = 0;
    local_40 = 2;
    local_3c = 0;
    while( true ) {
      if ((param_2 < local_3c) || ((param_2 <= local_3c && (param_1 <= local_40)))) break;
      uVar10 = parseLong(local_40 - 1);
      uVar11 = parseLong(local_40 - 1);
      puVar6 = (uint *)uVar11;
      uVar1 = *(uint *)uVar10;
      uVar2 = *puVar6;
      uVar3 = *puVar6;
      uVar4 = ((uint *)uVar10)[1];
      uVar5 = puVar6[1];
      uVar10 = parseLong(local_40);
      *(int *)uVar10 = uVar1 + uVar3;
      ((int *)uVar10)[1] = uVar4 + uVar5 + (uint)CARRY4(uVar1,uVar2);
      uVar10 = parseLong(local_40);
      local_30 = *(undefined8 *)uVar10;
      bVar9 = 0xfffffffe < local_40;
      local_40 = local_40 + 1;
      local_3c = local_3c + (uint)bVar9;
    }
    ret = local_30;
    local_8 = 0xffffffff;
    thunk_FUN_005d5da0();
  }
/.......

To be continue....

#如无特别声明,该文章均为 ciaoly 原创,转载请遵循 署名-非商业性使用 4.0 国际(CC BY-NC 4.0) 协议,即转载请注明文章来源。
#最后编辑时间为: 2021 年 09 月 26 日


create 添加新评论


account_circle
email
language
textsms





关于 DreamCat

主题名称:DreamCat | 版本:X2.6.220211

主题开发:HanFengA7 | TeddyNight | Dev-Leo | CornWorld | WhiteBearcn | DFFZMXJ

Designed by HanFengA7 Power by Typecho

Copyright © 2015-2022 by LychApe All rights reserved!

加我的QQ
加我的微博
加我的支付宝
加我的微信