2644 字
13 分鐘
TeamT5 Security Camp 2026 惡意程式分析

TeamT5 Security Camp 2026 惡意程式分析#

作者:陳揚叡

答題整理 (TL;DR)#

分析相關樣本#

  • 受害者觸發惡意程式的方式 (載入流程/誘發方式/可能有的加解密/等等)
    • 受害者透過雙擊執行 Safety Manager JD.jse,會在 C:\ProgramData 建立 3hsOE4s.JaBu
    • 3hsOE4s.JaBu 利用 LOLBAS-certutil 的方式執行二次解碼存為同目錄的下的 a2rqpas.u53Z,並使用 regsvr32.exe 執行該檔案
    • a2rqpas.u53Z 會利用滾輪式 xor 及凱薩加密載入各種 DLL
    • a2rqpas.u53Z 會修改 Registry 跟創建服務每次開機將自動執行 regsvr32.exe "C:\ProgramData\a2rqpas.u53Z 來維持常駐性
    • a2rqpas.u53Z 會利用自訂 RC4 演算法解密 Payload,並將其隱藏於 ADS (替代資料流) a2rqpas.u53Z:info 中以規避偵測
    • a2rqpas.u53Z 會連線至 C2 (uberlingen.com),通訊內容經過 Base64+RC4 加密,並判斷回傳自首是否 ‘y’ 來確認連線成功,並可接收各種指令
  • 惡意程式族群
    • AppleSeed + BabyShark + FlowerPower
  • 樣本編譯時間 + 推測攻擊時間
    • a2rqpas.u53Z:2024/05/13
    • 該檔案是透過 HR 招募來偽裝,Google 搜尋可知該公司並沒有特定時間點招募,但可以根據 CTI 判斷該程式有很多種變種,所以該程式攻擊時間應該不會超過 3 個月,推測介於 2024/05/31 ~ 2024/08/31
  • Decoy 檔案
    • SystemSafetyManagerID.pdf
  • C2
  • 常駐方式
    • 如果是低權限使用者會透過修改 Registry HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run 讓每次開機使用者登入後自動執行 regsvr32.exe "C:\ProgramData\a2rqpas.u53Z"
    • 如果是高權限使用者會每次開機時創建 CreateProcessW 執行以下命令 sc create CacheDB binPath= "cmd /c regsvr32.exe /s %s" start= auto
  • 相關 traffic image
  • 其他有用的資訊
    • PDF 數據存在韓文

分析受害/攻擊者身分#

  • 受害國家
    • 美國
  • 攻擊族群
    • Kimsuky

加分題#

  • Payload decoding/decryption algorithm
    • 大部分API都採用 API Hash( 透過 FNV-1a Hashing 動態尋找 API 位址 )
    • Payload 部分則是有採用 RC4、凱薩加密、滾動式 xor (解密腳本可至樣本分析部分)
  • Malware obfuscation techniques
    • 如果是混淆逆向難度則有:
      • 大量 SSE/AVX 指令集增加逆向難度
      • 大量採用 API Hash ( 透過 FNV-1a Hashing 動態尋找 API 位址 )
      • 滾動式 XOR、凱薩密碼、RC4
    • 如果是指混淆偵測則有:
      • ADS (Alternate Data Stream) 隱藏惡意 Payload
      • LoLBAS 繞過傳統 AV、EDR
  • Config offset + decoding method
    • 詳見樣本分析部分
  • Capability
    • 詳見樣本分析部分
  • 其他視情況/細節給分
    • 詳見樣本分析部分

樣本分析#

題目給了一個 Safety Manager JD.jse 在預設情況下會由 Windows Script Host (WScript.exe)去執行,所以猜測是誘騙受害者雙擊執行來觸發惡意程式。所以我們可以修改開啟方式為記事本檢查內容。

image

經分析,該樣本內部嵌有三段 Base64 編碼資料。樣本執行後,主要透過 XMLDOM 與 ADODB.Stream 物件進行解碼與還原

