本篇直接以一个例子来进一步学习angr在CTF比赛中的实战
2021年SCTF上的checkin(虽然叫checkin但是并不是很checkin就是了)
这是一个auto pwn,每次连上去先进行一个sha256的验证,然后会给出一大段base64加密后的数据,接收下来然后解密并保存成二进制文件,是一个ELF格式的可执行程序,拖进ida里面会发现,程序逻辑很简单但是量很大,通过不断的读入字符串和数字,然后对数据进行异或,异或之后再和一些固定数据比较,根据比较结果进行跳转。并且每次连上去拿到的程序不一样,要求在120秒以内跑完(事实证明其实十秒以内就跑的完)。
这个题在最深的地方有一个栈溢出,并且给了后门函数,也就是说我们需要用自动化的工具先把能走到溢出点的输入数据给跑出来,然后栈溢出改返回地址就可以,angr就可以完美胜任这个自动化工具
大致来看看程序逻辑:
七八千行的伪代码,所以人工逆向是不要想了,不过好在它逻辑很简单,我们只需要hook四个函数,hook比较函数是因为它逐字节比较会导致路径爆炸,每比较一个字节都会分出两种状态,呈2的指数级增长,所以必须hook掉,然后就是两个输入函数,将两个输入函数输入的内容符号化然后存到state中,然后设置好约束,比如rip和rsp的值,因为要修改返回地址,所以rip最后一定指向ret指令,rsp则应该是后门函数。
设置好之后开始explore,跑到所需状态,把之前符号化的变量中把具体值提取出来,拼到一起,就是让程序执行到所需状态要输入的数据,拿到了以后直接发给远程,就可以打通了。
这里提一下,在hook地址的时候用的是Project.hook,但是如果要hook的函数被多次调用,则需要hook整个函数,要做的是定义一个这样的类:
class xxx(angr.SimProcedure):
def run(self,arg···):
xxxx
run的参数和要hook的C语言的函数参数相同
比如本题在hook读入字符串函数的时候:
class ReplacementCheckInput(angr.SimProcedure):
def run(self, buf, len):
len = self.state.solver.eval(len)
self.state.memory.store(buf, getBVV(self.state, len))
原函数是:
这里相当于getBVV符号化指定长度的符号然后存入对应地址(buf)中
在hook读入一个数字的时候则更为简单
class ReplacementInputVal(angr.SimProcedure):
def run(self):
self.state.regs.rax = getBVV(self.state, 4, 'int')
因为读入数字的函数从调用者的角度来看,不过是把输入的东西解析成数字然后存到rax里,所以这里直接就是让rax等于一个符号化后的长度为4的符号即可。
最后来看看完整的脚本:
from pwn import *
import binascii
import angr
import claripy
import logging
import base64
l = logging.getLogger(name="angr")
def pass_proof(target, part):
ll = string.printable
for i in ll: