C++异常处理控制流下的OLLVM混淆

点击蓝字

d143131a0175d09a70ac8733b852fff9.png

关注我们

来源于网络,侵删

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位

bfa2927f2ec75a119de0b19dffd475b2.png

0x01 CFG

GETC

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

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

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.

bd71a51e90814156cbe041cd0932fbab.png

那么异常处理先不深究,继续回来箭头如何处理这个问题。那么箭头其实为三字节码,上下左右箭头分别对应 ^[[A ^[[B ^[[C ^[[D。此时开始动调,我第一次输入为上箭头,同时注意RAX。

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

84c52510b346a3e03219d231424c6ad8.png

随后到 40553A 读取为5B。

13e48a5100139b8701e2a90528176360.png

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

b799ab75ee63f2ec13d3248c7fcffc9d.png

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


OLLVM

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

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

92976d2876c98b481ce2f923c48e1932.png

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

  1. 函数的开始地址为序言(Prologue)的地址

  2. 序言的后继为主分发器(Main dispatcher)

  3. 后继为主分发器的块为预处理器(Predispatcher)

  4. 后继为预处理器的块为真实块(Relevant blocks)

  5. 无后继的块为retn块

  6. 剩下的为无用块与子分发器(Sub dispatchers)

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

  1. 静态分析CFG得到序言/入口块(Prologue)、主分发器(Main dis。

  2. patcher)、子分发器/无用块(Sub dispatchers)、真实块(Relevant blocks)、预分发器(Predispatcher)和返回块(Return)。

  3. 利用符号执行恢复真实块的前后关系,重建控制流。

  4. 根据第二步重建的控制流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那么漂亮。

 caaa2a2805f031d32552c3f7ca423349.png

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

从抛异常函数开始,对调用链上的函数逐个往前查找Landing pad。

如果没有找到Landing pad则把程序abort,如果找到则记下Landing pad的位置,再重新回到抛异常的函数那里开始,一帧一帧地清理调用链上各个函数内部的局部变量,直到 landing pad 所在的函数为止。

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定义了一系列函数以及数据结构来建立整个异常处理的流程及框架,主要函数包括以下列:

_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 来表示该异常,该异常也有一个头部,定义如下:

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 一个异常时,编译器会帮我们调用相应的函数分配出如下的结构:

 999e8cc7787fca2ad44519f83e25b47a.png

其中 __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里调整此函数。

_Unwind_Reason_Code __fastcall _gxx_personality_v0(int Version,_Unwind_Action actions,__int64 exceptionClass,_Unwind_Exception *exceptionObject,_Unwind_Context *context)

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

6172f9b5eba07cec01097baca4a883a4.png

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 导入结构体。

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修改。

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)。

81455a21eda7953d1f3cfb84f896a62c.png


ttypeIndex

首先要求父类为StdObfException的异常。

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

a4ac251d935757c562d5bdb1c271c2f2.png

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 创建的异常,也就是固定的。

a052ac024e7e640e9323798ccc3f63f0.png

现在知道了魔改后的流程是从哪里来到哪里去,人工方式就是跳到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块。

305daeaaa84e84c0a70b6e91c00fea0b.png

以下开始就是跟着官方脚本复现。我们再回忆一下正常的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中一眼丁真发现子分发器由该指令格式组成。

sub dispathers such as:
cmp
jx

于是由此区别出来:

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])

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

class PatchHelper:## ......# To get all cfgsdef 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 bbdef 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:continueq.put(patch_helper.block(succ))visited.add(succ)else:relevant_blocks.append(bb)return relevant_blocks

Relevant blocks:

*******************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(https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html) or 利用符号执行去除控制流平坦化(https://security.tencent.com/index.php/blog/msg/112) for more information).

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

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

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

生成GDB的脚本如下:

  • 40A3D4为我们catch块地址

  • _ZN18StdSubObfExceptionC2Ec为了打印异常类型

cmds = """\
set pagination offb *0x40A3D4
commandssilentprintf "landingPad: %x\\n", $rdxcontinue
endb _ZN18StdSubObfExceptionC2Ec
commandssilentprintf "selector: %x\\n", $rsicontinue
enddefine mytracebreak $arg0commandssilentprintf "%x\\n", $pcpython 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)
cat teatin
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefgdb inflated -x test.gdb --batch < testin > testout

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

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分析。

def parse_logs(logfn, prologue, patch_helper):with open(logfn, "r") as f:t = f.readlines()i = 0selector_s = "selector: "landingpad_s = "landingPad: "relations = set()laddr = prologuelselector = 0landingpad = 0while i < len(t):try:addr = int(t[i], 16)except:i += 1continueif 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 += 2elif t[i+1].startswith(landingpad_s):landingpad = int(t[i+1][len(landingpad_s):], 16)relations.add((addr, -1, landingpad))addr = landingpadwhile not patch_helper.is_unreachable(patch_helper.block(addr).direct_branch):addr = patch_helper.block(addr).direct_branchif t[i+2].startswith(selector_s):selector = int(t[i+2][len(selector_s):], 16)i += 3elif t[i+1].startswith("[Inferior "):i += 1else:print("Warning: %x doesn't have selector. "%addr)exit(0)laddr = addrlselector = selectorreturn 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:

************************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即可。

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_startprint(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) - 4if 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_sizefor 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))

官方完整脚本:

## 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 = 5def 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":continuetarget = bb.instrs[i].operands[0].immediateif target == self.func_terminate:return Truedef 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@staticmethoddef is_imm(operand):return (hasattr(operand, "_X86ImmediateOperand__key"))@staticmethoddef 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_throwdef 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_exceptiondef 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_exceptiondef 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 -= 1return idef get_patchable_from_relblk(self, bb):i = 0end = bb.start_address + bb.sizewhile i < len(bb.instrs) and not self.is_call_throw(bb.instrs[i]):i += 1i = self.skip_call_args(bb, i-1)if i == len(bb.instrs) - 1:start = endelse:start = bb.instrs[i+1].addressself.fill_nops(start, end)return (start, end-start)def __init__(self, proj, elf, barf, cfg) -> None:self.p = projobj = proj.loader.main_objectself.func_terminate = obj.symbols_by_name["__clang_call_terminate"].rebased_addrself.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_addrself.elf = elfself.elfData = bytearray(self.elf.data)self.barf = barfself.cfg = cfgself.nops = []def append_nop(self, nopblk):if nopblk[1] > 0:self.nops.append(nopblk)def finalize(self):self.nops.sort()idx = 0while idx < len(self.nops) - 1:if self.nops[idx][0] + self.nops[idx][1] != self.nops[idx+1][0]:idx += 1continueself.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 Nonestart = 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 nopreturn (-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_SIZEjmp = bytes([PatchHelper.opcode['jmp']])+struct.pack('<i', offset)self.do_patch(va_start, jmp)return PatchHelper.JMP_SIZEdef 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_startprint(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) - 4if 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_sizefor 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:continueq.put(patch_helper.block(succ))visited.add(succ)else:relevant_blocks.append(bb)return relevant_blocksdef parse_logs(logfn, prologue, patch_helper):with open(logfn, "r") as f:t = f.readlines()i = 0selector_s = "selector: "landingpad_s = "landingPad: "relations = set()laddr = prologuelselector = 0landingpad = 0while i < len(t):try:addr = int(t[i], 16)except:i += 1continueif 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 += 2elif t[i+1].startswith(landingpad_s):landingpad = int(t[i+1][len(landingpad_s):], 16)relations.add((addr, -1, landingpad))addr = landingpadwhile not patch_helper.is_unreachable(patch_helper.block(addr).direct_branch):addr = patch_helper.block(addr).direct_branchif t[i+2].startswith(selector_s):selector = int(t[i+2][len(selector_s):], 16)i += 3elif t[i+1].startswith("[Inferior "):i += 1else:print("Warning: %x doesn't have selector. "%addr)exit(0)laddr = addrlselector = selectorreturn list(relations)def generate_gdb_script(relevant_blocks):cmds = """\
set pagination offb *0x40A3D4
commandssilentprintf "landingPad: %x\n", $rdxcontinue
endb _ZN18StdSubObfExceptionC2Ec
commandssilentprintf "selector: %x\n", $rsicontinue
enddefine mytracebreak $arg0commandssilentprintf "%x\\n", $pcpython 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_blocksprologue = startmain_dispatcher = patch_helper.block(prologue).direct_branchrelevant_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 jmpsblk = patch_helper.block(parent)patch_helper.patch_branches(blk, childs)## Nop call allocate_exception and call obf_exceptionfor 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].addressstart = instr.addressend = instr.address + instr.sizepatch_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:

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

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

观看修复后的流程:

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));elsev107 = 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

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

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

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));elsev107 = v102 + (7LL << (3 * (unsigned __int8)count));org_input = input1;}s[count] = org_input;v119 = v107;}v5 = count + 1;v174 = v119;}while ( count != 11 );

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

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