image

最終釋放並執行三個關鍵檔案可以從 C:\ProgramData 實際發現:3hsOE4s.JaBu( 用於第二解碼 )、SystemSafetyManagerID.pdf Decoy)、 a2rqpas.u53Z(惡意核心 Payload)。

image

而以下將針對 SystemSafetyManagerID.pdf,a2rqpas.u53Z 做更詳細的分析:

1、SystemSafetyManagerID.pdf#

Filetype: pdf Hash (MD5): 6E5D5A8D06452852F1CCBC9B6DBAB3EB

樣本首先利用 XMLDOM 物件解碼內嵌的第二段 Base64 字串,隨後透過 ADODB.Stream 物件將還原後的二進位資料寫入磁碟,路徑為 C:\programData\SystemSafetyManagerID.pdf。在動態執行期間,我們可以觀察到該 PDF 檔案被成功建立並開啟,證實其作為掩飾惡意行為的誘餌用途。

image

透過 Google 搜尋 General Dynamics Land Systems 可以確認是美國一家軍用機械裝備的工業企業,所以猜測受害者可能是 美國

且該數據存在韓文,推測來自韓國 APT 組織 image

2、a2rqpas.u53Z#

Filetype: PE 64 Hash (MD5): 537806C02659A12C5B21EFA51B2322C1 Date Modified: 2024/05/13

該樣本採取了兩階段解碼與執行策略:

  • 同樣利用 XMLDOM 配合 ADODB.Stream 處理第三段 Base64 資料,將其初步儲存為中間檔案 C:\programData\5hsnks.jaba
  • 程式檢查 5hsnks.jaba 是否存在並利用(Living off the Land, LoLBAS) 技術,呼叫系統內建工具 certutil.exe 對該檔案進行二次解碼,最終產出 DLL 格式的 a2rqpas.u53Z
  • 利用 regsvr32.exe 載入並執行 a2rqpas.u53Z

以下是使用 Detect It Easy (DIE) 工具偵測出的開發工具環境,推測攻擊者使用 Visual Studio 進行惡意程式的開發及編譯

image

在 PE 的 IMAGE_FILE_HEADER 中可以看到 TimeDateStamp 記載著執行檔的編譯時間為 2024/05/13 10:01:12

image

此外根據收尋該 Hash 可以發現這篇文章 Analysis of the Triple Combo Threat of the Kimsuky Group,我們可以得知該檔案的攻擊手法及攻擊者身分

接下來我們將使用 IDA 針對 a2rqpas.u53Z 進行更加深入分析,並以 DLL 的入口函式 DllMain 作為切入點,逐層解析其核心惡意行為。

DllMain 的 function call chain: regsvr32.exe 載入 DLL \rightarrow DllEntryPoint() \rightarrow dllmain_dispatch() \rightarrow DllMain()

DllMain()#

在這部分惡意軟體作者使用了 API Hash 的方式來分別呼叫了三種 API,我們將著重分析於第三個API

image

經觀察,程式在取得 v6 的記憶體位址後,對 v31 進行呼叫並傳遞了 6 個參數。基於此呼叫慣例與參數數量,推測該 API 為 CreateThread

sub_18000F720()#

基於上述分析,sub_18000F720 作為執行緒的 Entry Point,可確認為此惡意程式的核心邏輯。鑑於 sub_18000F780 與 sub_1800034E0 負責初始化與記憶體配置,因此,接下來的分析將聚焦於其他呼叫的函式。

image

sub_180002EB0()#

初步分析: 可以看到該函式存在許多明文的.dll檔以及惡意加密過的字串,推測是載入這些DLL用於後續惡意行為

image image image

詳細分析:

函式一開始會使用跟凱薩加密載入 kernel32.dll

image image

接下來會使用滾動式xor加密檔名跟凱撒方式加密 LoadlibaryA

image image

