根据 mov ds:buffer0, eaxmov ds:buffer1, eax 得知开辟后的缓冲区被复制到标识为 buffer0buffer1 的两个存储区中

且根据 IDA 可以得知 buffer0 的地址为 0xABCC8A4buffer1 的地址为 0xABCC8AC

回到我们最开始认识 angr 的时候,我们知道 angr 并没有真正“运行”二进制文件(至少到目前为止),它只是在模拟运行状态,因此它实际上不需要将内存分配到堆中,实际上可以伪造任何地址。我们所做的是我们在堆栈选择两个地址存放我们的缓冲区地址。之后我们告诉 angr,将两个 fake address 分别保存到 buffer0, buffer1 ,因为程序实际执行的时候就会把 malloc 返回的地址保存到这里。最后我们把符号位向量保存到伪造的地址里。

这里我们选择 0xffffc93c0xffffc94c 即可(随便搞),然后将我们分别的缓冲区地址放入这两个地址中,参数 endness 用于设置端序,angr 默认为大端序,总共可选的值如下:

LE – 小端序 (little endian, least significant byte is stored at lowest address) BE – 大端序 (big endian, most significant byte is stored at lowest address) ME – 中间序 (Middle-endian. Yep.)

这里我们直接设置为与项目的程序相同即可

fake_heap_address0 = 0xffffc93c  
pointer_to_malloc_memory_address0 = 0xabcc8a4  
fake_heap_address1 = 0xffffc94c  
pointer_to_malloc_memory_address1 = 0xabcc8ac  
initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness)  
initial_state.memory.store(pointer_to_malloc_memory_address1, fake_heap_address1, endness=project.arch.memory_endness)

这里总的逻辑是这样的,之前是 buffer 指向的是 malloc 分配好的内存地址,string 存在这里。现在是 buffer 指向的是我们伪造的地址,符号位向量存在这里

BEFORE:  
buffer0 -> malloc()ed address 0 -> string 0  
buffer1 -> malloc()ed address 1 -> string 1  
​  
AFTER:  
buffer0 -> fake address 0 -> symbolic bitvector 0  
buffer1 -> fake address 1 -> symbolic bitvector 1
import angr  
import claripy  
  
  
def Go():  
    path_to_binary = "./06_angr_symbolic_dynamic_memory"  
    project = angr.Project(path_to_binary, auto_load_libs=False)  
    start_address = 0x8048699  
    initial_state = project.factory.blank_state(addr=start_address)  
  
    passwd_size_in_bits = 64  
    passwd0 = claripy.BVS('passwd0', passwd_size_in_bits)  
    passwd1 = claripy.BVS('passwd1', passwd_size_in_bits)  
  
    fake_heap_address0 = 0xffffc93c  
    pointer_to_malloc_memory_address0 = 0xabcc8a4  
    fake_heap_address1 = 0xffffc94c  
    pointer_to_malloc_memory_address1 = 0xabcc8ac  
    initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0,  
                               endness=project.arch.memory_endness)  
    initial_state.memory.store(pointer_to_malloc_memory_address1, fake_heap_address1,  
                               endness=project.arch.memory_endness)  
  
    initial_state.memory.store(fake_heap_address0, passwd0)  
    initial_state.memory.store(fake_heap_address1, passwd1)  
  
    simulation = project.factory.simgr(initial_state)  
  
    def is_successful(state):  
        stdout_output = state.posix.dumps(1)  
        if b'Good Job.\n' in stdout_output:  
            return True  
        else:  
            return False  
  
    def should_abort(state):  
        stdout_output = state.posix.dumps(1)  
        if b'Try again.\n' in stdout_output:  
            return True  
        else:  
            return False  
  
    simulation.explore(find=is_successful, avoid=should_abort)  
  
    if simulation.found:  
        for i in simulation.found:  
            solution_state = i  
            solution0 = solution_state.solver.eval(passwd0, cast_to=bytes)  
            solution1 = solution_state.solver.eval(passwd1, cast_to=bytes)  
            print("[+] Success! Solution is: {0} {1}".format(solution0.decode('utf-8'), solution1.decode('utf-8')))  
            # print(solution0, solution1)  
    else:  
        raise Exception('Could not find the solution')  
  
  
if __name__ == "__main__":  
    Go()