BlindRop | 二进制漏洞中的盲注 使用二分算法测试栈溢出长度

君叹   ·   发表于 2023-12-28 00:18:56   ·   安全工具

Blind ROP

基本介绍

BROP(Blind ROP) 于 2014 年由 Standford 的 Andrea Bittau 提出,其相关研究成果发表在 Oakland 2014,其论文题目是 Hacking Blind。
BROP 是没有对应应用程序的源代码或者二进制文件下,对程序进行攻击,劫持程序的执行流。

攻击条件

  1. 源程序必须存在栈溢出漏洞,以便于攻击者可以控制程序流程。
  2. 服务器端的进程在崩溃之后会重新启动,并且重新启动的进程的地址与先前的地址一样(这也就是说即使程序有 ASLR 保护,但是其只是在程序最初启动的时候有效果)。目前 nginx, MySQL, Apache, OpenSSH 等服务器应用都是符合这种特性的。

以上来源于ctf-wiki

通常,我们在测试栈溢出漏洞的时候,我们需要知道缓冲区长度,也就是缓冲区到栈上返回地址的距离。

测试

用ctfshow上的一道例题进行演示
如下图,我们输入 abcd ,四个字节
程序返回 No passwd,See you!
通过回显可以判断程序正常运行了

这时候再输入一个很长的数据,例如100个a
我们可以看到,程序输出了 timeout

由此判断这里程序发生了错误
以此猜测,程序发生了栈溢出漏洞

栈溢出

这里也浅浅的介绍一下栈溢出
有C语言代码如下

  1. #include <stdio.h>
  2. int main() {
  3. char buf[30];
  4. read(0, buf, 0x30);
  5. return 0;
  6. }

程序的返回地址(即这个函数执行完了之后,要去执行哪个函数)是布在栈上的
buf也是布置在栈上的
上面的c语言代码中使用 read() 函数从标准输入中读取 0x30(48)个字节存储到buf变量,但是分配给buf的空间只有 30 个字节,还有 18 个字节(如果我们输入了的话)
会被存储到buf后面的空间里,倘若 返回地址 的位置,刚好在 buf 后面,我们就能控制返回地址,从而控制程序的执行流程
举个例子, main 函数的返回地址是 exit,也就是结束进程的函数,倘若我们把exit修改为 system(‘/bin/sh’), 就获得了目标机器执行这个程序用户的shell。
题外话就说到这里,接下来开始文章的主题

一般测试

一般情况下,在猜测目标程序存在栈溢出漏洞后,我们会写一个这样的脚本
去测试栈长度

学过算法的朋友应该能看的出来,下面这个程序的算法复杂度是O(n)
即程序有多少数据,就要运行多少次循环

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2023/12/27 23:49
  3. # @Author : 君叹
  4. # @File : cs2.py
  5. from pwn import *
  6. buf_lenth = 1
  7. while True:
  8. try:
  9. io = remote("pwn.challenge.ctf.show", None)
  10. log.info(f"test: {buf_lenth}")
  11. payload = b'a' * buf_lenth
  12. io.sendafter("Welcome to CTFshow-PWN ! Do you know who is daniu?\n", payload)
  13. if io.recv().startswith(b"No passwd,See you!"):
  14. buf_lenth += 1
  15. else:
  16. log.success(f"buf length: {buf_lenth}")
  17. io.close()
  18. break
  19. io.close()
  20. except:
  21. 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 就说明程序正常运行了,小于等于目标值,右移左指针
没有返回,说明没有正常运行,大于目标值,左移右指针

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2023/12/13 21:49
  3. # @Author : 君叹
  4. # @File : getLength.py
  5. from pwn import *
  6. # 获取栈溢出长度
  7. def dichotomy(fun):
  8. num = 1
  9. jici = 0
  10. while fun(num):
  11. jici += 1
  12. num *= 2 # 确定范围
  13. min = num / 2
  14. max = num
  15. c = (max + min) // 2
  16. # print(max,min)
  17. # print("c -> ",c)
  18. while min <= max:
  19. jici += 1
  20. mid = (min + max) // 2
  21. if max - min == 1:
  22. log.success(f"共进行了 {jici} 次链接\n栈长度为: {mid}")
  23. return min
  24. if fun(mid): # 返回true,成立,那就是没到位
  25. min = mid
  26. else:
  27. max = mid
  28. log.success(f"共进行了 {jici} 次链接\n栈长度为: {mid}")
  29. return mid
  30. def getStackLength(addr, port):
  31. # 使用二分法快速寻找到 ebp-buf 的值
  32. def is_True(num):
  33. try:
  34. io = remote(addr, port)
  35. io.sendafter("Welcome to CTFshow-PWN ! Do you know who is daniu?\n", 'a' * int(num))
  36. data = io.recv()
  37. io.close()
  38. if not data.startswith(b"No passwd"):
  39. return False
  40. return True
  41. except EOFError:
  42. io.close()
  43. return False
  44. return dichotomy(is_True)
  45. if __name__ == '__main__':
  46. addr = None
  47. port = None
  48. len = getStackLength(addr, port)
  49. print(len)

运行结果

针对本题
共计14次链接,只用了原本不到20%的时间
当缓冲区空间越大,这个增幅也会越明显

用户名金币积分时间理由
Track-魔方 700.00 0 2023-12-28 20:08:51 深度 300 普适 200 可读200

打赏我,让我更有动力~

2 条回复   |  直到 10个月前 | 538 次浏览

17828147368
发表于 10个月前

1

评论列表

  • 加载数据中...

编写评论内容

guanzhangsec
发表于 10个月前

1

评论列表

  • 加载数据中...

编写评论内容
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.