c++异常化处理
ollvm-控制流平坦化
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处理我们第一段输入的地方。
40762940553a(专门用来处理箭头)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
先抛去原题,来认识一下这些名词:
函数的开始地址为序言(prologue)的地址
序言的后继为主分发器(main dispatcher)
后继为主分发器的块为预处理器(predispatcher)
后继为预处理器的块为真实块(relevant blocks)
无后继的块为retn块
剩下的为无用块与子分发器(sub dispatchers)
那参考文章,总结来说,利用angr符号执行去除控制流平坦化的步骤可以归结为三个步骤:
静态分析cfg得到序言/入口块(prologue)、主分发器(main dis。
patcher)、子分发器/无用块(sub dispatchers)、真实块(relevant blocks)、预分发器(predispatcher)和返回块(return)。
利用符号执行恢复真实块的前后关系,重建控制流。
根据第二步重建的控制流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 从抛异常函数开始,对调用链上的函数逐个往前查找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 一个异常时,编译器会帮我们调用相应的函数分配出如下的结构:
其中 __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修改类型。
scan_eh_tab 回忆__gxx_personality_v0函数功能: 检查当前函数是否有相应的 catch 语句。
清理当前函数中的局部变量。
在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)。
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
去该平坦化控制流,有两个步骤:
找到所有的真实块
找到真实块之间的关系
find all relevant blocks
我们可以从主分发器开始寻找,找到所有子分发器的后继者,这些后继者本身不是子分发器。 官方wp中一眼丁真发现子分发器由该指令格式组成。
sub dispathers such as:cmpjx 于是由此区别出来: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 != jmpissubdispatcher = lambda bb: (len(bb.instrs) == 2) and iscmpri(bb.instrs[0]) and iscjmp(bb.instrs[1]) 首先判断是否为子分发器,然后排除法找到所有真实块。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:*******************relevant blocks************************main_dispatcher:0x404a80relevant_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 off b *0x40a3d4commands silent printf landingpad: %x\n, $rdx continueend b _zn18stdsubobfexceptionc2eccommands silent printf selector: %x\n, $rsi continueend define mytrace break $arg0 commands silent printf %x\n, $pc python gdb.execute('continue') endendfor bb in relevant_blocks: cmds += (fmytrace *{hex(bb.address)} )cmds += runwith open(test.gdb, w) as f: f.write(cmds)cat teatin0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef gdb inflated -x test.gdb --batch testout 于是可以获取真实块接下来的landing pad与异常类型。breakpoint 1 at 0x40a3d4......breakpoint 88 at 0x404ca44075f9selector: 0landingpad: 4089bf4072b4selector: 0landingpad: 4085034075f9selector: 2landingpad: 4089bf4060e7selector: 0......40617cselector: 0landingpad: 409100409437[inferior 1 (process 13732) exited normally] 然后就写个parser分析。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:************************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_start print(fva_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(' 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 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 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('
> 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::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)); 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给出的源码。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 partif (... && state == 0xb3e659480) { std::cout << lit(congratulation! ) << 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::basic_string::_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; *(©_input1 + v15) = std::basic_string::find( v9, (unsigned int)*(©_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 <>= 3 assert t == 0key = ''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!!
锐龙9 3900曝光 相比于锐龙9 3900X功耗从105W大幅降低至65W
a&amp;s测评|全视“鹰眼”:艾睿双光谱热成像筒型摄像机
用一张AP Autosar的图来说明什么是Automotive OS
印制电路板PCB的可靠性设计-去耦电容配置
线路板气密性检测
C++异常化处理,OLLVM-控制流平坦化
北京市青少年人工智能素养提升计划启动仪式顺利举行
GPS模块的常见问题与解决方法
怎样对ECU进行固件升级
百度采用赛灵思FPGA所提供的功耗效率让加速器能部署于整个数据中心
恩智浦推出用于快速充电的业界最佳端到端USB Type-C解决方案
Explorium开发自动数据和功能发现平台
华为出货量同比增长了44.5%并进一步缩小与第一名三星之间的差距
高频交易(HTF)和人工智能(AI)正在改变BTC量化搬砖套利环境
大功率UPS电源元器件的作用
高通CEO莫伦科夫称5G将帮助我们构建更具韧性的社会和经济
电子雾化棒防水透气膜是什么,它的作用都有哪些
x86主板如何恢复出厂设置,如何将主板恢复默认
美国DARPA研发新算法,小型商用无人机变身自主侦察机
高通骁龙888新机曝光,预计下月发布