shad0w原理分析 part 1
01 生成木馬的方式
由作者的介紹可以得知,shad0w生成木馬的命令為
python3 shad0w.py beacon -p x64/windows/secure -H 192.168.1.81 -f exe -o beacon.exe
payload 分為兩種:
- 1、x64/windows/secure 分步加載
- 2、x64/windows/secure/static 直接生成beacon
secure 分步加載

get_payload_variables函數是對傳入的payload參數進行提取,提取出4個變量
1. self.arch 位數(x64,x86)
2. self.platform 環境windows
3. self.secure 是否啟用保護
4. self.static 是否直接生成beacon


由上面提取的static變量判斷是否分步執行,如果static為null,則將stager目錄下的所有文件拷貝到build里面。

并更新配置信息,更新的主要為
_C2_CALLBACK_ADDRESS 回連ip
_C2_CALLBACK_PORT 回連端口

下一步為編譯build里面的內容,
1、如果使用了secure,則會在makelist中添加宏變量secure(此變量用處為beacon中啟用syscall)
2、_crypt_strings 會異或加密函數名并將加密后字符串和key寫入到strings.h
3、使用make編譯stager

最后,payload_format.create函數中將stager變成shellcode

根據輸出結果對輸出內容進行判斷,如果為raw直接生成shellcode,exe 是先生成shellcode,再將shellcode寫入一個加載器編譯成exe,dll也是一樣。

shellcode生成是使用donut這個項目



static 生成beacon

靜態是將src或者injectable目錄的代碼拷貝到build編譯生成,后面步驟和上面一樣。(經測試src生成的beacon功能存在bug,此處我直接使用injectable中代碼生成beacon)
02 stager和beacon
stager
stager代碼再beacon/stager下

stager功能代碼較為簡單
第一步、向服務器請求beacon
簡單說是使用winhttp庫發起https請求
向/stager發起post請求,參數是payload=x64/windows/secure/static(定義了secure)或payload=x64/windows/static(沒有secure)(x64,x86是根據系統位數判斷)

beacon會在shad0w啟動listen時生成


過程和第一步生成stager的時候一樣,不過生成的位置是在/tmp/beacon_id/build下面。生成的exe轉成shellcode進行base64編碼會存入shad0w.payloads"x64_secure_static"。


服務端是使用flask啟動的服務器,會在控制端請求/stager返回shad0w.payloads["x64_secure_static"]["bin"]的內容。
第二步、使用shellcode加載器加載beacon

beacon
第一步、收集用戶信息、 NetBIOS

首先獲取sid,后通過sid獲取username、domain

獲取機器的位數、系統版本、是否編譯時定義secure

第二步向服務端注冊信息


向/register 發送上面收集的信息(username、domain、netbios、位數、系統版本、是否開啟secure)

會將服務端返回的id記錄在IdBuffer中。

服務端會記錄用戶信息,機器信息并生成beacon_id,返回beacon_id 第三步 會根據sleep休眠時間定期向服務端發送心跳包。

心跳包發送的方式為



向/tasks發起請求,參數為之前獲取的beacon_id

解析返回結果。task用于后續用于判斷執行方式。
InjectExecuteCode:注入shellcode于一個運行的進程。
SpawnExecuteCode: 新啟一個進程注入shellcode
Stdlib:自帶庫。
InjectExecuteDll:反射型dll注入注入dll。
args為后續操作執行的內容。


服務端處理


在請求中獲取beacon_id。opcode, data是在后續執行命令時獲取,心跳包控制端只傳遞beacon_id。

opcode為0, data為空 即心跳包傳遞。

返回存活狀態和task(0x1000)。(在beacon中0x1000為跳出)

03 免殺方式syscall介紹
shad0w使用的syscall的方式并不是硬編碼,而是在程序執行開始時讀取ntdll并將其寫入到內存,從而實現避免被edr/av hook。

先看一個函數使用syscall執行的流程。
ParseNtdll(&NtdllInfo, &rSyscall);
MakeSyscall("NtOpenThread", NtdllInfo.pExprtDir, NtdllInfo.lpRawData, NtdllInfo.pTextSection, NtdllInfo.pRdataSection, SyscallStub);
rSyscall.NtOpenThread(&hThread, PROCESS_ALL_ACCESS, &ObjectAttributes, &uTid);
CleanSyscall(SyscallStub);


說一下syscall執行的流程
1. 在程序執行開始時將ntdll.dll寫入內存
2. 解析ntdll的.text,.rdata節:
.text里面存儲導出函數執行的代碼 .rdata里面存儲的是導出函數的名稱
3. 在內存中找到要調用的函數(比如:NtOpenThread),將其拷貝到內存其他位置
4. 聲明函數原型
5. 定義函數類型的變量指向內存,通過變量調用系統調用。
04 執行方式介紹
1.InjectExecuteCode
- 首先通過進程的pid打開進程。
- 將內存屬性更改為可讀可寫,將shellcode拷貝到內存。
- 改為內存屬性為可讀可寫可執行。
- 獲取當前線程的句柄,掛起線程。
- 獲取目標線程的上下文
- 設置rip(程序指令寄存器)的地址為shellcode的地址
- 恢復線程
- shellcode執行

2.SpawnExecuteCode
- 創建匿名管道,創建線程從匿名管道讀取結果,向服務端發送結果。


2.創建進程,在進程中分配內存,寫入shellcode,apc注入執行

3.Stdlib

這個是控制端自寫的一些功能,主要是從服務端接受命令并進入相應的函數去執行。這個在將命令模塊的時候再具體介紹。
4.InjectExecuteDll
1、這個函數主要是通過pe文件格式解析導出表,找到ReflectiveLoader函數的入口點地址。2、通過前面InjectExecuteCode方法的掛起線程,將程序寄存器(rip)地址改成ReflectiveLoader函數的地址。3、ReflectiveLoader會恢復dll在內存中的位置從而執行。

未完待續
對shad0w的命令模塊還沒有進行介紹。
1. 命令模塊存在一些bug
2. 大量的模塊都是通過將exe轉成shellcode,再通過SpawnExecuteCode的方式執行,需要將一些常用的模塊二次開發后再進行介紹。
3. 目前已經重寫upload,download模塊。
4. 后續添加shell執行命令的模塊,將shellcode加載部分更改加載方式,不使用在其他進程中加載。