分析
题目给了一个log和一个纯机器码文件
机器码文件是MIPS架构的,我们可以用32位IDA打开,然后选择目标处理器为小端的MIPS即可。
MIPS指令
通过分析,我们发现在栈上有一段缓冲区最先由已知字符串常量初始化的,然后一个未知的字符串(也就是flag)被添加到后面,最终由相同的快速排序转化进行了排序。我们可以通过符号执行,将状态一步步转移到已知分支,然后符号的值就是flag了。
代码分析https://github.com/fangdada/ctf/tree/master/0CTF2016/trace
反汇编
也可以通过如下脚本反汇编机器码
#!/usr/bin/env python
from __future__ import print_function
from capstone import *
from capstone.mips import *
f=open('./data.bin','rb')
arch=CS_ARCH_MIPS
mode=CS_MODE_MIPS32+CS_MODE_LITTLE_ENDIAN
code=f.read()
comment="MIPS-32 (little-endian)"
print("platform:"+comment)
print("disasm:")
try:
md=Cs(arch,mode)
md.detial=True
for insn in md.disasm(code,0):
print("0x%x: \t%s\t%s" % (insn.address, insn.mnemonic, insn.op_str))
except CsError as e:
print("ERROR: %s" % e)
对应LOG文件和反汇编信息,可以得知,程序的加载地址为0x004009d4
bin文件加载
我们要用执行引擎模拟执行,先来看如下代码:
project = angr.Project("./data.bin", load_options={
'main_opts': {
'backend': 'blob',
'base_addr': 0x400770,
'arch': 'mipsel',
},
})
跟常规的加载选项不同,这一次用了blob后端引擎使用mipsel
架构来加载机器码文件,然后就可以模拟执行了。
Trace
state = project.factory.blank_state(addr=MAIN_START)
state.memory.store(FLAG_LOCATION, state.solver.BVS("flag", 8*32))
state.memory.store(FLAG_PTR_LOCATION, struct.pack("<I", FLAG_LOCATION))
sm = project.factory.simulation_manager(state)
choices = [state]
因为是用机器码模拟执行所以要用空状态初始化,然后设置一下开始地址,再把solver的变量存储进入求解,最后同样作为参数传给simulation_manager
就可以准备执行了。
- 解析trace的log文件代码如下:
def load_trace():
res = []
delay_slots = set()
with open("./trace_8339a701aae26588966ad9efa0815a0a.log") as f:
for line in f:
if line.startswith('[INFO]'):
addr = int(line[6:6+8], 16)
res.append(addr)
# every command like this is in delay slot
# (in this particular binary)
if ("move r1, r1" in line):
delay_slots.add(addr)
return res, delay_slots
trace_log
保存了trace的每个指令的地址,delay_slots
保存了适应处理器流水线设计的延迟指令。
trace_log, delay_slots = load_trace()
print("Tracing...")
for i, addr in enumerate(trace_log):
if addr in delay_slots:
continue
for s in choices:
if s.addr == addr:
break
else:
raise ValueError("couldn't advance to %08x, line %d" % (addr, i+1))
if s.addr == MAIN_END:
break
# if command is a jump, it's followed by a delay slot
# we need to advance by two instructions
# https://github.com/angr/angr/issues/71
if s.addr + 4 in delay_slots:
choices = project.factory.successors(s, num_inst=2).successors
else:
choices = project.factory.successors(s, num_inst=1).successors
state = s
重点来看choices=project.factory.successors(s, num_inst=2).successors
。
首先我们来看看官方文档里的解释:
successors
(simgr, state, **kwargs)
Perform the process of stepping a state forward, returning a SimSuccessors object.
- num_inst – The maximum number of instructions.
successors
会使用适合的模拟器(Simulation)执行,步进(step)一个状态(state),num_inst
指定了最大的步进的语句数。num_inst=2
就是为了跳过延迟指令delay slots
,否则会丢失状态分支,原因正如官方注释里的那个issue。之前那个for s in choices……的语句段就是让s指向最后的状态,而不会让s指向那个delay slot
,不然脚本会出问题。
等脚本最终trace完了之后state=s使state为最终状态,这时候之前保存的符号变量应当为flag了,将solver里的值取出转化为字符串就能得到flag了:
print("Running solver...")
solution = state.solver.eval(state.memory.load(FLAG_LOCATION, 32), cast_to=bytes)
print("The flag is", bytes.decode(solution).strip('\x00'))
return solution
EXP
#!/usr/bin/env python2
"""
In this challenge we're given a text file with trace of a program execution. The file has
two columns, address and instruction executed. So we know all the instructions being executed,
and which branches were taken. But the initial data is not known.
Reversing reveals that a buffer on the stack is initialized with known constant string first,
then an unknown string is appended to it (the flag), and finally it's sorted with some
variant of quicksort. And we need to find the flag somehow.
angr easily solves this problem. We only have to direct it to the right direction
at every branch, and solver finds the flag at a glance.
"""
from __future__ import print_function
import struct
import angr
MAIN_START = 0x4009d4
MAIN_END = 0x00400c18
FLAG_LOCATION = 0x400D80
FLAG_PTR_LOCATION = 0x410EA0
def load_trace():
res = []
delay_slots = set()
with open("./trace_8339a701aae26588966ad9efa0815a0a.log") as f:
for line in f:
if line.startswith('[INFO]'):
addr = int(line[6:6 + 8], 16)
res.append(addr)
# every command like this is in delay slot
# (in this particular binary)
if ("move r1, r1" in line):
delay_slots.add(addr)
return res, delay_slots
def main():
trace_log, delay_slots = load_trace()
# data.bin is simply the binary assembled from trace,
# starting on 0x400770
project = angr.Project("./data.bin", load_options={
'main_opts': {
'backend': 'blob',
'base_addr': 0x400770,
'arch': 'mipsel',
},
})
state = project.factory.blank_state(addr=MAIN_START,add_options=angr.options.unicorn)
state.memory.store(FLAG_LOCATION, state.solver.BVS("flag", 8 * 32))
state.memory.store(FLAG_PTR_LOCATION, struct.pack("<I", FLAG_LOCATION))
# sm = project.factory.simulation_manager(state) #why? not use it
choices = [state]
print("Tracing...")
for i, addr in enumerate(trace_log):
if addr in delay_slots:
continue
for s in choices: # find the state of this address in the choices
if s.addr == addr:
break
else:
raise ValueError("couldn't advance to %08x, line %d" % (addr, i + 1))
if s.addr == MAIN_END:
break
# if command is a jump, it's followed by a delay slot
# we need to advance by two instructions
# https://github.com/angr/angr/issues/71
if s.addr + 4 in delay_slots:
choices = project.factory.successors(s, num_inst=2).successors
else:
choices = project.factory.successors(s, num_inst=1).successors
state = s
print("Running solver...")
solution = state.solver.eval(state.memory.load(FLAG_LOCATION, 32), cast_to=bytes)
print("The flag is", bytes.decode(solution).strip('\x00'))
return solution
# def test():
# assert main() == "0ctf{tr135m1k5l96551s9l5r}"
if __name__ == "__main__":
main()
知识点
- 利用python反汇编代码
- delay_slots的作用 (关于延时槽,可参阅https://blog.csdn.net/groundhappy/article/details/71711938 )
- trace代码和解析trace日志
Comments | NOTHING