根据 mov ds:buffer0, eax
和 mov ds:buffer1, eax
得知开辟后的缓冲区被复制到标识为 buffer0
和 buffer1
的两个存储区中
且根据 IDA 可以得知 buffer0
的地址为 0xABCC8A4 ,buffer1
的地址为 0xABCC8AC
回到我们最开始认识 angr 的时候,我们知道 angr 并没有真正“运行”二进制文件(至少到目前为止),它只是在模拟运行状态,因此它实际上不需要将内存分配到堆中,实际上可以伪造任何地址。我们所做的是我们在堆栈选择两个地址存放我们的缓冲区地址。之后我们告诉 angr,将两个 fake address 分别保存到 buffer0
, buffer1
,因为程序实际执行的时候就会把 malloc 返回的地址保存到这里。最后我们把符号位向量保存到伪造的地址里。
这里我们选择 0xffffc93c 和 0xffffc94c 即可(随便搞),然后将我们分别的缓冲区地址放入这两个地址中,参数 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 = 0x ffffc93c
pointer_to_malloc_memory_address0 = 0x abcc8a4
fake_heap_address1 = 0x ffffc94c
pointer_to_malloc_memory_address1 = 0x abcc8ac
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 = 0x 8048699
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 = 0x ffffc93c
pointer_to_malloc_memory_address0 = 0x abcc8a4
fake_heap_address1 = 0x ffffc94c
pointer_to_malloc_memory_address1 = 0x abcc8ac
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()