0aa5693867c2ed4cbeb07521ac33430e.png

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

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解码操作,再经过取模除十操作得到一个数组。

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臭的代码,不过干的事情不是很复杂。

a9ffd2c9094321d12f0fea07afa317b9.png

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

具体参考lchild师傅的Write up 

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

0x04 GetFlag!!

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

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

s = []
t = 0xB3E659480
# 每3个字节为一次输入
for i in range(12):s.append(t & 0x7)t >>= 3assert 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) # ??↓↓→←→←3417values = [0x00000000331b6d84, 0x0000000054cab29a, 0x000000000cd0afcd,
0x000000006636db08, 0x0000000000021528, 0x0000000005d62020, 0x00000000070bc7c1,
0x00000000006739bd, 0x00000000001b084a]
table = []
for i in values:table.append([])s = ''value = ifor 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 00 0 5 0 0 0 7 6 00 0 0 0 4 0 0 1 00 0 0 0 0 0 0 8 00 6 3 9 0 0 0 0 00 0 0 0 3 0 5 0 02 9 0 0 8 0 6 0 00 7 0 0 9 3 0 0 03 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 == 0flag = ''
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!!

53fc0013fbf285fb8addea03ab6f659f.png

d7be5a016c93d8edfb79773caef21241.gif

如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

