此篇来了解一下如何对内存寄存器进行直接存取
对于.bss
段等固定地址的变量我们可以利用claripy
直接地址写入,进行初始化state
。下面我通过一个例子进行简单说明。
sym-write
载入IDA
其中变量u
位于.bss
段,是未初始化的变量,我们可以在state
状态,初始化simulation_manager
的state
时,将其设置.
为了可以进行符号地址的写入,在设置state
需要申明add_options={"SYMBOLIC_WRITE_ADDRESSES"}
angr提供了SimMemory
类对内存进行操作。
并且提供了两种方法
通过上下文,可以知道u
是一个8bit的值。因此我们通过state.memory.store(0x804a021, u)
将u
存储到0x804a021
处
然后设置需要到达的路径即可
sm.explore(find=0x80484e3, avoid=0x80484f5)
是不是很简单呢~
例题来自flareon2015_2
IDA载入,发现是windows程序,而且逻辑也很简单。
为了避免调用windows
的API,我们需要从0x401084
设为起始状态,并且设置好传入的参数。
通过上下文,可以知道0x402159
存放的是输入的数据,根据windows 32 位参数传递规则,我们可以如下进行构造。
s.memory.store(s.regs.esp+12, s.solver.BVV(40, s.arch.bits))
s.mem[s.regs.esp+8:].dword = 0x402159
s.mem[s.regs.esp+4:].dword = 0x4010e4
s.mem[s.regs.esp:].dword = 0x401064
其中s.mem[s.regs.esp:].dword
用来设置内存的值,并且大小为dword
angr支持许多类型的数据,包括dword
,word
,long
,int
,uint8_t
,uint32_t
等等。我建议只要对自己的理解不产生影响,选取合适类型即可。我通常会选择uint8_t
之类的数据类型,毕竟对linux
编程比较熟悉。
完整代码如下:
#!/usr/bin/env python
import angr
def main():
b = angr.Project("very_success", load_options={"auto_load_libs":False})
# create a state at the checking function
# Since this is a windows binary we have to start after the windows library calls
# remove lazy solves since we don't want to explore unsatisfiable paths
s = b.factory.blank_state(addr=0x401084)
# set up the arguments on the stack
s.memory.store(s.regs.esp+12, s.solver.BVV(40, s.arch.bits))
s.mem[s.regs.esp+8:].dword = 0x402159
s.mem[s.regs.esp+4:].dword = 0x4010e4
s.mem[s.regs.esp:].dword = 0x401064
# store a symbolic string for the input
s.memory.store(0x402159, s.solver.BVS("ans", 8*40))
# explore for success state, avoiding failure
sm = b.factory.simulation_manager(s)
sm.explore(find=0x40106b, avoid=0x401072)
# print(the string)
found_state = sm.found[0]
return found_state.solver.eval(found_state.memory.load(0x402159, 40), cast_to=bytes).strip(b'\0')
def test():
assert main() == b'[email protected]'
if __name__ == '__main__':
print(main())
angrbird
这题比较经典。
首先程序很直接的反调试,而且又是这种线性的混淆,这用angr来解决是再合适不过了。
一条斜线。有点飘。
其中刚开始的部分就是反调试,通常的做法是patch
程序,不过这里用angr来解决。
为了绕过反调试我们需要将入口地址设置在mov rdx, cs:stdin
也就是0x4007C2
,同时结合IDA的分析,我们需要将mov [rbp+var_70], offset off_606018
布局到相应的内存中,这里应该是在设置函数表。
同时我们还应该设置好fgets
函数的参数mov ecx, [rbp+n]
其中的[rbp+n]
应该设置为21
同时为了执行程序还应该设置mov rbp, rsp
这几步是必不可少的,因为跳过反调试,需要一定的代价。
因此初始化部分代码如下:
state.regs.rbp = state.regs.rsp
state.mem[state.regs.rbp - 0x74].uint32_t = 0x40
state.mem[state.regs.rbp - 0x70].uint64_t = 0x1000
state.mem[state.regs.rbp - 0x68].uint64_t = 0x1008
state.mem[state.regs.rbp - 0x60].uint64_t = 0x1010
state.mem[state.regs.rbp - 0x58].uint64_t = 0x1018
正确设置完初始状态后就是正常的执行了。
此题使用angr有两种方法,分别是在不同的地方进行条件约束,我个人认为对angr的应用会有所帮助。仅做简单记录。
google2016_unbreakable_1
通过命令行输入,并设置条件约束。
p = angr.Project('unbreakable',load_options={"auto_load_libs": False})
argv = claripy.BVS("argv",0x43*8)
state = p.factory.entry_state(args={"./unbreakable",argv},add_options={angr.options.LAZY_SOLVES})
state.libc.buf_symbolic_bytes=0x43 + 1
for byt in argv.chop(8):
state.add_constraints(state.solver.And(byt >= ord(' '),byt <= ord('~')))
其中的state.libc.buf_symbolic_bytes=0x43 + 1
是非常有必要的,我看官方所说,angr默认的symbolic_bytes
只有60bytes,对于这题来说太小了。也就是说如果命令行传入的大小大于默认的值,所以需要手动调整大小。
而且条件约束也是十分有必要的,这里说明一下chop
方法:
意思就是截取,所以我们每8bits截取,然后进行条件约束。
代码如下:
import angr
import claripy
START_ADDR = 0x4005bd # first part of program that does computation
AVOID_ADDR = 0x400850 # address of function that prints wrong
FIND_ADDR = 0x400830 # address of function that prints correct
INPUT_ADDR = 0x6042c0 # location in memory of user input
INPUT_LENGTH = 0xf2 - 0xc0 + 1 # derived from the first and last character
# reference in data
def extract_memory(state):
"""Convience method that returns the flag input memory."""
return state.solver.eval(state.memory.load(INPUT_ADDR, INPUT_LENGTH), cast_to=bytes)
def main():
p = angr.Project('unbreakable',load_options={"auto_load_libs": False})
argv = claripy.BVS("argv",0x43*8)
state = p.factory.entry_state(args={"./unbreakable",argv},add_options={angr.options.LAZY_SOLVES})
state.libc.buf_symbolic_bytes=0x43 + 1
for byt in argv.chop(8):
state.add_constraints(state.solver.And(byt >= ord(' '),byt <= ord('~')))
ex = p.factory.simulation_manager(state)
ex.explore(find=(FIND_ADDR,), avoid=(AVOID_ADDR,))
flag = extract_memory(ex.found[0]) # ex.one_found is equiv. to ex.found[0]
print(flag)
if __name__ == '__main__':
main()
第二种方法跳过命令行输入的过程,直接将值存储到引用的内存当中,并设置条件约束。比较简单。代码如下:
import angr
import claripy
START_ADDR = 0x4005bd # first part of program that does computation
AVOID_ADDR = 0x400850 # address of function that prints wrong
FIND_ADDR = 0x400830 # address of function that prints correct
INPUT_ADDR = 0x6042c0 # location in memory of user input
INPUT_LENGTH = 0xf2 - 0xc0 + 1 # derived from the first and last character
def main():
p = angr.Project('unbreakable')
state = p.factory.blank_state(addr=START_ADDR, add_options={angr.options.LAZY_SOLVES})
flag_chars = [state.solver.BVS('flag_%d' % i, 8) for i in range(0x43)]
for flag_chr in flag_chars:
state.add_constraints(state.solver.And(flag_chr >= ord(' '),flag_chr <= ord('~')))
for i in range(0x43):
state.memory.store(INPUT_ADDR+i,flag_chars[i])
#state.
ex = p.factory.simulation_manager(state)
ex.explore(find=(FIND_ADDR,), avoid=(AVOID_ADDR,))
found = ex.found[0]
flag = found.solver.eval(state.memory.load(INPUT_ADDR, INPUT_LENGTH), cast_to=bytes)
print(flag)
if __name__ == '__main__':
main()
通过以上四道例题,相信大家对angr的内存寄存器有一定的了解,如果有必要,我们可以另设一个起始状态,然后设置好所需的参数。
下面后介绍angr中的Hook