点击蓝字
关注我们
来源于网络,侵删
Inflated!!!
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处理我们第一段输入的地方。
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
先抛去原题,来认识一下这些名词:
函数的开始地址为序言(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:
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 );
这个时候就可以更改我们的输入(指的是输入箭头再输入字符)再来一遍。
成功解析出我们的第一段输入。
由于两个文件分析过程不贴了,可以直接看官方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;*(©_input1 + v15) = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::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 << 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即可!
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!!
如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取
戳“阅读原文”我们一起进步