首页
论坛
课程
招聘
【ACTF2022】Inflated(C++异常处理控制流下的OLLVM混淆)
2022-7-21 18:03 12462

【ACTF2022】Inflated(C++异常处理控制流下的OLLVM混淆)

P.Z 活跃值
1
2022-7-21 18:03
12462

Inflated!!!

  1. C++异常化处理
  2. OLLVM-控制流平坦化
  3. Two Puzzles

Exception

一般碰到C++异常逆向,确定了异常分发、处理部分,直接把call throw改为jmp catch块,再F5即可

 

PS: 多个catch块根据rdx来当为异常处理数值决定哪个为对应的catch块

 

(关于以上,这篇讲的很详细)

https://4nsw3r.top/2022/02/03/SCTF-REVERSE-CplusExceptionEncrypt-%E8%B5%9B%E5%90%8E%E5%A4%8D%E7%8E%B0/#Clang-x64

 

然而,这题没这么简单,套了个ollvm!?基于异常处理的ollvm,无论从哪个角度都没法使用之前的老套路

 

耐心看完这两篇文章就会有所收获,对于此题的被异常处理搞乱掉的cfg就会有所理解

https://www.cnblogs.com/catch/p/3604516.html

https://www.cnblogs.com/catch/p/3619379.html

OLLVM

要是平常的ollvm都可以按照这篇来解决

https://bluesadi.github.io/0x401RevTrain-Tools/angr/10_%E5%88%A9%E7%94%A8angr%E7%AC%A6%E5%8F%B7%E6%89%A7%E8%A1%8C%E5%8E%BB%E9%99%A4%E6%8E%A7%E5%88%B6%E6%B5%81%E5%B9%B3%E5%9D%A6%E5%8C%96/

 

其他的原理讲的非常好,问题是这题并不是那么简单,但为了去ollvm我们的思路也是一样的,所以要对ollvm的cfg熟悉,并懂得我们该如何恢复一个被ollvm混淆后的代码

 

现在就开始写我对这题的看法!

 

参考Write up:

https://github.com/Lnkvct/CTF-for-Fun/blob/main/Challenges/Inflated-ACTF2022/writeup.md

https://www.cnblogs.com/FW-ltlly/p/16472171.html

lchild师傅的Write up(pdf所以没法给链接)

0x00 日常查壳

(感觉好久没写wp了)

 

无壳64位

 

图片描述

0x01 CFG

GETC

在讲这题ollvm与异常处理之前,有必要先搞懂我们到底是怎么输入的

 

一共有三处getc处理我们第一段输入的地方

1
2
3
407629
40553A(专门用来处理箭头)
405676(专门用来处理箭头)

程序最先开始运行的是 407629,这里我们可以输入上下左右箭头与特定的数字

  • 如果是数字,程序读取加密进行存放
  • 如果是箭头,会继续进行处理

(同时我们的输入还会决定异常类型)

Official Write up: The value of the first field of the thrown StdObfException object comes from the second input passed to the construct of StdObfException.

 

图片描述

 

(这图好大...有没有什么办法处理这些图片还有博客页面的方法)

 

那么异常处理先不深究,继续回来箭头如何处理这个问题

 