e3a7c793b3e7ae4c5c55ed9a8be1a917.gif

戳“阅读原文”我们一起进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【微服务】springboot整合kafka-stream使用详解

目录 一、前言 二、kafka stream概述 2.1 什么是kafka stream 2.2 为什么需要kafka stream 2.2.1 对接成本低 2.2.2 节省资源 2.2.3 使用简单 2.3 kafka stream特点 2.4 kafka stream中的一些概念 2.5 Kafka Stream应用场景 三、环境准备 3.1 搭建zk 3.1.1 自定义d…

C语言知识总结一:C语言的基本知识汇总

点击蓝字关注我们来源于网络&#xff0c;侵删C语言是一种计算机程序设计语言。它既有高级语言的特点&#xff0c;又具有汇编语言的特点。它可以作 为系统设计语言&#xff0c;编写工作系统应用程序&#xff0c;也可以作为应用程序设计语言&#xff0c;编写不依赖计算机 硬件的应…

jboss8日志级别设置_罐中研讨会:设置JBoss BPM Suite全日研讨会

jboss8日志级别设置是否在寻找一种简单的方法来宣传&#xff0c;展示或演示JBoss业务流程管理套件&#xff08;BPM Suite&#xff09;产品的入门难度&#xff1f; 别无所求&#xff0c;因为我们已经召集了这个研讨会&#xff0c;因此您可以围绕JBoss BPM Suite构建一个晚上&a…

yapi 接口文档_1分钟docker部署顶尖 API 文档管理系统

YApi 是高效、易用、功能强大的 api 管理平台&#xff0c;旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API&#xff0c;YApi 还为用户提供了优秀的交互体验&#xff0c;开发人员只需利用平台提供的接口数据写入工具以及简单的点击…

微软CTO建议业界弃用C/C++采用Rust,遭C++之父回怼

点击蓝字关注我们来源于网络&#xff0c;侵删近日&#xff0c;Microsoft Azure CTO、Sysinternals 的主要开发者 Mark Russinovich 在其社交账号上发布动态称&#xff0c;开发人员是时候停止使用 C/C 来启动新项目&#xff0c;并建议可在需要使用 non-GC 语言的场景中使用 Rust…

红帽 jboss_红帽峰会2015所需的JBoss BPM内容指南

红帽 jboss明年再见&#xff1f; 今年在Red Hat Summit上&#xff0c;我们在JBoss BRMS和JBoss BPM Suite演讲中获得了很多乐趣。 在DevNation周围也有一些社区会议&#xff0c;重点介绍了使我们的产品成为可能的项目。 您可以在他们的博客上找到此演讲的概述&#xff0c;并…

Python、C、Java 和 C++ 四足鼎立,其他已无胜算? | TIOBE 10 月编程语言排行榜

点击蓝字关注我们来源于网络&#xff0c;侵删技术的千变万化&#xff0c;都是有迹可循的&#xff0c;最新的 TIOBE 十月编程语言榜单重磅发布&#xff0c;快来看看有哪些值得关注的变化吧&#xff01;四大编程语言不断增强其主导地位曾几何时&#xff0c;编程语言界中 Java、C、…

C语言 #define 和 typedef 区别