寫個簡單腳本發現他是載入 user32.dll

import struct
v74 = [0x73666114, 0x342B2B65, 0x71707F]
data = b''.join(struct.pack('<I', x) for x in v74)
result = ""
v13 = 20
for v14 in range(10):
char_code = data[v14 + 1] ^ (v13 + v14)
result += chr(char_code)
print(result)#user32.dll

沒意外就是使用各種加密,最終我們可以解密出除了原本的 kernel32.dll,user32.dll 還有像是 wininet.dll,winhttp.dll,shlwapi.dll,advapi32.dll,urlmon.dll 的DLL。

sub_180003530()#

該函式一開始會判斷 sub_180002440 imagesub_180002440 存在 OpenProcessToken 函數,在查閱官方文件可以知道是跟權限有關的函式,所以可以猜測 sub_180003530() 一開始是判斷權限再分別執行不同操作。 image

透過分析 If 區塊發現使用了 SIMD (SSE) 指令集與簡單的減法加密來混淆字串,並透過 CreateProcessW 來呼叫 image

image

寫個簡單腳本解密:

import struct
def decode_cmdline():
v80 = [7274623, 7274540, 7405694, 8388717]
v81 = [0x6D004F002C0071, 0x5000710074006F]
v82 = [0x75006E002C004E, 0x80006D005C007A]
v83 = [0x2E002C00490074, 0x2C00700079006F]
# v84-v100 (int)
rest = [
7274555, 8257580, 7536753, 8519807, 4128894, 3801150,
8650865, 2883697, 8323131, 3211308, 3014783, 8323116,
7143552, 8388734, 2883657, 8454253, 8061056, 0
]
# 打包 bytes
data = b""
for x in v80: data += struct.pack('<I', x)
for x in v81: data += struct.pack('<Q', x)
for x in v82: data += struct.pack('<Q', x)
for x in v83: data += struct.pack('<Q', x)
for x in rest: data += struct.pack('<I', x)
# 每個 short-12
decoded_str = ""
# 將 bytes 轉為 16-bit unsigned shorts
shorts = struct.unpack(f'<{len(data)//2}H', data)
for val in shorts:
if val == 0: break
# 解密算法: val - 12
decrypted_char = chr(val - 12)
decoded_str += decrypted_char
print(f"Decrypted Command:\n{decoded_str}")
if __name__ == "__main__":
decode_cmdline()

image

可以得知當使用者為高權用戶會創建 CacheDB 的服務,每次重開機後會執行 regsvr32.exe "C:\ProgramData\a2rqpas.u53Z"

接下來分析 Else 區塊我們發現了被xor加密的Registry字串 software\microsoft\windows\currentversion\run 該 Registry 用於開機自動啟動,所以可以知道該函式主要用來修改Registry來持久化

image