那么箭头其实为三字节码,上下左右箭头分别对应 ^[[A ^[[B ^[[C ^[[D

 

(此时开始动调,我第一次输入为上箭头,同时注意RAX)

 

那么在 407629 第一次处理箭头会读取为1B

 

图片描述

 

随后到 40553A 读取为5B

 

图片描述

 

最后到达 405676 可以发现我们的上箭头代码所对应的字符为A

 

图片描述

 

以上就解释了第一段输入的处理,等到最后解密第一段输入就会用到此

OLLVM

引用这张图,想要去掉ollvm最基本的是要认识这几个块

https://security.tencent.com/index.php/blog/msg/112

 

图片描述

 

先抛去原题,来认识一下这些名词

  1. 函数的开始地址为序言(Prologue)的地址
  2. 序言的后继为主分发器(Main dispatcher)
  3. 后继为主分发器的块为预处理器(Predispatcher)
  4. 后继为预处理器的块为真实块(Relevant blocks)
  5. 无后继的块为retn块
  6. 剩下的为无用块与子分发器(Sub dispatchers)

那参考文章,总结来说,利用angr符号执行去除控制流平坦化的步骤可以归结为三个步骤:

  1. 静态分析CFG得到序言/入口块(Prologue)、主分发器(Main dispatcher)、子分发器/无用块(Sub dispatchers)、真实块(Relevant blocks)、预分发器(Predispatcher)和返回块(Return)
  2. 利用符号执行恢复真实块的前后关系,重建控制流
  3. 根据第二步重建的控制流Patch程序,输出恢复后的可执行文件

简单来说就是获取所有的块,利用angr符号执行我们的真实块,查看真实块之间的流程,再抛去我们不要的块,patch程序,完成!

 

(那么具体的实现看文章)

https://bluesadi.github.io/0x401RevTrain-Tools/angr/10_%E5%88%A9%E7%94%A8angr%E7%AC%A6%E5%8F%B7%E6%89%A7%E8%A1%8C%E5%8E%BB%E9%99%A4%E6%8E%A7%E5%88%B6%E6%B5%81%E5%B9%B3%E5%9D%A6%E5%8C%96/

 

然而这题...,根本不像啊!可以看出这题的CFG根本看不懂,不像单单ollvm混淆过的cfg那么漂亮

 

图片描述

Exception

为了搞懂CFG为什么成这样了,得先了解下异常的原理,参考原文

https://www.cnblogs.com/catch/p/3604516.html

 

对于最基本的thown catch不再赘述,这篇讲到很清楚

https://4nsw3r.top/2022/02/03/SCTF-REVERSE-CplusExceptionEncrypt-%E8%B5%9B%E5%90%8E%E5%A4%8D%E7%8E%B0/#Clang-x64

异常抛出后,发生了什么事情?

  1. 如果当前函数没有catch,就沿着函数的调用链继续往上抛,然后出现两种情况

    • 在某个函数中找到相应的catch

    • 没找到相应的catch,调用 std::terminate() (这个函数是把程序abort)

  2. 如果想找到了相应的catch,执行相应的操作

    • 程序中catch的代码块有个专有名词:Landing pad
  3. 从抛异常到开始 -> 执行Landing pad代码 这整个过程叫作Stack unwind

Stack unwind

  1. 从抛异常函数开始,对调用链上的函数逐个往前查找Landing pad
  2. 如果没有找到Landing pad则把程序abort,如果找到则记下Landing pad的位置,再重新回到抛异常的函数那里开始,一帧一帧地清理调用链上各个函数内部的局部变量,直到 landing pad 所在的函数为止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void func1()
{
  cs a; // stack unwind时被析构。
  throw 3;
}
 
void func2()
{
  cs b;
  func1();
}
 
void func3()
{
  cs c;
  try
  {
    func2();
  }
  catch (int)
  {
    //进入这里之前, func1, func2已经被unwind.
  }
}

stack unwind的过程可以简单看成函数调用的逆过程,这个过程在实现上由一个专门的stack unwind库来实现

  • stack unwind库在intel平台上

  • 属于Itanium ABI 接口中的一部分

  • 与具体的语言无关,由系统实现
  • 任何上层语言都可以通过这个接口的基础实现各自的异常处理
  • GCC就是通过这个接口实现C++的异常处理

Itanium C++ ABI

ltanium C++ ABI定义了一系列函数以及数据结构来建立整个异常处理的流程及框架,主要函数包括以下列:

1
2
3
4
5
6
7
8
9
10
_Unwind_RaiseException,
_Unwind_Resume,
_Unwind_DeleteException,
_Unwind_GetGR,
_Unwind_SetGR,
_Unwind_GetIP,
_Unwind_SetIP,
_Unwind_GetRegionStart,
_Unwind_GetLanguageSpecificData,
_Unwind_ForcedUnwind

其中 _Unwind_RaiseException() 函数进行stack unwind,它在用户执行throw的时被调用

 

主要功能

  • 从当前函数开始,对调用链上的每一个函数都调用一个叫做 personality routine 的函数(__gxx_personality_v0)

    • personality routine 该函数由上层的语言定义及提供实现
  • _Unwind_RaiseException() 会在内部把函数栈调用现场重现,然后传给 personality routine,该函数主要做两件事情

    1. 检查当前函数是否有相对应的catch
    2. 清理调用栈上的局部变量

那么稍稍总结一下,就是当程序抛出异常就要进行 stack unwind 操作

 

而这个操作具体是 _Unwind_RaiseException() 中的 personality routine() 实现了检查catch和清理栈上的局部变量

C++ ABI

基于前面介绍的 ltanium ABI,编译器层面也定义了一系列 ABI 与之交互

 

当我们在代码中写下 throw xxx,编译器会分配一个数据结构 __cxa_exception 来表示该异常,该异常也有一个头部,定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct __cxa_exception
{
  std::type_info *    exceptionType;
  void (*exceptionDestructor) (void *);
  unexpected_handler    unexpectedHandler;
  terminate_handler    terminateHandler;
  __cxa_exception *    nextException;
 
  int     handlerCount;
  int     handlerSwitchValue;
  const char *     actionRecord;
  const char *     languageSpecificData;
  void *     catchTemp;
  void *     adjustedPtr;
 
  _Unwind_Exception    unwindHeader;
};

当用户 throw 一个异常时,编译器会帮我们调用相应的函数分配出如下的结构

 

图片描述

 

其中 __cxa_exception 就是头部,exception_obj 则是 "throw xxx" 中的 xxx,这两部分在内存中是连续的。

  • 异常对象由函数 __cxa_allocate_exception() 进行创建

  • 最后由 __cxa_free_exception() 进行销毁

当我们在程序里执行了抛出异常的操作,编译器为我们做了如下的事情:

  1. 调用 cxa_allocate_exception 函数,分配一个异常对象(cxa_exception,数据结构如上)
  2. 调用 __cxa_throw 函数,这个函数会将异常对象做一些初始化
  3. __cxa_throw() 调用 Itanium ABI 里的 _Unwind_RaiseException() 从而开始 unwind
  4. _Unwind_RaiseException() 对调用链上的函数进行 unwind 时,调用 personality routine()
  5. 该异常如能被处理(有相应的 catch),则 personality routine 会依次对调用链上的函数进行清理
  6. _Unwind_RaiseException() 将控制权转到相应的catch代码
  7. unwind 完成,用户代码继续执行

总结太Bravo了!

再看异常处理

有了这些前置知识,再看题目中的异常,由前面描述可知实现 unwind stack 的具体过程是通过 __gxx_personality_v0(即personality routine)实现

 

这时候我们再去IDA里调整此函数

1
2
3
4
5
6
_Unwind_Reason_Code __fastcall _gxx_personality_v0(
        int Version,
        _Unwind_Action actions,
        __int64 exceptionClass,
        _Unwind_Exception *exceptionObject,
        _Unwind_Context *context)

光标在函数,按Y修改类型

 

图片描述

 

scan_eh_tab

 

回忆__gxx_personality_v0函数功能

  1. 检查当前函数是否有相应的 catch 语句。
  2. 清理当前函数中的局部变量。

在personality routine()下的 scan_eh_tab() 该函数有我们最关心的两个值,同时也是魔改处

与源码对比:https://code.woboq.org/llvm/libcxxabi/src/cxa_personality.cpp.html#__cxxabiv1::scan_eh_tab

 

Shfit + F1 -> INS 导入结构体

1
2
3
4
5
6
7
8
9
struct scan_results
{
int64_t ttypeIndex;
const uint8_t* actionRecord;
const uint8_t* languageSpecificData;
uintptr_t landingPad;
void* adjustedPtr;
_Unwind_Reason_Code reason;
};

光标在scan_eh_tab函数上按Y修改

1
void scan_eh_tab(scan_results *results, _Unwind_Action actions, bool native_exception, _Unwind_Exception *unwind_exception, _Unwind_Context *context)

Landing pad

Landing pad(指向catch块的分发处,只单单拿到landing pad还不够,这时候还缺少一个对应异常类型ttypeIndex)

 

图片描述

ttypeIndex

首先要求父类为StdObfException的异常

 

最后的ttypeIndex由 thrown_object_ptr(由我们的第一段输入所决定的thrown_object_ptr) 和 原始固定固定typeIndex 决定

 

图片描述

Official Write up: And we have figured out that the ttypeIndex is determined by the first field of the thrown StdObfException object and the lptinfo passed to __cxa_throw. The value of the first field of the thrown StdObfException object comes from the second input passed to the construct of StdObfException.

 

那么这两个值到底具体指的是什么??

 

其实上面已经给出了答案,反复调试可知,可以发现我们的第一段输入设置了父类StdObfException

 

the first field of the thrown StdObfException object 指的就是我们的输入

 

the lptinfo passed to __cxa_throw 指的就是当 ___cxa_allocate_exception 创建的异常,也就是固定的

 

图片描述

 

现在知道了魔改后的流程是从哪里来到哪里去,人工方式就是跳到landing pad再设置rdx为ttypeIndex就可以到达我们所对应的catch块

什么叫CFG!

那么现在知道了routine personality 中的 scan_eh_tab被修改了,而IDA平常能识别throw catch这些块的原因就是这些正常的源码

 

然而landingpad与ttypeIndex都被修改了,所以导致了IDA识别的CFG成了这个样子

 

我们根本没法用肉眼知道throw的块在哪,只有通过动调才能确定,然而这就导致了原先的deflat脚本都不不行了

 

原因主要为两点

  1. 无法确定throw后的块
  2. throw可能对着多个catch块,这时候就通过rdi(ttypeIndex)进行catch块分发(landingPad)

原因还有种种就不一一举例,就无法正常原先deflat所需要的CFG块

 

图片描述

 

以下开始就是跟着官方脚本复现

 

我们再回忆一下正常的ollvm的执行流程

 

Prologue(入口块)-> Main dispatcher(主分发器)-> Sub dispathers(子分发器)-> Relevant blocks(真实块)-> Predispather(预分发器)-> Main dispatcher(主分发器)...

 

总结一下这道题的CFG

 

我们的下一个真实块取决于系统生产的lptinfo和我们的第一段输入所导致的StdObfException,在每个真实块的结束,我们不只是跳往与预分发器,而是调用 __cxa_throw 进行第二次调度,我们称二次调用为 second dispatch

 

所以我们的执行流就是

 

... -> main dispatcher -> sub dispatchers -> relevant block -> throw StdObfException exception -> Secondary dispatchers -> pre-dispatcher -> main dispatcher -> ...

 

除此之外,程序还抛出了一些真正的异常,对于这些异常,第二次调用发生于Landing pad末尾

 

... -> main dispatcher -> sub dispatchers -> relevant block that throws real exceptions -> the according real LandingPad block -> throw StdObfException exception -> Secondary dispatchers -> pre-dispatcher -> main dispatcher -> ...

0x02 Deflat Solution

去该平坦化控制流,有两个步骤:

  1. 找到所有的真实块
  2. 找到真实块之间的关系

Find all relevant blocks

我们可以从主分发器开始寻找,找到所有子分发器的后继者,这些后继者本身不是子分发器

 

官方WP中一眼丁真发现子分发器由该指令格式组成

1
2
3
sub dispathers such as:
cmp
jx

于是由此区别出来

1
2
3
4
5
6
7
isCmpRI = lambda instr: instr.mnemonic == "cmp" and\
  hasattr(instr.operands[0], "_X86RegisterOperand__key") and\
  hasattr(instr.operands[1], "_X86ImmediateOperand__key")
isCJmp = lambda instr: instr.mnemonic.startswith("j") and \
  instr.mnemonic != "jmp"
isSubDispatcher = lambda bb: (len(bb.instrs) == 2) and\
   isCmpRI(bb.instrs[0]) and isCJmp(bb.instrs[1])

首先判断是否为子分发器,然后排除法找到所有真实块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class PatchHelper:
  ## ......
  # To get all cfgs
  def block(self, addr):
    bb = self.cfg.find_basic_block(addr)
    if bb is None:
      bb = barf.bb_builder.strategy._disassemble_bb(addr, barf.binary.ea_end, {})
    return bb
 
def get_relevant_blocks(cfg, patch_helper, main_dispatcher):
  isCmpRI = lambda instr: instr.mnemonic == "cmp" and\
    hasattr(instr.operands[0], "_X86RegisterOperand__key") and\
    hasattr(instr.operands[1], "_X86ImmediateOperand__key")
  isCJmp = lambda instr: instr.mnemonic.startswith("j") and \
    instr.mnemonic != "jmp"
  isSubDispatcher = lambda bb: (len(bb.instrs) == 2) and\
     isCmpRI(bb.instrs[0]) and isCJmp(bb.instrs[1])
  relevant_blocks = []
  visited = set()
  q = SimpleQueue()
  q.put(patch_helper.block(main_dispatcher))
  while not q.empty():
    bb = q.get()
    # Either Sub Patchers or Relevant blocks?
    if isSubDispatcher(bb):
      for succ, cond in bb.branches:
        if succ in visited:
          continue
        q.put(patch_helper.block(succ))
        visited.add(succ)
    else:
      relevant_blocks.append(bb)
  return relevant_blocks

Relevant blocks:

1
2
3
*******************relevant blocks************************
main_dispatcher:0x404a80
relevant_blocks: ['0x409437', '0x406443', '0x404ab8', '0x408031', '0x407842', '0x407d31', '0x407437', '0x407f4f', '0x4076bd', '0x407a6b', '0x40723e', '0x407fc4', '0x409458', '0x407bc7', '0x40732f', '0x407ebc', '0x407566', '0x407960', '0x4070fa', '0x405e7a', '0x4078e3', '0x407e5a', '0x4074ca', '0x405c87', '0x407741', '0x407af5', '0x4072b4', '0x405ded', '0x4077b6', '0x407c6b', '0x4073a4', '0x405b29', '0x4075f9', '0x407a06', '0x4071aa', '0x406cfe', '0x406c94', '0x406ef0', '0x406859', '0x40707d', '0x406b62', '0x406f5f', '0x4065c9', '0x406e5d', '0x406a72', '0x406d7b', '0x406704', '0x406def', '0x406964', '0x40944b', '0x4064a5', '0x405469', '0x405a5f', '0x404fae', '0x40532c', '0x40589c', '0x404d58', '0x4053d3', '0x405923', '0x404ec5', '0x40529a', '0x4057b8', '0x404bc4', '0x405f2a', '0x4056f0', '0x406299', '0x4068f0', '0x4063b0', '0x406bf9', '0x406323', '0x406646', '0x40620f', '0x406b00', '0x4060e7', '0x4067bb', '0x40617c', '0x4069e3', '0x40606d', '0x406521', '0x4051fe', '0x405647', '0x404e14', '0x4055b5', '0x4050cc', '0x40550b', '0x404ca4']

Find the flow

官网WP指出抽象出来)留个坑,以后熟了试试

Official Write up: A good idea is to abstract the throw StdObfException -> catch process and do the one basic block symbolic execution (You can refer to Deobfuscation: recovering an OLLVM-protected program or 利用符号执行去除控制流平坦化 for more information).

 

于是官网WP又给了个更有趣的方法,GDB脚本!

 

为了找到真实块之间的流程,通过普通的执行然后打印真实块需要的信息!

 

但是我们不一样能得到所有的流程因为部分可能没执行到,但是我们依然可以利用提取出来的信息去恢复部分控制流,并弄清楚如何输入可以恢复更多流程。(怎么好像梦到过我在这写wp...)

 

生成GDB的脚本如下

  • 40A3D4为我们catch块地址
  • _ZN18StdSubObfExceptionC2Ec为了打印异常类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
cmds = """\
set pagination off
 
b *0x40A3D4
commands
  silent
  printf "landingPad: %x\\n", $rdx
  continue
end
 
b _ZN18StdSubObfExceptionC2Ec
commands
  silent
  printf "selector: %x\\n", $rsi
  continue
end
 
define mytrace
  break $arg0
  commands
    silent
    printf "%x\\n", $pc
    python gdb.execute('continue')
  end
end
"""
for bb in relevant_blocks:
    cmds += (f"mytrace *{hex(bb.address)} \n")
cmds += "run\n"
with open("test.gdb", "w") as f:
    f.write(cmds)
1
2
3
4
cat teatin
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
 
gdb inflated -x test.gdb --batch < testin > testout

于是可以获取真实块接下来的landing pad与异常类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Breakpoint 1 at 0x40a3d4
......
Breakpoint 88 at 0x404ca4
4075f9
selector: 0
landingPad: 4089bf
4072b4
selector: 0
landingPad: 408503
4075f9
selector: 2
landingPad: 4089bf
4060e7
selector: 0
......
40617c
selector: 0
landingPad: 409100
409437
[Inferior 1 (process 13732) exited normally]

然后就写个PARSER分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def parse_logs(logfn, prologue, patch_helper):
  with open(logfn, "r") as f:
    t = f.readlines()
  i = 0
  selector_s = "selector: "
  landingpad_s = "landingPad: "
  relations = set()
  laddr = prologue
  lselector = 0
  landingpad = 0
  while i < len(t):
    try:
      addr = int(t[i], 16)
    except:
      i += 1
      continue
    if not laddr is None:
      relations.add((laddr, lselector, addr))
    if t[i+1].startswith(selector_s):
      selector = int(t[i+1][len(selector_s):], 16)
      i += 2
    elif t[i+1].startswith(landingpad_s):
      landingpad = int(t[i+1][len(landingpad_s):], 16)
      relations.add((addr, -1, landingpad))
      addr = landingpad
      while not patch_helper.is_unreachable(patch_helper.block(addr).direct_branch):
        addr = patch_helper.block(addr).direct_branch
      if t[i+2].startswith(selector_s):
        selector = int(t[i+2][len(selector_s):], 16)
      i += 3
    elif t[i+1].startswith("[Inferior "):
      i += 1
    else:
      print("Warning: %x doesn't have selector. "%addr)
      exit(0)
    laddr = addr
    lselector = selector
  return list(relations)
 
print('************************flow******************************')
relations = parse_logs(sys.argv[3], prologue, patch_helper)
relations.sort(key = lambda x:x)
flow = {}
for bb, selector, child in relations:
  if bb in flow:
    while len(flow[bb]) < selector:
      flow[bb].append(-1)
    flow[bb].append(child)
    assert(len(flow[bb]) == selector+1)
  else:
    flow[bb] = [child]
for (k, v) in list(flow.items()):
    print('%#x:' % k, [hex(child) for child in v])

Flows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
************************flow******************************
0x404820: ['0x4075f9']
0x404ab8: ['0x404ab8', '0x406c94']
0x404bc4: ['0x407bc7']
0x404ca4: ['0x406bf9']
0x404ec5: ['0x4053d3']
0x404fae: ['0x406b00']
0x4051fe: ['0x40707d']
0x4053d3: ['0x406521']
0x405469: ['0x407d31']
0x4056f0: ['0x405a5f', '0x4056f0']
0x4057b8: ['0x404ab8']
0x405923: ['0x405923', '0x406e5d']
0x405a5f: ['0x4067bb']
0x405b29: ['0x406964', '0x406646']
0x405c87: ['0x405c87', '0x407437']
0x405f2a: ['0x405f2a', '0x4063b0']
0x4060e7: ['0x40723e']
0x40617c: ['0x409437']
0x40620f: ['0x405f2a']
0x406299: ['0x404bc4', '0x4057b8']
0x4063b0: ['0x4063b0', '0x405469']
0x4064a5: ['0x406704', '0x40620f']
0x406521: ['0x4074ca', '0x404bc4']
0x4065c9: ['0x40723e']
0x406646: ['0x406964']
0x406704: ['0x405c87']
0x4067bb: ['0x4082b6']
0x406964: ['0x405b29', '0x404ca4']
0x4069e3: ['0x408281']
0x406a72: ['0x404fae']
0x406b00: ['0x406299']
0x406bf9: ['0x405923']
0x406c94: ['0x4074ca']
0x406cfe: ['0x40723e']
0x406e5d: ['0x406e5d', '0x4077b6']
0x406f5f: ['0x406f5f', '0x407566']
0x40707d: ['0x40707d', '0x407960']
0x4070fa: ['0x406f5f']
0x4071aa: ['0x4056f0']
0x40723e: ['0x4072b4']
0x4072b4: ['0x4075f9', '0x4071aa']
0x407437: ['0x407437', '0x4064a5']
0x4074ca: ['0x404ec5', '0x407c6b']
0x407566: ['0x407566', '0x407a6b']
0x4075f9: ['0x4072b4', '-0x1', '0x4060e7', '0x406cfe', '0x4078e3', '0x4065c9']
0x4076bd: ['0x404ec5']
0x4077b6: ['0x406bf9', '0x4070fa']
0x4078e3: ['0x40723e']
0x407960: ['0x4081f5']
0x407a6b: ['0x4070fa', '0x406704']
0x407bc7: ['0x406a72', '0x407bc7']
0x407c6b: ['0x4069e3']
0x407d31: ['0x407d31', '0x407ebc']
0x407ebc: ['0x407ebc', '0x40617c']
0x4081f5: ['0x405b29']
0x408281: ['0x4051fe']
0x4082b6: ['0x4076bd']

Patch

修复程序环节!

  • 当我们已经确定了执行流程,像抛异常 子分发器什么都是多余的了,统统patch掉

  • 对于后继块只有一个的真实块,只需要jmp过去

  • 对于有多个后继块的,需要通过esi(也就是异常类型)来改成cmp esi, ... jz即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def patch_branches(self, bb, va_targets):
  va_start, size = self.get_patchable_from_relblk(bb)
  if size < PatchHelper.JMP_SIZE:
    print("[Warning] patch_jmp at block %x may fail. size: %d."%(bb.address, size))
  org_start = va_start
  print(f"va_start: {hex(va_start)}, bb addr: {hex(bb.address)}, size: {size}")
  ## `cmp esi, v` instr takes 3 bytes while `je xxx` takes 6 bytes
  ## And the last jmp instr takes 5 bytes.
  total_size = 9 * len(va_targets) - 4
  if size < total_size:
    ## If the nop block at the end of current block is not large enough,
       ## try to find another nop block and then jump to it.
    nx_va_start, nx_size = self.get_nop_by_size(total_size)
    if nx_size == 0:
      print("[Error] `patch_branches` needs a nop block with size larger than %d."%(total_size))
    self.patch_jmp(va_start, nx_va_start)
    va_start, size = nx_va_start, nx_size
 
  for i, t in enumerate(va_targets[:-1]):
    cmp_instr = bytes([0x83,0xfe,i])
    self.do_patch(va_start, cmp_instr)
    va_start += len(cmp_instr)
    cj_instr = bytes([PatchHelper.opcode['j'],PatchHelper.opcode['e']])
    if t == -1:
      ## -1 represent that we do not know the flow for this selector value for now.
      cj_instr += struct.pack('<i', self.func_terminate-va_start-6)
      # cj_instr = asm(f"je {hex(self.func_terminate)}", vma=va_start)
    else:
      cj_instr += struct.pack('<i', t-va_start-6)
      # cj_instr = asm(f"je {hex(t)}", vma=va_start)
    self.do_patch(va_start, cj_instr)
    va_start += len(cj_instr)
  va_start += self.patch_jmp(va_start, va_targets[-1])
  if va_start > org_start+size:
    print("[Warning] patches at (%x, %x) overlaps next blk. "%(org_start, va_start))

官方完整脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
## filename: deflat.py
from ast import Tuple
from xmlrpc.client import Boolean
from barf.barf import BARF
import angr
import struct
import sys
from pwnlib import elf
from queue import SimpleQueue
# from pwn import *
 
class PatchHelper:
  opcode = {'a' :0x87, 'ae':0x83, 'b' :0x82, 'be':0x86, 'c' :0x82, 'e' :0x84, 'z' :0x84, 'g' :0x8F,
            'ge':0x8D, 'l' :0x8C, 'le':0x8E, 'na':0x86, 'nae':0x82,'nb':0x83, 'nbe':0x87,'nc':0x83,
            'ne':0x85, 'ng':0x8E, 'nge':0x8C,'nl':0x8D, 'nle':0x8F,'no':0x81, 'np':0x8B, 'ns':0x89,
            'nz':0x85, 'o' :0x80, 'p' :0x8A, 'pe':0x8A, 'po':0x8B, 's' :0x88, 'nop':0x90,'jmp':0xE9, 'j':0x0F}
  JMP_SIZE = 5
 
  def is_unreachable(self, bb):
    if isinstance(bb, int):
      bb = self.block(bb)
    for i in range(len(bb.instrs)):
      if bb.instrs[i].mnemonic != "call":
        continue
      target = bb.instrs[i].operands[0].immediate
      if target == self.func_terminate:
        return True
 
  def block(self, addr):
    bb = self.cfg.find_basic_block(addr)
    if bb is None:
      bb = barf.bb_builder.strategy._disassemble_bb(addr, barf.binary.ea_end, {})
    return bb
 
  @staticmethod
  def is_imm(operand):
    return (hasattr(operand, "_X86ImmediateOperand__key"))
 
  @staticmethod
  def is_reg(operand):
    return (hasattr(operand, "_X86RegisterOperand__key"))
 
  def is_call_throw(self, instr):
    return instr.mnemonic == "call" and \
        self.is_imm(instr.operands[0]) and\
        instr.operands[0].immediate == self.func_throw
 
  def is_call_allocate_exception(self, instr):
    return instr.mnemonic == "call" and \
        self.is_imm(instr.operands[0]) and\
        instr.operands[0].immediate == self.func_allocate_exception
 
  def is_call_obf_exception(self, instr):
    return instr.mnemonic == "call" and \
        self.is_imm(instr.operands[0]) and\
        instr.operands[0].immediate == self.func_obf_exception
 
 
  def skip_call_args(self, bb, i):
    while ((bb.instrs[i].mnemonic in ["xor","mov","lea"]) and\
      (len(bb.instrs[i].operands) > 0) and (self.is_reg(bb.instrs[i].operands[0])) and\
      (bb.instrs[i].operands[0].name in ["edx", "rdx", "esi", "rsi", "edi", "rdi"])) or \
      bb.instrs[i].mnemonic == "nop":
      i -= 1
    return i
 
  def get_patchable_from_relblk(self, bb):
    i = 0
    end = bb.start_address + bb.size
    while i < len(bb.instrs) and not self.is_call_throw(bb.instrs[i]):
      i += 1
    i = self.skip_call_args(bb, i-1)
    if i == len(bb.instrs) - 1:
      start = end
    else:
      start = bb.instrs[i+1].address
    self.fill_nops(start, end)
    return (start, end-start)
 
  def __init__(self, proj, elf, barf, cfg) -> None:
    self.p = proj
    obj = proj.loader.main_object
    self.func_terminate = obj.symbols_by_name["__clang_call_terminate"].rebased_addr
    self.func_throw = obj.plt["__cxa_throw"]
    self.func_allocate_exception = obj.plt["__cxa_allocate_exception"]
    self.func_obf_exception = obj.symbols_by_name["_ZN18StdSubObfExceptionC2Ec"].rebased_addr
    self.elf = elf
    self.elfData = bytearray(self.elf.data)
    self.barf = barf
    self.cfg = cfg
    self.nops = []
 
  def append_nop(self, nopblk):
    if nopblk[1] > 0:
      self.nops.append(nopblk)
 
  def finalize(self):
    self.nops.sort()
    idx = 0
    while idx < len(self.nops) - 1:
      if self.nops[idx][0] + self.nops[idx][1] != self.nops[idx+1][0]:
        idx += 1
        continue
      self.nops[idx]=(self.nops[idx][0], self.nops[idx][1]+self.nops[idx+1][1])
      del self.nops[idx+1]
 
  def fill_nops(self, va_start, va_end):
    assert not self.elf is None
    start = self.elf.vaddr_to_offset(va_start)
    end   = self.elf.vaddr_to_offset(va_end)
    for i in range(start, end):
      self.elfData[i] = PatchHelper.opcode['nop']
 
  def get_nop_by_size(self, min_size):
    for idx, nop in enumerate(self.nops):
      if nop[1] > min_size:
        del self.nops[idx]
        return nop
    return (-1, 0)
 
  def do_patch(self, va_start, codes):
    start = self.elf.vaddr_to_offset(va_start)
    for i in range(len(codes)):
      self.elfData[start+i] = codes[i]
 
  def patch_jmp(self, va_start, va_target):
    offset = va_target - va_start - PatchHelper.JMP_SIZE
    jmp = bytes([PatchHelper.opcode['jmp']])+struct.pack('<i', offset)
    self.do_patch(va_start, jmp)
    return PatchHelper.JMP_SIZE
 
  def patch_branches(self, bb, va_targets):
    va_start, size = self.get_patchable_from_relblk(bb)
    if size < PatchHelper.JMP_SIZE:
      print("[Warning] patch_jmp at block %x may fail. size: %d."%(bb.address, size))
    org_start = va_start
    print(f"va_start: {hex(va_start)}, bb addr: {hex(bb.address)}, size: {size}")
    ## `cmp esi, v` instr takes 3 bytes while `je xxx` takes 6 bytes
    ## And the last jmp instr takes 5 bytes.
    total_size = (3+6) * len(va_targets) - 4
    if size < total_size:
      ## If the nop block at the end of current block is not large enough,
         ## try to find another nop block and then jump to it.
      nx_va_start, nx_size = self.get_nop_by_size(total_size)
      if nx_size == 0:
        print("\033[31m[Error]\033[0m `patch_branches` needs a nop block with size larger than %d."%(total_size))
      self.patch_jmp(va_start, nx_va_start)
      va_start, size = nx_va_start, nx_size
    for i, t in enumerate(va_targets[:-1]):
      cmp_instr = bytes([0x83,0xfe,i])
      self.do_patch(va_start, cmp_instr)
      va_start += len(cmp_instr)
      cj_instr = bytes([PatchHelper.opcode['j'],PatchHelper.opcode['e']])
      if t == -1:
        ## -1 represent that we do not know the flow for this selector value for now.
        cj_instr += struct.pack('<i', self.func_terminate-va_start-6)
        # cj_instr = asm(f"je {hex(self.func_terminate)}", vma=va_start)
      else:
        cj_instr += struct.pack('<i', t-va_start-6)
        # cj_instr = asm(f"je {hex(t)}", vma=va_start)
      self.do_patch(va_start, cj_instr)
      va_start += len(cj_instr)
    va_start += self.patch_jmp(va_start, va_targets[-1])
    if va_start > org_start+size:
      print("[Warning] patches at (%x, %x) overlaps next blk. "%(org_start, va_start))
 
def get_relevant_blocks(cfg, patch_helper, main_dispatcher):
  isCmpRI = lambda instr: instr.mnemonic == "cmp" and\
    hasattr(instr.operands[0], "_X86RegisterOperand__key") and\
    hasattr(instr.operands[1], "_X86ImmediateOperand__key")
  isCJmp = lambda instr: instr.mnemonic.startswith("j") and \
    instr.mnemonic != "jmp"
  isSubDispatcher = lambda bb: (len(bb.instrs) == 2) and\
     isCmpRI(bb.instrs[0]) and isCJmp(bb.instrs[1])
  relevant_blocks = []
  visited = set()
  q = SimpleQueue()
  q.put(patch_helper.block(main_dispatcher))
  while not q.empty():
    bb = q.get()
    if isSubDispatcher(bb):
      patch_helper.append_nop((bb.start_address, bb.size))
      for succ, cond in bb.branches:
        if succ in visited:
          continue
        q.put(patch_helper.block(succ))
        visited.add(succ)
    else:
      relevant_blocks.append(bb)
  return relevant_blocks
 
 
def parse_logs(logfn, prologue, patch_helper):
  with open(logfn, "r") as f:
    t = f.readlines()
  i = 0
  selector_s = "selector: "
  landingpad_s = "landingPad: "
  relations = set()
  laddr = prologue
  lselector = 0
  landingpad = 0
  while i < len(t):
    try:
      addr = int(t[i], 16)
    except:
      i += 1
      continue
    if not laddr is None:
      relations.add((laddr, lselector, addr))
    if t[i+1].startswith(selector_s):
      selector = int(t[i+1][len(selector_s):], 16)
      i += 2
    elif t[i+1].startswith(landingpad_s):
      landingpad = int(t[i+1][len(landingpad_s):], 16)
      relations.add((addr, -1, landingpad))
      addr = landingpad
      while not patch_helper.is_unreachable(patch_helper.block(addr).direct_branch):
        addr = patch_helper.block(addr).direct_branch
      if t[i+2].startswith(selector_s):
        selector = int(t[i+2][len(selector_s):], 16)
      i += 3
    elif t[i+1].startswith("[Inferior "):
      i += 1
    else:
      print("Warning: %x doesn't have selector. "%addr)
      exit(0)
    laddr = addr
    lselector = selector
  return list(relations)
 
 
def generate_gdb_script(relevant_blocks):
  cmds = """\
set pagination off
 
b *0x40A3D4
commands
  silent
  printf "landingPad: %x\n", $rdx
  continue
end
 
b _ZN18StdSubObfExceptionC2Ec
commands
  silent
  printf "selector: %x\n", $rsi
  continue
end
 
define mytrace
  break $arg0
  commands
    silent
    printf "%x\\n", $pc
    python gdb.execute('continue')
  end
end
"""
  for bb in relevant_blocks:
    cmds += (f"mytrace *{hex(bb.address)} \n")
  cmds += "run\n"
  with open("test.gdb", "w") as f:
    f.write(cmds)
 
 
if __name__ == '__main__':
    if len(sys.argv) < 3:
        print('Usage: python deflat.py filename function_address(hex) [logfile]')
        exit(0)
 
    # context.arch = "amd64"
    # context.os = "linux"
    # context.endian = "little"
 
    filename = sys.argv[1]
    start = int(sys.argv[2], 16)
 
    origin = elf.ELF(filename)
    b = angr.Project(filename, load_options={'auto_load_libs': False, 'main_opts':{'custom_base_addr': 0}})
    barf = BARF(filename)
    cfg = barf.recover_cfg(start=start)
    patch_helper = PatchHelper(b, origin, barf, cfg)
    blocks = cfg.basic_blocks
 
    prologue = start
    main_dispatcher = patch_helper.block(prologue).direct_branch
    relevant_blocks = get_relevant_blocks(cfg, patch_helper, main_dispatcher)
    nop = patch_helper.get_patchable_from_relblk(patch_helper.block(prologue))
    patch_helper.append_nop(nop)
 
    print('*******************relevant blocks************************')
    print('main_dispatcher:%#x' % main_dispatcher)
    print('relevant_blocks:', [hex(bb.address) for bb in relevant_blocks])
 
 
    if len(sys.argv) < 4:
      generate_gdb_script(relevant_blocks)
      exit(0)
 
    print('************************flow******************************')
    relations = parse_logs(sys.argv[3], prologue, patch_helper)
    relations.sort(key = lambda x:x)
    flow = {}
    for bb, selector, child in relations:
      if bb in flow:
        while len(flow[bb]) < selector:
          flow[bb].append(-1)
        flow[bb].append(child)
        assert(len(flow[bb]) == selector+1)
      else:
        flow[bb] = [child]
    for (k, v) in list(flow.items()):
        print('%#x:' % k, [hex(child) for child in v])
 
    print('************************patch*****************************')
    patch_helper.finalize()
    for (parent, childs) in list(flow.items()):
      ## Patch jmps
      blk = patch_helper.block(parent)
      patch_helper.patch_branches(blk, childs)
      ## Nop call allocate_exception and call obf_exception
      for idx, instr in enumerate(blk.instrs):
        if patch_helper.is_call_allocate_exception(instr) or\
          patch_helper.is_call_obf_exception(instr):
          # si = patch_helper.skip_call_args(blk, idx-1)+1
          # start = blk.instrs[si].address
          start = instr.address
          end = instr.address + instr.size
          patch_helper.fill_nops(start, end)
 
    with open(filename + '.recovered', 'wb') as f:
        f.write(bytes(patch_helper.elfData))
    print('Successful! The recovered file: %s' % (filename + '.recovered'))

Work flow:

1
2
3
$ python deflat.py inflated 0x404820
$ gdb inflated -x test.gdb --batch < testin > testout
$ python deflat.py inflated 0x404820 testout

(按照以上流程,test.gdb可能会报个错,程序把本身有个\n是脚本中需要打印的,但直接转义成真换行了需要手动恢复)

 

观看修复后的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
int __cdecl main(int argc, const char **argv, const char **envp)
{
  ......
  v3 = fileno(stdin);
  tcgetattr(v3, &intermiosBufBackup);
  cfmakeraw(&intermiosBuf);
  tcsetattr(v3, 0, &intermiosBuf);
  *(_OWORD *)v196 = 0LL;
  v195 = 0LL;
  *(_OWORD *)s = 0LL;
  *(_QWORD *)&v196[13] = 0LL;
  v124 = &v168;
  v123 = &v167;
  v164 = v199;
  v187 = &v198;
  v186 = &v96;
  v185 = &v97;
  v184 = &v100;
  v122 = &s[12];
  v108 = v103;
  v163 = &v197;
  v183 = &v99;
  v162 = &v166;
  ......
  v5 = 0LL;
  do
  {
    v72 = v4;
    v98 = getc(stdin);
    v73 = v98 << 24;
    v74 = v98 << 24 == 0x1B000000;
    if ( v98 << 24 == 0x31000000 )
      v74 = 2;
    if ( v73 == 0x37000000 )
      v74 = 3;
    if ( v73 == 0x33000000 )
      v74 = 4;
    if ( v73 == 0x34000000 )
      v74 = 5;
    v101 = v5;
    v102 = v72;
    v119 = v72;
    if ( v74 )
    {
      if ( v74 == 1 )
        _clang_call_terminate(5LL);
      if ( v74 == 2 )
      {
        v107 = v102 + (4LL << (3 * (unsigned __int8)v101));
        v85 = v98;
      }
      else if ( v74 == 3 )
      {
        v107 = v102 + (5LL << (3 * (unsigned __int8)v101));
        v85 = v98;
      }
      else
      {
        if ( v74 == 4 )
          v107 = v102 + (6LL << (3 * (unsigned __int8)v101));
        else
          v107 = v102 + (7LL << (3 * (unsigned __int8)v101));
        v85 = v98;
      }
      s[v101] = v85;
      v119 = v107;
    }
    v5 = v101 + 1;
    v174 = v119;
  }
  while ( v101 != 11 );
  s[12] = 0;
  v69 = fileno(stdin);
  tcsetattr(v69, 0, &intermiosBufBackup);
  for ( i = 0LL; i < 5; ++i )
    *((_BYTE *)v136 + i) = byte_40E0F3[i] - byte_40E0F8[i];
  v188 = &v190;
  v190 = v136[0];
  v189 = 4LL;
  v191 = 0;
  __isoc99_scanf(&v190, v122);
  v26 = v188;
  v175 = v188;
  *(_OWORD *)v188 = xmmword_40E040;
  v26[4] = 639210836;
  *((_BYTE *)v26 + 20) = 16;
  *(_QWORD *)((char *)v26 + 34) = 0x1005E763241AA6B1LL;
  *(_OWORD *)((char *)v26 + 21) = xmmword_40E148;
  __cxa_begin_catch(v26);
  v155 = strlen(v122);
  v128 = 0LL;
  v113 = 0;
  v125 = v155;
  v147 = 0LL;
  do
  {
    v133 = v125 - 1;
    v86 = v122[v147];
    v160 = v128;
    v110 = v113;
    v176 = v147;
    isalnum(v86);
    v50 = (unsigned int)(v160 + 1);
    *(&v95 + (int)v160) = v86;
    v181 = v176 + 1;
    v130 = v50;
    v112 = v110;
    v146 = 0LL;
    if ( (_DWORD)v50 == 4 )
    {
      do
      {
        v106 = 0LL;
        v149 = v146;
        do
        {
          v199[v106 + 16] = byte_40E071[v106] - byte_40E0B2[v106];
          ++v106;
        }
        while ( v106 < 0x41 );
        v56 = v163;
        *(_QWORD *)v163 = v164;
        v165 = 64LL;
        v169 = (_OWORD *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_create(
                           v56,
                           &v165,
                           0LL);
        v9 = (void **)v163;
        v10 = v169;
        *(_QWORD *)v163 = v169;
        v11 = v165;
        *(_QWORD *)v164 = v165;
        v12 = MEMORY[5];
        v13 = MEMORY[0x15];
        v14 = MEMORY[0x25];
        v10[3] = MEMORY[0x35];
        v10[2] = v14;
        v10[1] = v13;
        *v10 = v12;
        *(_QWORD *)v187 = v11;
        *((_BYTE *)v10 + v11) = 0;
        v15 = v149;
        *(&v95 + v15) = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::find(
                          v9,
                          (unsigned int)*(&v95 + v149),
                          0LL);
        v177 = *v9;
        operator delete(v177);
        v146 = v149 + 1;
      }
      while ( v149 != 3 );
      v17 = *v186;
      *v183 = (4 * *v57) | ((unsigned __int8)*v186 >> 4) & 3;
      v18 = *v185;
      *v59 = (16 * v17) | ((unsigned __int8)*v185 >> 2) & 0xF;
      *v184 = *v58 + (v18 << 6);
      v152 = v110;
      v151 = 0LL;
      do
      {
        v6 = v151;
        v7 = (unsigned __int8)*(&v99 + v151) / 0xAu;
        v8 = v152;
        v199[v152 + 96] = (unsigned __int8)*(&v99 + v151) % 0xAu;
        v199[v8 + 97] = v7;
        v151 = v6 + 1;
        v152 = v8 + 2;
        v182 = v8 + 2;
      }
      while ( v6 != 2 );
      v130 = 0LL;
      v112 = v182;
    }
    v128 = v130;
    v113 = v112;
    v125 = v133;
    v147 = v181;
  }
  while ( v133 );
  __cxa_end_catch();
  v193 = 152788034LL;
  v192[3] = xmmword_40E130;
  v192[2] = xmmword_40E120;
  v192[1] = xmmword_40E110;
  v192[0] = xmmword_40E100;
  v138 = 152788034LL;
  cipher_helper<12037464u,StList<0ul,1ul,2ul,3ul,4ul,5ul,6ul,7ul,8ul,9ul,10ul,11ul,12ul,13ul,14ul,15ul,16ul,17ul,18ul,19ul,20ul,21ul,22ul,23ul,24ul,25ul,26ul,27ul,28ul,29ul,30ul,31ul,32ul,33ul,34ul,35ul,36ul,37ul,38ul,39ul>>::get_array(
    152788034LL,
    "Knows the futility yet does it anyway. ");
  v55 = v138;
  *(_OWORD *)(v138 + 56) = xmmword_40E16D;
  *(_OWORD *)(v55 + 40) = xmmword_40E15D;
  *(_QWORD *)(v55 + 72) = 0x6FF0E70B5B3F60A4LL;
  v137 = (void *)0x6FF0E70B5B3F60A4LL;
  __cxa_begin_catch((void *)0x6FF0E70B5B3F60A4LL);
  v145 = 0LL;
  do
  {
    v67 = v145;
    *((_DWORD *)v192 + 2 * v145) ^= 0x9005408u;
    v145 = v67 + 1;
  }
  while ( v67 != 8 );
  __cxa_end_catch();
  *(_OWORD *)v75 = xmmword_40E030;
  *((_QWORD *)v75 + 2) = 0x48D1556A814FF991LL;
  *((_QWORD *)v75 + 5) = 0x48B0E10161EA8322LL;
  v25 = -2.526699287193993e95;
  *(_OWORD *)(v75 + 24) = xmmword_40E185;
  __cxa_begin_catch(v75);
  v121 = 0LL;
  v109 = 0;
  do
  {
    v27 = v121;
    v179 = (unsigned __int64 *)v192 + (unsigned int)v121 / 9uLL;
    v28 = *v179;
    v29 = (unsigned int)v121 % 9;
    v30 = pow(v25, (double)(int)((unsigned int)v121 % 9 + 1));
    v178 = v28;
    v31 = v28 % (unsigned int)(int)(v30 + 0.5);
    y = (double)v29;
    v32 = pow(11.0, (double)v29) + 0.5;
    v33 = (unsigned int)(int)v32;
    v25 = v32 - 9.223372036854776e18;
    v158 = v27;
    v157 = v109;
    v111 = v109;
    if ( v31 < v33 )
    {
      v111 = v157 + 1;
      v51 = v199[(int)v157 + 96];
      v52 = pow(v25, y) + 0.5;
      v53 = (unsigned int)(int)v52;
      v25 = v52 - 9.223372036854776e18;
      *v179 = v178 + v51 * v53;
    }
    v121 = (unsigned int)(v158 + 1);
    v109 = v111;
  }
  while ( (_DWORD)v158 != 80 );
  __cxa_end_catch();
  v88 = 1;
  v140 = 0LL;
  do
  {
    v60 = v108;
    v108[8] = 0;
    *(_QWORD *)v60 = 0LL;
    v171 = *((_QWORD *)v192 + v140);
    v126 = 0LL;
    v170 = v140;
    do
    {
      v19 = v126;
      v20 = v126 + 1;
      v21 = pow(v25, (double)((int)v126 + 1));
      v22 = v171 % (unsigned int)(int)(v21 + 0.5);
      v23 = pow(11.0, (double)v19) + 0.5;
      v24 = (unsigned int)(int)v23;
      v25 = v23 - 9.223372036854776e18;
      v103[v22 / v24] = 1;
      v141 = 1LL;
      v89 = v88;
      v126 = v20;
    }
    while ( v20 != 9 );
    do
    {
      v61 = v89;
      if ( !v103[v141] )
        v61 = 0;
      ++v141;
      v115 = v61;
      v89 = v61;
    }
    while ( v141 != 10 );
    v140 = v170 + 1;
    v131 = 0LL;
    v87 = v115;
    v88 = v115;
  }
  while ( v170 != 8 );
  do
  {
    v68 = v108;
    v108[8] = 0;
    *(_QWORD *)v68 = 0LL;
    v172 = (double)((int)v131 + 1);
    v40 = (double)(int)v131;
    v173 = (double)(int)v131;
    v161 = (unsigned int)v131;
    v142 = 0LL;
    do
    {
      v62 = v142;
      v63 = *((_QWORD *)v192 + v142);
      v64 = v63 % (unsigned int)(int)(pow(v40, v172) + 0.5);
      v65 = pow(11.0, v173) + 0.5;
      v66 = (unsigned int)(int)v65;
      v40 = v65 - 9.223372036854776e18;
      v103[v64 / v66] = 1;
      v142 = v62 + 1;
      v144 = 1LL;
      v90 = v87;
    }
    while ( v62 != 8 );
    do
    {
      v71 = v90;
      if ( !v103[v144] )
        v71 = 0;
      ++v144;
      v116 = v71;
      v90 = v71;
    }
    while ( v144 != 10 );
    v131 = (unsigned int)(v161 + 1);
    v132 = 0LL;
    v92 = v116;
    v87 = v116;
  }
  while ( (_DWORD)v131 != 9 );
  do
  {
    v54 = v108;
    v108[8] = 0;
    *(_QWORD *)v54 = 0LL;
    v135 = 3 * ((unsigned int)v132 / 3);
    v134 = 3 * ((unsigned int)v132 % 3) + 1;
    v129 = 0LL;
    v159 = (unsigned int)v132;
    do
    {
      v34 = v129;
      v35 = *((_QWORD *)v192 + (int)(v135 + (unsigned int)v129 / 3));
      v36 = (v134 + (unsigned int)v129 % 3) % 9;
      v37 = v35 % (unsigned int)(int)(pow(v40, (double)(v36 + 1)) + 0.5);
      v38 = pow(11.0, (double)v36) + 0.5;
      v39 = (unsigned int)(int)v38;
      v40 = v38 - 9.223372036854776e18;
      v103[v37 / v39] = 1;
      v129 = (unsigned int)(v34 + 1);
      v150 = 1LL;
      v94 = v92;
    }
    while ( v34 != 8 );
    do
    {
      v70 = v94;
      if ( !v103[v150] )
        v70 = 0;
      ++v150;
      v104 = v70;
      v94 = v70;
    }
    while ( v150 != 10 );
    v132 = (unsigned int)(v159 + 1);
    v92 = v104;
  }
  while ( (_DWORD)v159 != 8 );
  v48 = v108;
  v108[8] = 0;
  *(_QWORD *)v48 = 0LL;
  v127 = 0LL;
  do
  {
    v41 = v127;
    v42 = 9 - v127;
    if ( !(_DWORD)v127 )
      v42 = 0;
    v43 = *((_QWORD *)v192 + v42);
    v44 = v127 + 1;
    v45 = v43 % (unsigned int)(int)(pow(v40, (double)((int)v127 + 1)) + 0.5);
    v46 = pow(11.0, (double)v41) + 0.5;
    v47 = (unsigned int)(int)v46;
    v40 = v46 - 9.223372036854776e18;
    v103[v45 / v47] = 1;
    v143 = 1LL;
    v91 = v104;
    v127 = v44;
  }
  while ( v44 != 9 );
  do
  {
    v49 = v91;
    if ( !v103[v143] )
      v49 = 0;
    ++v143;
    v117 = v49;
    v91 = v49;
  }
  while ( v143 != 10 );
  v16 = v108;
  v108[8] = 0;
  *(_QWORD *)v16 = 0LL;
  v139 = 0LL;
  do
  {
    v76 = v139 + 1;
    v77 = v139 == 8;
    v78 = v139 + 1;
    if ( v139 == 8 )
      v78 = 0;
    v79 = *((_QWORD *)v192 + v139);
    v80 = v79 % (unsigned int)(int)(pow(v40, (double)(v78 + 1)) + 0.5);
    v81 = pow(11.0, (double)v78) + 0.5;
    v82 = (unsigned int)(int)v81;
    v40 = v81 - 9.223372036854776e18;
    v103[v80 / v82] = 1;
    v148 = 1LL;
    v93 = v117;
    v139 = v76;
  }
  while ( !v77 );
  do
  {
    v83 = v93;
    if ( !v103[v148] )
      v83 = 0;
    ++v148;
    v118 = v83;
    v93 = v83;
  }
  while ( v148 != 10 );
  return 0;
}

0x03 Solve the Puzzles

PART ONE

之前也提到过,由于我们的输入部分流可能执行不到,很明显我们刚刚根本没有输入上下左右箭头啥的

 

所以关于处理上下左右箭头的代码无了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
do
 {
   v72 = v4;
   input1 = getc(stdin);
   v73 = input1 << 24;
   shift_input1 = input1 << 24 == 0x1B000000;
   if ( input1 << 24 == 0x31000000 )
     shift_input1 = 2;
   if ( v73 == 0x37000000 )
     shift_input1 = 3;
   if ( v73 == 0x33000000 )
     shift_input1 = 4;
   if ( v73 == 0x34000000 )
     shift_input1 = 5;
   count = v5;
   v102 = v72;
   v119 = v72;
   if ( shift_input1 )
   {
     if ( shift_input1 == 1 )
       _clang_call_terminate((void *)5);
     if ( shift_input1 == 2 )
     {
       v107 = v102 + (4LL << (3 * (unsigned __int8)count));
       org_input = input1;
     }
     else if ( shift_input1 == 3 )
     {
       v107 = v102 + (5LL << (3 * (unsigned __int8)count));
       org_input = input1;
     }
     else
     {
       if ( shift_input1 == 4 )
         v107 = v102 + (6LL << (3 * (unsigned __int8)count));
       else
         v107 = v102 + (7LL << (3 * (unsigned __int8)count));
       org_input = input1;
     }
     s[count] = org_input;
     v119 = v107;
   }
   v5 = count + 1;
   v174 = v119;
 }
 while ( count != 11 );

这个时候就可以更改我们的输入(指的是输入箭头再输入字符)再来一遍

 

成功解析出我们的第一段输入

 

图片描述

 

由于两个文件分析过程不贴了,可以直接看官方WP给出的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int part1_size = 12;
while(count < part1_size) {
  char a = getchar();
  if (a == 27) {
    if (getchar() == 91) {
      char c = getchar();
      try {
        rmCjJ0(true, c);
      } catch(Le3KW5 &cc) {
        char c = cc.state;
        if (c == 65) {
          state += 0ull << (3 * count);
        } else if (c==66) {
          state += 2ull << (3 * count);
        } else if (c==67) {
          state += 1ull << (3 * count);
        } else if (c==68) {
          state += 3ull << (3 * count);
        }
      }
      flag[count] = c;
    }
  } else if (a=='1') {
    state += 4ull << (3 * count);
    flag[count] = a;
  } else if (a=='7') {
    state += 5ull << (3 * count);
    flag[count] = a;
  } else if (a=='3') {
    state += 6ull << (3 * count);
    flag[count] = a;
  } else if (a=='4') {
    state += 7ull << (3 * count);
    flag[count] = a;
  }
  count += 1;
}
// ... Second Part ...
// Check Part
if (... && state == 0xb3e659480) {
  std::cout << LIT("Congratulation! \n") << LIT("Your flag is ACTF{") << flag << LIT("_amazing!}") << std::endl;
}

PART TWO

这个部分完全跟着lchild的分析来了

 

接着就是第二段输入

 

首先是经过一段Base64解码操作,再经过取模除十操作得到一个数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
  if ( (_DWORD)v50 == 4 )
  {
    do
    {
      v106 = 0LL;
      v149 = v146;
      do
      {
        baseTable[v106 + 16] = byte_40E071[v106] - byte_40E0B2[v106];// baseTable
        ++v106;
      }
      while ( v106 < 0x41 );
      v56 = (__int64)v163;
      *(_QWORD *)v163 = v164;
      v165 = 64LL;
      v169 = (_OWORD *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_create(
                         v56,
                         &v165,
                         0LL);
      v9 = (void **)v163;
      v10 = v169;
      *(_QWORD *)v163 = v169;
      v11 = v165;
      *(_QWORD *)v164 = v165;
      v12 = MEMORY[5];
      v13 = MEMORY[0x15];
      v14 = MEMORY[0x25];
      v10[3] = MEMORY[0x35];
      v10[2] = v14;
      v10[1] = v13;
      *v10 = v12;
      *(_QWORD *)v187 = v11;
      *((_BYTE *)v10 + v11) = 0;
      v15 = v149;
      *(&copy_input1 + v15) = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::find(
                                v9,
                                (unsigned int)*(&copy_input1 + v149),
                                0LL);
      v177 = *v9;
      operator delete(v177);
      v146 = v149 + 1;
    }
    while ( v149 != 3 );
    v17 = *v186;
    *v183 = (4 * *v57) | ((unsigned __int8)*v186 >> 4) & 3;
    v18 = *v185;
    *v59 = (16 * v17) | ((unsigned __int8)*v185 >> 2) & 0xF;
    *v184 = *v58 + (v18 << 6);
    v152 = v110;
    v151 = 0LL;
    do
    {                                         // 对输入进行操作分值操作
      v6 = v151;
      v7 = (unsigned __int8)*(&v99 + v151) / 0xAu;
      v8 = v152;
      baseTable[v152 + 96] = (unsigned __int8)*(&v99 + v151) % 0xAu;
      baseTable[v8 + 97] = v7;
      v151 = v6 + 1;
      v152 = v8 + 2;
      v182 = v8 + 2;
    }
    while ( v6 != 2 );
    v130 = 0LL;
    v112 = v182;
  }
  v128 = v130;
  v113 = v112;
  copy_len = v133;
  v147 = v181;
}
while ( v133 );                               // 以上是对input进行了base64解码

之后计算了九个数值,和一堆pang臭的代码,不过干的事情不是很复杂

 

图片描述

 

第一个循环是复制,后两个循环判断行列,不难发现这是个数独,拿网站一把梭了

具体参考lchild师傅的Write up

# https://sudoku.vip/sudoku-x-solver/

0x04 GetFlag!!

第一个解密就直接移回去即可

 

第二个解密出数独的值,列移动,取出值恢复原权位值,最后Base64即可!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
s = []
t = 0xB3E659480
# 每3个字节为一次输入
for i in range(12):
    s.append(t & 0x7)
    t >>= 3
 
assert t == 0
key = ''
for i in s:
    if i == 0: key += '↑'
    elif i == 1: key += '→'
    elif i == 2: key += '↓'
    elif i == 3: key += '←'
    elif i == 4: key += '1'
    elif i == 5: key += '7'
    elif i == 6: key += '3'
    elif i == 7: key += '4'
print(key) # ??↓↓→←→←3417
 
 
values = [0x00000000331b6d84, 0x0000000054cab29a, 0x000000000cd0afcd,
0x000000006636db08, 0x0000000000021528, 0x0000000005d62020, 0x00000000070bc7c1,
0x00000000006739bd, 0x00000000001b084a]
table = []
for i in values:
    table.append([])
    s = ''
    value = i
    for j in range(9):
        table[-1].append(int(value % 11))
        s += "%2d" % (value % 11)
        value /= 11
 #   print(s[2: ] + s[: 2])
'''
 0 0 0 0 0 0 0 4 0
 0 0 5 0 0 0 7 6 0
 0 0 0 0 4 0 0 1 0
 0 0 0 0 0 0 0 8 0
 0 6 3 9 0 0 0 0 0
 0 0 0 0 3 0 5 0 0
 2 9 0 0 8 0 6 0 0
 0 7 0 0 9 3 0 0 0
 3 0 0 0 0 1 0 0 0
'''
 
# print(sum(table, []).count(0))
# https://sudoku.vip/sudoku-x-solver/
 
solves = [
[8, 1, 6, 7, 5, 2, 3, 4, 9],
[4, 3, 5, 8, 1, 9, 7, 6, 2],
[7, 2, 9, 3, 4, 6, 8, 1, 5],
[9, 4, 7, 1, 6, 5, 2, 8, 3],
[5, 6, 3, 9, 2, 8, 4, 7, 1],
[1, 8, 2, 4, 3, 7, 5, 9, 6],
[2, 9, 1, 5, 8, 4, 6, 3, 7],
[6, 7, 4, 2, 9, 3, 1, 5, 8],
[3, 5, 8, 6, 7, 1, 9, 2, 4]
]
 
# 数独列右移
for i in range(9):
    solves[i] = [solves[i][-1]] + solves[i][: -1]
#    print(solves[i])
 
numbers = []
for y in range(9):
    for x in range(9):
            if table[y][x] == 0:
#                print(table[y][x])
                numbers.append(solves[y][x])
 
assert len(numbers) % 2 == 0
 
flag = ''
for i in range(0, len(numbers), 2):
    flag += chr(numbers[i] + 10 * numbers[i + 1])
 
import base64
# print(flag)
print(base64.b64encode(str.encode(flag)))
 
# ↑↑↓↓→←→←3417
# WT05ICpTW0tcPyYxETgMGTBDUSphES1TLgwtVUwd

最后输入上上下下右左右左3417再二段

 

GetFlag!!

 

图片描述

总结

对于我这种0 throw catch小白0 ollvm小白,过程中是补了不少文章...学识尚浅如果有错误的地方欢迎指出,一定会严加学习修改!

 

最后拿到flag,也是对这题的一个happy ending,真是REVERSE生涯中目前做的最难的一题了

 

回忆起最开始wjh师傅教我的unicorn再到Helen师傅出的login,这次又上了台阶!逆向真是越来越有趣了。--7.21 00:55


看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

最后于 2022-7-21 22:31 被P.Z编辑 ,原因:
上传的附件:
收藏
点赞3
打赏
分享
打赏 + 100.00雪花
打赏次数 1 雪花 + 100.00
 
赞赏  Editor   +100.00 2022/08/10 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (10)
雪    币: 46
活跃值: 活跃值 (728)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
NutCracker 活跃值 2022-7-21 20:27
2
0
来,挑战一下我的ARealBug。
雪    币: 1118
活跃值: 活跃值 (642)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
P.Z 活跃值 1 2022-7-21 21:36
3
0

我会一直研究的!!

最后于 2022-7-21 22:29 被P.Z编辑 ,原因:
雪    币: 1118
活跃值: 活跃值 (642)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
P.Z 活跃值 1 2022-7-21 21:39
4
0
NutCracker 来,挑战一下我的ARealBug。
我会一直研究的!
雪    币: 5
活跃值: 活跃值 (501)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_rhynjqzk 活跃值 2022-7-21 21:54
5
0
师傅写的够详细,很保姆,就是有两图显示不出来了2333
雪    币: 1118
活跃值: 活跃值 (642)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
P.Z 活跃值 1 2022-7-21 22:10
6
0
mb_rhynjqzk 师傅写的够详细,很保姆,就是有两图显示不出来了2333
已修正)
雪    币: 1118
活跃值: 活跃值 (642)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
P.Z 活跃值 1 2022-8-9 20:23
7
0

在bilibili上录制的一期这题的视频讲解

https://www.bilibili.com/video/BV1ad4y1S74W?spm_id_from=333.999.0.0&vd_source=f4fa3266c2bdd0e06034b3230cd27c93

最后于 2022-8-9 20:23 被P.Z编辑 ,原因:
雪    币: 315
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_ifbhogkm 活跃值 2022-8-12 16:27
8
0
雪    币: 6943
活跃值: 活跃值 (4268)
能力值: ( LV13,RANK:239 )
在线值:
发帖
回帖
粉丝
sunfishi 活跃值 3 2022-8-12 16:48
9
0
好文
雪    币: 1118
活跃值: 活跃值 (642)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
P.Z 活跃值 1 2022-8-16 14:45
10
0
雪    币: 1118
活跃值: 活跃值 (642)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
P.Z 活跃值 1 2022-8-16 14:49
11
0
sunfishi 好文[em_63]
Mas0n
游客
登录 | 注册 方可回帖
返回