前言

此篇来了解一下如何对内存寄存器进行直接存取

通过直接地址写入

对于.bss段等固定地址的变量我们可以利用claripy直接地址写入,进行初始化state。下面我通过一个例子进行简单说明。

例题来自于sym-write

载入IDA

其中变量u位于.bss段,是未初始化的变量,我们可以在state状态,初始化simulation_managerstate时,将其设置.

为了可以进行符号地址的写入,在设置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

源链接

Hacking more

...