点击蓝字关注我们来源于网络&#xff0c;侵删在C语言编程中&#xff0c;typedef 和 #define是最常用语句&#xff0c;可能很多工作过几年的工程师都没有去深究过它们的一些用法和区别。typedef的用法在C/C语言中&#xff0c;typedef常用来定义一个标识符及关键字的别名&#xf…

Spring Batch –用JavaConfig替换XML作业配置

最近&#xff0c;我协助一个客户启动并运行了Spring Batch实现。 该团队决定继续使用针对批处理作业的基于JavaConfig的配置&#xff0c;而不是传统的基于XML的配置。 随着这越来越成为配置Java应用程序的一种常用方法&#xff0c;我觉得是时候更新Keyhole的Spring Batch系列了…

sql limit 子句_Java 8流中的常见SQL子句及其等效项

sql limit 子句功能编程允许使用通用语言进行准声明性编程 。 通过使用功能强大的流畅API&#xff08;例如Java 8的Stream API &#xff09;或jOOλ的顺序Stream扩展Seq或更复杂的库&#xff08;例如javaslang或functionaljava&#xff09; &#xff0c;我们可以以一种非常简洁…

C++编程中的核心知识点!

点击蓝字关注我们来源于网络&#xff0c;侵删尊重函数接口&#xff0c;尽量不作内部改动C代码语句分为&#xff1a;内置类型&#xff0c;名字&#xff0c;变量&#xff0c;操作符&#xff0c;标量&#xff0c;字符串&#xff0c;预处理指示&#xff08;如#include&#xff09;等…

C++ 语言的单元测试与代码覆盖率

点击蓝字关注我们来源于网络&#xff0c;侵删前言测试是软件开发过程中一个必须的环节&#xff0c;测试确保软件的质量符合预期。对于工程师自己来说&#xff0c;单元测试也是提升自信心的一种方式。直接交付没有经过测试的代码是不太好的&#xff0c;因为这很可能会浪费整个团…

C++ 模板(Template)总结,长点小知识

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删前言&#xff1a;大家好&#xff0c;今天给大家分享一篇关于 C 模板总结概述.模板&#xff08;Template&#xff09;指 C 程序设计设计语言中采用…

归并排序 java_归并排序(Java实现)

package primary_01;/** 归并排序* 1.算法实现* 2.验证算法的正确性* 3.分析算法的复杂度*/public class Merge_sort {public static void main(String[] args) {int arr [] {2,1,6,5,9,8,2020,199};sort(arr, 0, arr.length-1);for (int i : arr) {System.out.print(i" &…

C++17 常用新特性:带初始化的 if 和 switch 语句

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删早在2016-03-14&#xff0c;Thomas Kppe 在https://wg21.link/p0305r0中就提出了在if和switch使用初始化语句&#xff0c;使用时的代码示例如下&…

四大语言加强统治地位,Rust威胁C/C++

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删TIOBE 公布了 2022 年 10 月的编程语言排行榜。截至今日&#xff0c;Python、Java、C 和 C 语言已经在 TIOBE 榜单的前 4 位盘踞了相当长的一段时…

sql计算留存_SQL无所不能:DBA宝妈宝爸系列分享

原文链接&#xff1a;https://www.modb.pro/db/22315 目前程序从功能上其实已经完全满足客户(当然我这里的客户都是指媳妇儿)需求&#xff0c;具体可参考&#xff1a;使用SQL计算宝宝每次吃奶的时间间隔 - Part1&#xff1a;分析函数的使用使用SQL计算宝宝每次吃奶的时间间隔 -…

对 int 变量赋值的操作是原子的吗?为什么?

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删这个是在面试的时候遇到的问题&#xff0c;当时没有答出来。回到家以后查了查&#xff0c;整理记录下来。原问题&#xff1a;什么指令集支持原子…

C语言知识总结:if-else判断语句和switch-case选择语句

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删1、if-else判断语句一个基本的if语句由一个关键字if开头&#xff0c;跟上在括号&#xff08;&#xff09;里边的是表示逻辑条件的表达式&#xf…

hibernate性能_改善Hibernate应用程序性能的7种方法

hibernate性能Hibernate是将Java Web应用程序与SQL数据库集成的好工具&#xff0c;但是当应用程序的响应时间开始显着增加时&#xff0c;我们应该怎么做&#xff1f; 当我们怀疑应用程序是否会随着客户群的增长而扩展时&#xff0c;我们该怎么办&#xff1f; 在花大价钱托管或…