def decrypt_registry():
parts = [
0xD7E0D793,
0xD7F5D7FC,
0xD7E1D7F2D7E4D7E7,
0xD7FAD7FED7CFD7F6,
0xD7E0D7FCD7E1D7F0,
0xD7CFD7E7D7F5D7FC,
0xD7F7D7FDD7FAD7E4,
0xD7CFD7E0D7E4D7FC,
0xD7E1D7E1D7E6D7F0,
0xD7E5D7E7D7FDD7F6,
0xD7FAD7E0D7E1D7F6,
0xD7E1D7CFD7FDD7FC,
0xD7FDD7E6
]
data_bytes = b"".join(
p.to_bytes((p.bit_length() + 7) // 8, "little")
for p in parts
)
key = 0xD793
result = []
# 按 2 bytes 解析
for i in range(1, len(data_bytes) // 2):
encrypted = int.from_bytes(data_bytes[i*2:i*2+2], 'little')
ch = (encrypted ^ key) & 0xFF
if ch == 0:
break
result.append(chr(ch))
print("".join(result))#software\microsoft\windows\currentversion\run
if __name__ == "__main__":
decrypt_registry()

還可以看到會嘗試啟動 Registry ,0x80000001 代表 HKCU image 隨後用 regsvr32.exe 啟動 image 所以該函式當使用者屬於低權限登入時自動執行 regsvr32.exe 來載入這個惡意 DLL。

sub_180003F30()#

初步分析:#

在該函式的一開始就可以看到存在 CreateFileW 以及 Readfile 所以大概可以知道這個函式的主要是檔案操作。

image image

詳細分析:#

首先如同前面一樣該函式也是使用 API HASH 方式來呼叫 v12

image

接下來會把呼叫 v12 的結果立刻當參數呼叫 sub_180002DA0

image

其實肉眼看就很明顯了是 swprintf,他會加上一個格式化路徑 %s:info

image

所以合理推測前面 v12 就是 GetModuleFileNameW,並且會抓取當前檔案路徑加上 :info ,並往這個 ADS 寫入資料。

為了驗證前面假設,繼續往下分析可以發現他會用 CreateFile API 嘗試開啟 a2rqpns.u53Z:info 如果開啟失敗將會執行記憶體解密 RC4 ,反之會用 ReadFile API 讀取檔案寫入惡意 4652 bytes Payload image image

我們也可以用 powershell 確認

image

接下來我們可以寫腳本還原當開啟失敗時執行的 RC4 解密

import struct
HEX_V52 = "411F1C5D5B381C1E1C1D0B1B1C3F2A78"
HEX_K1 = "124D4D0D04664142474752437B594F1D"
HEX_K2 = "57425F5C440206746D1612141C5C485F"
def to_le(h): return bytearray(bytes.fromhex(h)[::-1])
def decrypt_config(v52, k1, k2, payload):
buf = v52 + bytearray(16)
k = buf[0]
for i in range(31): buf[i+1] ^= k
vlen = 0
while vlen + 1 < len(buf) and buf[vlen + 1] != 0: vlen += 1
key = k1 + k2
k0 = key[0]
for i in range(31): key[i+1] ^= (k0 + i) & 0xFF
S = list(range(256))
j = 0
idx = 0
for i in range(256):
j = (S[i] + j + key[idx + 1]) & 0xFF
S[i], S[j] = S[j], S[i]
idx = (idx + 1) % vlen
out = bytearray()
i = 0
j = 0
for b in payload:
i = (i + 1) & 0xFF
si = S[i]
j = (j + si) & 0xFF
sj = S[j]
S[i], S[j] = sj, si
out.append(b ^ S[(si + sj) & 0xFF])
return out
if __name__ == "__main__":
with open("Data.bin", "rb") as f: #4625 bytes data
data = f.read()
dec = decrypt_config(to_le(HEX_V52), to_le(HEX_K1), to_le(HEX_K2), data)
with open("Payload_Decrypted.bin", "wb") as f:
f.write(dec)
print("[+] 完成")

把還原出來的 Payload_Decrypted.bin 丟到線上 HxD 就可以發現 CC

image

透過 google 搜尋可知 uberlingen 是來自德國的城市並且被 DNS Sinkholing

螢幕擷取畫面 2025-11-19 073840

sub_1800049C0()#

我們首先會注意到該函式存在各種的 case ,所以判斷這個函式會持續與 C2 連接並接收對方的各種命令。

image

詳細分析 GetData() 結構會發現他會做 POST 請求格式為 id=user&pwd=page1&ctx=<id>

image

並且判斷接收到的第一個 byte 是否等於 ‘y’,如果不等於就代表通訊失敗,如果首字節是 ‘y’,接著會分別執行 Base64 解碼跟 RC4 解密。

image

我們可以使用 Wireshark 去抓取相關流量

image

TeamT5 Security Camp 2026 惡意程式分析
https://bearrr777.github.io/posts/teamt5/惡意程式分析/
作者
RUI
發佈於
2025-12-27
許可協議
CC BY-NC-SA 4.0