源码及二进制文件链接: https://github.com/angr/angr-doc/tree/master/examples/asisctffinals2015_license
分析题目:
运行二进制文件,输出:
key file not found!
看来该二进制需要读取一个文件,拖到IDA里看它需要读取的文件路径及名称是什么。
找到以后发现是:"_a\nb\tc_"。
那创建一个名为"_a\nb\tc_"的文件。创建这个文件的时候是有知识点的呀,“\n”“\t”都是转义符,所以最后创建的文件名称是这个样子的:
这么奇怪的名字怎么创建呢?就是写个c代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
FILE* fstream;
char msg[100] = "Hello!I have read this file.";
fstream=fopen("_a\nb\tc_","at+");
if(fstream==NULL)
{
printf("open file _a\\nb\\tc_ failed!\n");
exit(1);
}
else
{
printf("open file _a\\nb\\tc_ succeed!\n");
}
fclose(fstream);
return 0;
}
再次执行 license 文件,这时候提示:wrong formatted key file。文件格式又不对,此时就是对文件的内容进行检测了,直接上符号执行,求解对文件内容的约束。
解决思路:
1.将文件内容初始化为符号变量,加载到license程序中。
2.找到输出success等字符串的调用地址。
3.找到从读取文件到success调用地址的路径。
4.确定flag的存放地址:flag_addr。
5.求解,读取flag_addr的内容。
第一步,初始化符号变量时,用例代码是将文件的格式确定了下来,这样可以减少符号执行的计算复杂度和计算时间。
文件格式为:5行,每行包含6个字符,这部分是通过IDA逆向源码获得。还是一个权衡的问题,如果自己懒得看这部分,直接将格式未知的符号文件传入,可能需要等待更长的时间才能得到结果。
输出success的地址为:
0x400E93
flag存放的地址为:上图中rsp+0x278-0xd8。
其实下面的用例代码做了很多优化,比如事先确定好文件格式,以及在求解后通过sim_procedures模拟strlen函数估计flag的长度。
事实证明求解flag长度这一部分是可以省略的,将flag_length_int替换为大于等于38的整数,也可以读取出flag。
import angr
def main():
p = angr.Project("license", load_options={'auto_load_libs': False})
# Create a blank state
state = p.factory.blank_state()
# Build the file whose name is weird
license_name = "_a\nb\tc_"
# This is the license file
# From analyzing the binary, we know that the license file should have five
# lines in total, and each line has 6 characters. Not setting file content
# may also work, but in that case, angr will produce many more paths, and we
# will spent much more time in path trimming.
bytes = None
constraints = [ ]
for i in xrange(5):
line = [ ]
for j in xrange(6):
line.append(state.solver.BVS('license_file_byte_%d_%d' % (i, j), 8))
state.add_constraints(line[-1] != 0x0a)
if bytes is None:
bytes = state.solver.Concat(*line)
else:
bytes = state.solver.Concat(bytes, state.solver.BVV(0x0a, 8), *line)
content = angr.state_plugins.SimSymbolicMemory(memory_id="file_%s" % license_name)
content.set_state(state)
content.store(0, bytes)
license_file = angr.storage.SimFile(license_name, 'rw', content=content, size=len(bytes) / 8)
# Build the file system dict
# This interface might change in the near future
fs = {
license_name: license_file
}
state.posix.fs = fs
ex = p.surveyors.Explorer(
start=state,
find=(0x400e93, ),
avoid=(0x400bb1, 0x400b8f, 0x400b6d, 0x400a85,
0x400ebf, 0x400a59)
)
ex.run()
# One path will be found
found = ex.found[0]
rsp = found.regs.rsp
flag_addr = rsp + 0x278 - 0xd8 # Ripped from IDA
# Perform an inline call to strlen() in order to determine the length of the
# flag
FAKE_ADDR = 0x100000
strlen = lambda state, arguments: \
angr.SIM_PROCEDURES['libc']['strlen'](p, FAKE_ADDR, p.arch).execute(
state, arguments=arguments
)
flag_length = strlen(found, arguments=[flag_addr]).ret_expr
# In case it's not null-terminated, we get the least number as the length
flag_length_int = min(found.solver.eval_upto(flag_length, 3))
# Read out the flag!
flag_int = found.solver.eval(found.memory.load(flag_addr, flag_length_int))
flag = hex(flag_int)[2:-1].decode("hex")
#print state.posix.fs[license_name]
return flag
def test():
assert main() == 'ASIS{8d2cc30143831881f94cb05dcf0b83e0}'
if __name__ == '__main__':
print main()