一般碰到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都可以按照这篇来解决
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所以没法给链接)
(感觉好久没写wp了)
无壳64位
在讲这题ollvm与异常处理之前,有必要先搞懂我们到底是怎么输入的
一共有三处getc处理我们第一段输入的地方
程序最先开始运行的是 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最基本的是要认识这几个块
https://security.tencent.com/index.php/blog/msg/112
先抛去原题,来认识一下这些名词
那参考文章,总结来说,利用angr符号执行去除控制流平坦化的步骤可以归结为三个步骤:
简单来说就是获取所有的块,利用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那么漂亮
为了搞懂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
如果当前函数没有catch,就沿着函数的调用链继续往上抛,然后出现两种情况
在某个函数中找到相应的catch
没找到相应的catch,调用 std::terminate() (这个函数是把程序abort)
如果想找到了相应的catch,执行相应的操作
从抛异常到开始 -> 执行Landing pad代码 这整个过程叫作Stack unwind
Stack unwind
stack unwind的过程可以简单看成函数调用的逆过程,这个过程在实现上由一个专门的stack unwind库来实现
stack unwind库在intel平台上
属于Itanium ABI 接口中的一部分
ltanium C++ ABI定义了一系列函数以及数据结构来建立整个异常处理的流程及框架,主要函数包括以下列:
其中 _Unwind_RaiseException() 函数进行stack unwind ,它在用户执行throw的时被调用
主要功能
从当前函数开始,对调用链上的每一个函数都调用一个叫做 personality routine 的函数(__gxx_personality_v0)
_Unwind_RaiseException() 会在内部把函数栈调用现场重现,然后传给 personality routine,该函数主要做两件事情
那么稍稍总结一下,就是当程序抛出异常就要进行 stack unwind 操作
而这个操作具体是 _Unwind_RaiseException() 中的 personality routine() 实现了检查catch和清理栈上的局部变量
基于前面介绍的 ltanium ABI,编译器层面 也定义了一系列 ABI 与之交互
当我们在代码中写下 throw xxx,编译器会分配一个数据结构 __cxa_exception 来表示该异常,该异常也有一个头部,定义如下
当用户 throw 一个异常时,编译器会帮我们调用相应的函数分配出如下的结构
其中 __cxa_exception 就是头部,exception_obj 则是 "throw xxx" 中的 xxx,这两部分在内存中是连续的。
异常对象由函数 __cxa_allocate_exception() 进行创建
最后由 __cxa_free_exception() 进行销毁
当我们在程序里执行了抛出异常的操作,编译器为我们做了如下的事情:
总结太Bravo了!
有了这些前置知识,再看题目中的异常,由前面描述可知实现 unwind stack 的具体过程是通过 __gxx_personality_v0(即personality routine)实现
这时候我们再去IDA里调整此函数
光标在函数,按Y修改类型
scan_eh_tab
回忆__gxx_personality_v0函数功能
在personality routine()下的 scan_eh_tab() 该函数有我们最关心的两个值,同时也是魔改处
与源码对比:https://code.woboq.org/llvm/libcxxabi/src/cxa_personality.cpp.html#__cxxabiv1::scan_eh_tab
Shfit + F1 -> INS 导入结构体
光标在scan_eh_tab函数上按Y修改
Landing pad(指向catch块的分发处 ,只单单拿到landing pad还不够,这时候还缺少一个对应异常类型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块
那么现在知道了routine personality 中的 scan_eh_tab被修改了,而IDA平常能识别throw catch这些块的原因就是这些正常的源码
然而landingpad与ttypeIndex都被修改了,所以导致了IDA识别的CFG成了这个样子
我们根本没法用肉眼知道throw的块在哪,只有通过动调才能确定,然而这就导致了原先的deflat脚本都不不行了
原因主要为两点
原因还有种种就不一一举例,就无法正常原先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 -> ...
去该平坦化控制流,有两个步骤:
我们可以从主分发器开始寻找,找到所有子分发器的后继者,这些后继者本身不是子分发器
官方WP中一眼丁真发现子分发器由该指令格式组成
于是由此区别出来
首先判断是否为子分发器,然后排除法找到所有真实块
Relevant blocks:
官网WP指出抽象出来)留个坑,以后熟了试试
Official Write up: A good idea is to abstract the throw StdObfException -> catch
process and do the one basic block symbolic execution (You can refer to Deobfuscation: recovering an OLLVM-protected program or 利用符号执行去除控制流平坦化 for more information).
于是官网WP又给了个更有趣的方法,GDB脚本!
为了找到真实块之间的流程,通过普通的执行然后打印真实块需要的信息!
但是我们不一样能得到所有的流程因为部分可能没执行到,但是我们依然可以利用提取出来的信息去恢复部分控制流,并弄清楚如何输入可以恢复更多流程。(怎么好像梦到过我在这写wp...)
生成GDB的脚本如下
于是可以获取真实块接下来的landing pad与异常类型
然后就写个PARSER分析
Flows:
修复程序环节!
当我们已经确定了执行流程,像抛异常 子分发器什么都是多余的了,统统patch掉
对于后继块只有一个的真实块,只需要jmp过去
官方完整脚本
Work flow:
(按照以上流程,test.gdb可能会报个错,程序把本身有个\n是脚本中需要打印的,但直接转义成真换行了需要手动恢复)
观看修复后的流程
之前也提到过,由于我们的输入部分流可能执行不到,很明显我们刚刚根本没有输入上下左右箭头啥的
所以关于处理上下左右箭头的代码无了
这个时候就可以更改我们的输入(指的是输入箭头再输入字符)再来一遍
成功解析出我们的第一段输入
由于两个文件分析过程不贴了,可以直接看官方WP给出的源码
这个部分完全跟着lchild的分析来了
接着就是第二段输入
首先是经过一段Base64解码操作,再经过取模除十操作得到一个数组
之后计算了九个数值,和一堆pang臭的代码,不过干的事情不是很复杂
第一个循环是复制,后两个循环判断行列,不难发现这是个数独,拿网站一把梭了
具体参考lchild师傅的Write up
# https://sudoku.vip/sudoku-x-solver/
第一个解密就直接移回去即可
第二个解密出数独的值,列移动,取出值恢复原权位值,最后Base64即可!
最后输入上上下下右左右左3417再二段
GetFlag!!
对于我这种0 throw catch小白0 ollvm小白,过程中是补了不少文章...学识尚浅如果有错误的地方欢迎指出,一定会严加学习修改!
最后拿到flag,也是对这题的一个happy ending,真是REVERSE生涯中目前做的最难的一题了
回忆起最开始wjh师傅教我的unicorn再到Helen师傅出的login,这次又上了台阶!逆向真是越来越有趣了。--7.21 00:55
407629
40553A
(专门用来处理箭头)
405676
(专门用来处理箭头)
407629
40553A
(专门用来处理箭头)
405676
(专门用来处理箭头)
void func1()
{
cs a;
/
/
stack unwind时被析构。
throw
3
;
}
void func2()
{
cs b;
func1();
}
void func3()
{
cs c;
try
{
func2();
}
catch (
int
)
{
/
/
进入这里之前, func1, func2已经被unwind.
}
}
void func1()
{
cs a;
/
/
stack unwind时被析构。
throw
3
;
}
void func2()
{
cs b;
func1();
}
void func3()
{
cs c;
try
{
func2();
}
catch (
int
)
{
/
/
进入这里之前, func1, func2已经被unwind.
}
}
_Unwind_RaiseException,
_Unwind_Resume,
_Unwind_DeleteException,
_Unwind_GetGR,
_Unwind_SetGR,
_Unwind_GetIP,
_Unwind_SetIP,
_Unwind_GetRegionStart,
_Unwind_GetLanguageSpecificData,
_Unwind_ForcedUnwind
_Unwind_RaiseException,
_Unwind_Resume,
_Unwind_DeleteException,
_Unwind_GetGR,
_Unwind_SetGR,
_Unwind_GetIP,
_Unwind_SetIP,
_Unwind_GetRegionStart,
_Unwind_GetLanguageSpecificData,
_Unwind_ForcedUnwind
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;
};
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;
};
_Unwind_Reason_Code __fastcall _gxx_personality_v0(
int
Version,
_Unwind_Action actions,
__int64 exceptionClass,
_Unwind_Exception
*
exceptionObject,
_Unwind_Context
*
context)
_Unwind_Reason_Code __fastcall _gxx_personality_v0(
int
Version,
_Unwind_Action actions,
__int64 exceptionClass,
_Unwind_Exception
*
exceptionObject,
_Unwind_Context
*
context)
struct scan_results
{
int64_t ttypeIndex;
const uint8_t
*
actionRecord;
const uint8_t
*
languageSpecificData;
uintptr_t landingPad;
void
*
adjustedPtr;
_Unwind_Reason_Code reason;
};
struct scan_results
{
int64_t ttypeIndex;
const uint8_t
*
actionRecord;
const uint8_t
*
languageSpecificData;
uintptr_t landingPad;
void
*
adjustedPtr;
_Unwind_Reason_Code reason;
};
void scan_eh_tab(scan_results
*
results, _Unwind_Action actions,
bool
native_exception, _Unwind_Exception
*
unwind_exception, _Unwind_Context
*
context)
void scan_eh_tab(scan_results
*
results, _Unwind_Action actions,
bool
native_exception, _Unwind_Exception
*
unwind_exception, _Unwind_Context
*
context)
sub dispathers such as:
cmp
jx
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
])
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:
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()
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
class
PatchHelper:
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()
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
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
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'
]
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
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'
]
cmds
=
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)
cmds
=
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
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
gdb inflated
-
x test.gdb
-
-
batch < testin > testout
cat teatin
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
gdb inflated
-
x test.gdb
-
-
batch < testin > testout
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]
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]
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])
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])
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
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'
]
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
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'
]
def
patch_branches(
self
, bb, va_targets):
va_start, size
=
self
.get_patchable_from_relblk(bb)
if
size < PatchHelper.JMP_SIZE:
print
(
"[Warning] patch_jmp at block %x may fail. size: %d."
%
(bb.address, size))
org_start
=
va_start
print
(f
"va_start: {hex(va_start)}, bb addr: {hex(bb.address)}, size: {size}"
)
total_size
=
9
*
len
(va_targets)
-
4
if
size < total_size:
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
:
cj_instr
+
=
struct.pack(
'<i'
,
self
.func_terminate
-
va_start
-
6
)
else
:
cj_instr
+
=
struct.pack(
'<i'
, t
-
va_start
-
6
)
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
patch_branches(
self
, bb, va_targets):
va_start, size
=
self
.get_patchable_from_relblk(bb)
if
size < PatchHelper.JMP_SIZE:
print
(
"[Warning] patch_jmp at block %x may fail. size: %d."
%
(bb.address, size))
org_start
=
va_start
print
(f
"va_start: {hex(va_start)}, bb addr: {hex(bb.address)}, size: {size}"
)
total_size
=
9
*
len
(va_targets)
-
4
if
size < total_size:
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
:
cj_instr
+
=
struct.pack(
'<i'
,
self
.func_terminate
-
va_start
-
6
)
else
:
cj_instr
+
=
struct.pack(
'<i'
, t
-
va_start
-
6
)
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))
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
class
PatchHelper:
opcode
=
{
'a'
:
0x87
,
'ae'
:
0x83
,
'b'
:
0x82
,
'be'
:
0x86
,
'c'
:
0x82
,
'e'
:
0x84
,
'z'
:
0x84
,
'g'
:
0x8F
,
'ge'
:
0x8D
,
'l'
:
0x8C
,
'le'
:
0x8E
,
'na'
:
0x86
,
'nae'
:
0x82
,
'nb'
:
0x83
,
'nbe'
:
0x87
,
'nc'
:
0x83
,
'ne'
:
0x85
,
'ng'
:
0x8E
,
'nge'
:
0x8C
,
'nl'
:
0x8D
,
'nle'
:
0x8F
,
'no'
:
0x81
,
'np'
:
0x8B
,
'ns'
:
0x89
,
'nz'
:
0x85
,
'o'
:
0x80
,
'p'
:
0x8A
,
'pe'
:
0x8A
,
'po'
:
0x8B
,
's'
:
0x88
,
'nop'
:
0x90
,
'jmp'
:
0xE9
,
'j'
:
0x0F
}
JMP_SIZE
=
5
def
is_unreachable(
self
, bb):
if
isinstance
(bb,
int
):
bb
=
self
.block(bb)
for
i
in
range
(
len
(bb.instrs)):
if
bb.instrs[i].mnemonic !
=
"call"
:
continue
target
=
bb.instrs[i].operands[
0
].immediate
if
target
=
=
self
.func_terminate:
return
True
def
block(
self
, addr):
bb
=
self
.cfg.find_basic_block(addr)
if
bb
is
None
:
bb
=
barf.bb_builder.strategy._disassemble_bb(addr, barf.binary.ea_end, {})
return
bb
@staticmethod
def
is_imm(operand):
return
(
hasattr
(operand,
"_X86ImmediateOperand__key"
))
@staticmethod
def
is_reg(operand):
return
(
hasattr
(operand,
"_X86RegisterOperand__key"
))
def
is_call_throw(
self
, instr):
return
instr.mnemonic
=
=
"call"
and
\
self
.is_imm(instr.operands[
0
])
and
\
instr.operands[
0
].immediate
=
=
self
.func_throw
def
is_call_allocate_exception(
self
, instr):
return
instr.mnemonic
=
=
"call"
and
\
self
.is_imm(instr.operands[
0
])
and
\
instr.operands[
0
].immediate
=
=
self
.func_allocate_exception
def
is_call_obf_exception(
self
, instr):
return
instr.mnemonic
=
=
"call"
and
\
self
.is_imm(instr.operands[
0
])
and
\
instr.operands[
0
].immediate
=
=
self
.func_obf_exception
def
skip_call_args(
self
, bb, i):
while
((bb.instrs[i].mnemonic
in
[
"xor"
,
"mov"
,
"lea"
])
and
\
(
len
(bb.instrs[i].operands) >
0
)
and
(
self
.is_reg(bb.instrs[i].operands[
0
]))
and
\
(bb.instrs[i].operands[
0
].name
in
[
"edx"
,
"rdx"
,
"esi"
,
"rsi"
,
"edi"
,
"rdi"
]))
or
\
bb.instrs[i].mnemonic
=
=
"nop"
:
i
-
=
1
return
i
def
get_patchable_from_relblk(
self
, bb):
i
=
0
end
=
bb.start_address
+
bb.size
while
i <
len
(bb.instrs)
and
not
self
.is_call_throw(bb.instrs[i]):
i
+
=
1
i
=
self
.skip_call_args(bb, i
-
1
)
if
i
=
=
len
(bb.instrs)
-
1
:
start
=
end
else
:
start
=
bb.instrs[i
+
1
].address
self
.fill_nops(start, end)
return
(start, end
-
start)
def
__init__(
self
, proj, elf, barf, cfg)
-
>
None
:
self
.p
=
proj
obj
=
proj.loader.main_object
self
.func_terminate
=
obj.symbols_by_name[
"__clang_call_terminate"
].rebased_addr
self
.func_throw
=
obj.plt[
"__cxa_throw"
]
self
.func_allocate_exception
=
obj.plt[
"__cxa_allocate_exception"
]
self
.func_obf_exception
=
obj.symbols_by_name[
"_ZN18StdSubObfExceptionC2Ec"
].rebased_addr
self
.elf
=
elf
self
.elfData
=
bytearray(
self
.elf.data)
self
.barf
=
barf
self
.cfg
=
cfg
self
.nops
=
[]
def
append_nop(
self
, nopblk):
if
nopblk[
1
] >
0
:
self
.nops.append(nopblk)
def
finalize(
self
):
self
.nops.sort()
idx
=
0
while
idx <
len
(
self
.nops)
-
1
:
if
self
.nops[idx][
0
]
+
self
.nops[idx][
1
] !
=
self
.nops[idx
+
1
][
0
]:
idx
+
=
1
continue
self
.nops[idx]
=
(
self
.nops[idx][
0
],
self
.nops[idx][
1
]
+
self
.nops[idx
+
1
][
1
])
del
self
.nops[idx
+
1
]
def
fill_nops(
self
, va_start, va_end):
assert
not
self
.elf
is
None
start
=
self
.elf.vaddr_to_offset(va_start)
end
=
self
.elf.vaddr_to_offset(va_end)
for
i
in
range
(start, end):
self
.elfData[i]
=
PatchHelper.opcode[
'nop'
]
def
get_nop_by_size(
self
, min_size):
for
idx, nop
in
enumerate
(
self
.nops):
if
nop[
1
] > min_size:
del
self
.nops[idx]
return
nop
return
(
-
1
,
0
)
def
do_patch(
self
, va_start, codes):
start
=
self
.elf.vaddr_to_offset(va_start)
for
i
in
range
(
len
(codes)):
self
.elfData[start
+
i]
=
codes[i]
def
patch_jmp(
self
, va_start, va_target):
offset
=
va_target
-
va_start
-
PatchHelper.JMP_SIZE
jmp
=
bytes([PatchHelper.opcode[
'jmp'
]])
+
struct.pack(
'<i'
, offset)
self
.do_patch(va_start, jmp)
return
PatchHelper.JMP_SIZE
def
patch_branches(
self
, bb, va_targets):
va_start, size
=
self
.get_patchable_from_relblk(bb)
if
size < PatchHelper.JMP_SIZE:
print
(
"[Warning] patch_jmp at block %x may fail. size: %d."
%
(bb.address, size))
org_start
=
va_start
print
(f
"va_start: {hex(va_start)}, bb addr: {hex(bb.address)}, size: {size}"
)
total_size
=
(
3
+
6
)
*
len
(va_targets)
-
4
if
size < total_size:
nx_va_start, nx_size
=
self
.get_nop_by_size(total_size)
if
nx_size
=
=
0
:
print
(
"\033[31m[Error]\033[0m `patch_branches` needs a nop block with size larger than %d."
%
(total_size))
self
.patch_jmp(va_start, nx_va_start)
va_start, size
=
nx_va_start, nx_size
for
i, t
in
enumerate
(va_targets[:
-
1
]):
cmp_instr
=
bytes([
0x83
,
0xfe
,i])
self
.do_patch(va_start, cmp_instr)
va_start
+
=
len
(cmp_instr)
cj_instr
=
bytes([PatchHelper.opcode[
'j'
],PatchHelper.opcode[
'e'
]])
if
t
=
=
-
1
:
cj_instr
+
=
struct.pack(
'<i'
,
self
.func_terminate
-
va_start
-
6
)
else
:
cj_instr
+
=
struct.pack(
'<i'
, t
-
va_start
-
6
)
self
.do_patch(va_start, cj_instr)
va_start
+
=
len
(cj_instr)
va_start
+
=
self
.patch_jmp(va_start, va_targets[
-
1
])
if
va_start > org_start
+
size:
print
(
"[Warning] patches at (%x, %x) overlaps next blk. "
%
(org_start, va_start))
def
get_relevant_blocks(cfg, patch_helper, main_dispatcher):
isCmpRI
=
lambda
instr: instr.mnemonic
=
=
"cmp"
and
\
hasattr
(instr.operands[
0
],
"_X86RegisterOperand__key"
)
and
\
hasattr
(instr.operands[
1
],
"_X86ImmediateOperand__key"
)
isCJmp
=
lambda
instr: instr.mnemonic.startswith(
"j"
)
and
\
instr.mnemonic !
=
"jmp"
isSubDispatcher
=
lambda
bb: (
len
(bb.instrs)
=
=
2
)
and
\
isCmpRI(bb.instrs[
0
])
and
isCJmp(bb.instrs[
1
])
relevant_blocks
=
[]
visited
=
set
()
q
=
SimpleQueue()
q.put(patch_helper.block(main_dispatcher))
while
not
q.empty():
bb
=
q.get()
if
isSubDispatcher(bb):
patch_helper.append_nop((bb.start_address, bb.size))
for
succ, cond
in
bb.branches:
if
succ
in
visited:
continue
q.put(patch_helper.block(succ))
visited.add(succ)
else
:
relevant_blocks.append(bb)
return
relevant_blocks
def
parse_logs(logfn, prologue, patch_helper):
with
open
(logfn,
"r"
) as f:
t
=
f.readlines()
i
=
0
selector_s
=
"selector: "
landingpad_s
=
"landingPad: "
relations
=
set
()
laddr
=
prologue
lselector
=
0
landingpad
=
0
while
i <
len
(t):
try
:
addr
=
int
(t[i],
16
)
except
:
i
+
=
1
continue
if
not
laddr
is
None
:
relations.add((laddr, lselector, addr))
if
t[i
+
1
].startswith(selector_s):
selector
=
int
(t[i
+
1
][
len
(selector_s):],
16
)
i
+
=
2
elif
t[i
+
1
].startswith(landingpad_s):
landingpad
=
int
(t[i
+
1
][
len
(landingpad_s):],
16
)
relations.add((addr,
-
1
, landingpad))
addr
=
landingpad
while
not
patch_helper.is_unreachable(patch_helper.block(addr).direct_branch):
addr
=
patch_helper.block(addr).direct_branch
if
t[i
+
2
].startswith(selector_s):
selector
=
int
(t[i
+
2
][
len
(selector_s):],
16
)
i
+
=
3
elif
t[i
+
1
].startswith(
"[Inferior "
):
i
+
=
1
else
:
print
(
"Warning: %x doesn't have selector. "
%
addr)
exit(
0
)
laddr
=
addr
lselector
=
selector
return
list
(relations)
def
generate_gdb_script(relevant_blocks):
cmds
=
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
)
filename
=
sys.argv[
1
]
start
=
int
(sys.argv[
2
],
16
)
origin
=
elf.ELF(filename)
b
=
angr.Project(filename, load_options
=
{
'auto_load_libs'
:
False
,
'main_opts'
:{
'custom_base_addr'
:
0
}})
barf
=
BARF(filename)
cfg
=
barf.recover_cfg(start
=
start)
patch_helper
=
PatchHelper(b, origin, barf, cfg)
blocks
=
cfg.basic_blocks
prologue
=
start
main_dispatcher
=
patch_helper.block(prologue).direct_branch
relevant_blocks
=
get_relevant_blocks(cfg, patch_helper, main_dispatcher)
nop
=
patch_helper.get_patchable_from_relblk(patch_helper.block(prologue))
patch_helper.append_nop(nop)
print
(
'*******************relevant blocks************************'
)
print
(
'main_dispatcher:%#x'
%
main_dispatcher)
print
(
'relevant_blocks:'
, [
hex
(bb.address)
for
bb
in
relevant_blocks])
if
len
(sys.argv) <
4
:
generate_gdb_script(relevant_blocks)
exit(
0
)
print
(
'************************flow******************************'
)
relations
=
parse_logs(sys.argv[
3
], prologue, patch_helper)
relations.sort(key
=
lambda
x:x)
flow
=
{}
for
bb, selector, child
in
relations:
if
bb
in
flow:
while
len
(flow[bb]) < selector:
flow[bb].append(
-
1
)
flow[bb].append(child)
assert
(
len
(flow[bb])
=
=
selector
+
1
)
else
:
flow[bb]
=
[child]
for
(k, v)
in
list
(flow.items()):
print
(
'%#x:'
%
k, [
hex
(child)
for
child
in
v])
print
(
'************************patch*****************************'
)
patch_helper.finalize()
for
(parent, childs)
in
list
(flow.items()):
blk
=
patch_helper.block(parent)
patch_helper.patch_branches(blk, childs)
for
idx, instr
in
enumerate
(blk.instrs):
if
patch_helper.is_call_allocate_exception(instr)
or
\
patch_helper.is_call_obf_exception(instr):
start
=
instr.address
end
=
instr.address
+
instr.size
patch_helper.fill_nops(start, end)
with
open
(filename
+
'.recovered'
,
'wb'
) as f:
f.write(bytes(patch_helper.elfData))
print
(
'Successful! The recovered file: %s'
%
(filename
+
'.recovered'
))
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
class
PatchHelper:
opcode
=
{
'a'
:
0x87
,
'ae'
:
0x83
,
'b'
:
0x82
,
'be'
:
0x86
,
'c'
:
0x82
,
'e'
:
0x84
,
'z'
:
0x84
,
'g'
:
0x8F
,
'ge'
:
0x8D
,
'l'
:
0x8C
,
'le'
:
0x8E
,
'na'
:
0x86
,
'nae'
:
0x82
,
'nb'
:
0x83
,
'nbe'
:
0x87
,
'nc'
:
0x83
,
'ne'
:
0x85
,
'ng'
:
0x8E
,
'nge'
:
0x8C
,
'nl'
:
0x8D
,
'nle'
:
0x8F
,
'no'
:
0x81
,
'np'
:
0x8B
,
'ns'
:
0x89
,
'nz'
:
0x85
,
'o'
:
0x80
,
'p'
:
0x8A
,
'pe'
:
0x8A
,
'po'
:
0x8B
,
's'
:
0x88
,
'nop'
:
0x90
,
'jmp'
:
0xE9
,
'j'
:
0x0F
}
JMP_SIZE
=
5
def
is_unreachable(
self
, bb):
if
isinstance
(bb,
int
):
bb
=
self
.block(bb)
for
i
in
range
(
len
(bb.instrs)):
if
bb.instrs[i].mnemonic !
=
"call"
:
continue
target
=
bb.instrs[i].operands[
0
].immediate
if
target
=
=
self
.func_terminate:
return
True
def
block(
self
, addr):
bb
=
self
.cfg.find_basic_block(addr)
if
bb
is
None
:
bb
=
barf.bb_builder.strategy._disassemble_bb(addr, barf.binary.ea_end, {})
return
bb
@staticmethod
def
is_imm(operand):
return
(
hasattr
(operand,
"_X86ImmediateOperand__key"
))
@staticmethod
def
is_reg(operand):
return
(
hasattr
(operand,
"_X86RegisterOperand__key"
))
def
is_call_throw(
self
, instr):
return
instr.mnemonic
=
=
"call"
and
\
self
.is_imm(instr.operands[
0
])
and
\
instr.operands[
0
].immediate
=
=
self
.func_throw
def
is_call_allocate_exception(
self
, instr):
return
instr.mnemonic
=
=
"call"
and
\
self
.is_imm(instr.operands[
0
])
and
\
instr.operands[
0
].immediate
=
=
self
.func_allocate_exception
def
is_call_obf_exception(
self
, instr):
return
instr.mnemonic
=
=
"call"
and
\
self
.is_imm(instr.operands[
0
])
and
\
instr.operands[
0
].immediate
=
=
self
.func_obf_exception
def
skip_call_args(
self
, bb, i):
while
((bb.instrs[i].mnemonic
in
[
"xor"
,
"mov"
,
"lea"
])
and
\
(
len
(bb.instrs[i].operands) >
0
)
and
(
self
.is_reg(bb.instrs[i].operands[
0
]))
and
\
(bb.instrs[i].operands[
0
].name
in
[
"edx"
,
"rdx"
,
"esi"
,
"rsi"
,
"edi"
,
"rdi"
]))
or
\
bb.instrs[i].mnemonic
=
=
"nop"
:
i
-
=
1
return
i
def
get_patchable_from_relblk(
self
, bb):
i
=
0
end
=
bb.start_address
+
bb.size
while
i <
len
(bb.instrs)
and
not
self
.is_call_throw(bb.instrs[i]):
i
+
=
1
i
=
self
.skip_call_args(bb, i
-
1
)
if
i
=
=
len
(bb.instrs)
-
1
:
start
=
end
else
:
start
=
bb.instrs[i
+
1
].address
self
.fill_nops(start, end)
return
(start, end
-
start)
def
__init__(
self
, proj, elf, barf, cfg)
-
>
None
:
self
.p
=
proj
obj
=
proj.loader.main_object
self
.func_terminate
=
obj.symbols_by_name[
"__clang_call_terminate"
].rebased_addr
self
.func_throw
=
obj.plt[
"__cxa_throw"
]
self
.func_allocate_exception
=
obj.plt[
"__cxa_allocate_exception"
]
self
.func_obf_exception
=
obj.symbols_by_name[
"_ZN18StdSubObfExceptionC2Ec"
].rebased_addr
self
.elf
=
elf
self
.elfData
=
bytearray(
self
.elf.data)
self
.barf
=
barf
self
.cfg
=
cfg
self
.nops
=
[]
def
append_nop(
self
, nopblk):
if
nopblk[
1
] >
0
:
self
.nops.append(nopblk)
def
finalize(
self
):
self
.nops.sort()
idx
=
0
while
idx <
len
(
self
.nops)
-
1
:
if
self
.nops[idx][
0
]
+
self
.nops[idx][
1
] !
=
self
.nops[idx
+
1
][
0
]:
idx
+
=
1
continue
self
.nops[idx]
=
(
self
.nops[idx][
0
],
self
.nops[idx][
1
]
+
self
.nops[idx
+
1
][
1
])
del
self
.nops[idx
+
1
]
def
fill_nops(
self
, va_start, va_end):
assert
not
self
.elf
is
None
start
=
self
.elf.vaddr_to_offset(va_start)
end
=
self
.elf.vaddr_to_offset(va_end)
for
i
in
range
(start, end):
self
.elfData[i]
=
PatchHelper.opcode[
'nop'
]
def
get_nop_by_size(
self
, min_size):
for
idx, nop
in
enumerate
(
self
.nops):
if
nop[
1
] > min_size:
del
self
.nops[idx]
return
nop
return
(
-
1
,
0
)
def
do_patch(
self
, va_start, codes):
start
=
self
.elf.vaddr_to_offset(va_start)
for
i
in
range
(
len
(codes)):
self
.elfData[start
+
i]
=
codes[i]
def
patch_jmp(
self
, va_start, va_target):
offset
=
va_target
-
va_start
-
PatchHelper.JMP_SIZE
jmp
=
bytes([PatchHelper.opcode[
'jmp'
]])
+
struct.pack(
'<i'
, offset)
self
.do_patch(va_start, jmp)
return
PatchHelper.JMP_SIZE
def
patch_branches(
self
, bb, va_targets):
va_start, size
=
self
.get_patchable_from_relblk(bb)
if
size < PatchHelper.JMP_SIZE:
print
(
"[Warning] patch_jmp at block %x may fail. size: %d."
%
(bb.address, size))
org_start
=
va_start
print
(f
"va_start: {hex(va_start)}, bb addr: {hex(bb.address)}, size: {size}"
)
total_size
=
(
3
+
6
)
*
len
(va_targets)
-
4
if
size < total_size:
nx_va_start, nx_size
=
self
.get_nop_by_size(total_size)
if
nx_size
=
=
0
:
print
(
"\033[31m[Error]\033[0m `patch_branches` needs a nop block with size larger than %d."
%
(total_size))
self
.patch_jmp(va_start, nx_va_start)
va_start, size
=
nx_va_start, nx_size
for
i, t
in
enumerate
(va_targets[:
-
1
]):
cmp_instr
=
bytes([
0x83
,
0xfe
,i])
self
.do_patch(va_start, cmp_instr)
va_start
+
=
len
(cmp_instr)
cj_instr
=
bytes([PatchHelper.opcode[
'j'
],PatchHelper.opcode[
'e'
]])
if
t
=
=
-
1
:
cj_instr
+
=
struct.pack(
'<i'
,
self
.func_terminate
-
va_start
-
6
)
else
:
cj_instr
+
=
struct.pack(
'<i'
, t
-
va_start
-
6
)
self
.do_patch(va_start, cj_instr)
va_start
+
=
len
(cj_instr)
va_start
+
=
self
.patch_jmp(va_start, va_targets[
-
1
])
if
va_start > org_start
+
size:
print
(
"[Warning] patches at (%x, %x) overlaps next blk. "
%
(org_start, va_start))
def
get_relevant_blocks(cfg, patch_helper, main_dispatcher):
isCmpRI
=
lambda
instr: instr.mnemonic
=
=
"cmp"
and
\
hasattr
(instr.operands[
0
],
"_X86RegisterOperand__key"
)
and
\
hasattr
(instr.operands[
1
],
"_X86ImmediateOperand__key"
)
isCJmp
=
lambda
instr: instr.mnemonic.startswith(
"j"
)
and
\
instr.mnemonic !
=
"jmp"
isSubDispatcher
=
lambda
bb: (
len
(bb.instrs)
=
=
2
)
and
\
isCmpRI(bb.instrs[
0
])
and
isCJmp(bb.instrs[
1
])
relevant_blocks
=
[]
visited
=
set
()
q
=
SimpleQueue()
q.put(patch_helper.block(main_dispatcher))
while
not
q.empty():
bb
=
q.get()
if
isSubDispatcher(bb):
patch_helper.append_nop((bb.start_address, bb.size))
for
succ, cond
in
bb.branches:
if
succ
in
visited:
continue
q.put(patch_helper.block(succ))
visited.add(succ)
else
:
relevant_blocks.append(bb)
return
relevant_blocks
def
parse_logs(logfn, prologue, patch_helper):
with
open
(logfn,
"r"
) as f:
t
=
f.readlines()
i
=
0
selector_s
=
"selector: "
landingpad_s
=
"landingPad: "
relations
=
set
()
laddr
=
prologue
lselector
=
0
landingpad
=
0
while
i <
len
(t):
try
:
addr
=
int
(t[i],
16
)
except
:
i
+
=
1
continue
if
not
laddr
is
None
:
relations.add((laddr, lselector, addr))
if
t[i
+
1
].startswith(selector_s):
selector
=
int
(t[i
+
1
][
len
(selector_s):],
16
)
i
+
=
2
elif
t[i
+
1
].startswith(landingpad_s):
landingpad
=
int
(t[i
+
1
][
len
(landingpad_s):],
16
)
relations.add((addr,
-
1
, landingpad))
addr
=
landingpad
while
not
patch_helper.is_unreachable(patch_helper.block(addr).direct_branch):
addr
=
patch_helper.block(addr).direct_branch
if
t[i
+
2
].startswith(selector_s):
selector
=
int
(t[i
+
2
][
len
(selector_s):],
16
)
i
+
=
3
elif
t[i
+
1
].startswith(
"[Inferior "
):
i
+
=
1
else
:
print
(
"Warning: %x doesn't have selector. "
%
addr)
exit(
0
)
laddr
=
addr
lselector
=
selector
return
list
(relations)
def
generate_gdb_script(relevant_blocks):
cmds
=
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
)
filename
=
sys.argv[
1
]
start
=
int
(sys.argv[
2
],
16
)
origin
=
elf.ELF(filename)
b
=
angr.Project(filename, load_options
=
{
'auto_load_libs'
:
False
,
'main_opts'
:{
'custom_base_addr'
:
0
}})
barf
=
BARF(filename)
cfg
=
barf.recover_cfg(start
=
start)
patch_helper
=
PatchHelper(b, origin, barf, cfg)
blocks
=
cfg.basic_blocks
prologue
=
start
main_dispatcher
=
patch_helper.block(prologue).direct_branch
relevant_blocks
=
get_relevant_blocks(cfg, patch_helper, main_dispatcher)
nop
=
patch_helper.get_patchable_from_relblk(patch_helper.block(prologue))
patch_helper.append_nop(nop)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-7-21 22:31
被P.Z编辑
,原因:
上传的附件: