Have you ever wondered what goes through the mind of a malware
author? How they build their tools? How they organize their
development projects? What kind of computers and software they use? We
took a stab and answering some of those questions by exploring malware
debug information.
We find that malware developers give descriptive names to their
folders and code projects, often describing the capabilities of the
malware in development. These descriptive names thus show up in a
PDB path when a malware project is compiled with symbol
debugging information. Everyone loves an origin story, and debugging
information gives us insight into the malware development environment,
a small, but important keyhole into where and how a piece of malware
was born. We can use our newfound insight to detect malicious activity
based in part on PDB paths and other debug details.
Welcome to part one of a multi-part, href="https://twitter.com/search?q=%23dailypdb">tweet-inspired
series about PDB paths, their relation to malware, and how they may be
useful in both defensive and offensive operations.
Digital storage systems have revolutionized our world but in order
to make use of our stored data and retrieve it in an efficient manner,
we must organize it sensibly. Users structure directories carefully
and give files and folders unique and descriptive names. Often users
name folders and files based on their content. Computers force users
to label and annotate their data based on the data type, role, and
purpose. This human-computer convention means that most digital
content has some descriptive surface area, or descriptive “features”
that are present in many files, including malware files.
FireEye approaches detection and hunting from many angles, but on
FireEye’s Advanced Practices team, we often like to flex on “weak
signals.” We like to search for features of malware that are not evil
in isolation but uncommon or unique enough to be useful. We create
conditional rules that when met are “weak signals” telling us that a
subset of data, such as a file object or a process, has some odd or
novel features. These features are often incidental outcomes of
adversary methods, or modus operandi, that each represent deliberate
choices made by malware developers or intrusion operators. Not all
these features were meant to be in there, and they were certainly not
intended for defenders to notice. This is especially true for PDB
paths, which can be described as an outcome of the compilation
process, a toolmark left in malware that describes the development environment.
A program database (PDB) file, often referred to as a “symbol file,”
is generated upon compilation to store debugging information about an
individual build of a program. A PDB may store symbols, addresses,
names of functions and resources and other information that may assist
with debugging the program to find the exact source of an exception or error.
Malware is software, and malware developers are software developers.
Like any software developers, malware authors often have to debug
their code and sometimes end up creating PDBs as part of their
development process. If they do not spend time debugging their
malware, they href="https://twitter.com/stvemillertime/status/1118334333402501120">risk
their malware not functioning correctly on victim hosts, or not
being able to successfully communicate with their malware remotely.
But how are PDBs created and connected to programs? Let’s examine
the formation of one PDB path through the eyes of a malware developer
and blogger, the soon-to-be-infamous “smiller.”
Smiller has a lot of programming projects and organizes them in an
aptly labeled folder structure on his computer. This project is for a
shellcode loader embedded in an HTML Application (HTA) file, and the
developer stores it quite logically in the folder:
D:\smiller\projects\super_evil_stuff\shellcode\
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture1.png" alt="" />
Figure 1: The simple “Test” project code
file “Program.cs” which embeds a piece of shellcode and a launcher
executable within an HTML Application (HTA) file
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture2.png" alt="" />
Figure 2: The malicious Visual Studio
solution HtaDotnet and corresponding “Test” project folder as seen
through Windows Explorer. The names of the folders and files are
suggestive of their functionalities
The malware author then compiles their “Test” project Visual Studio
in a default “Debug” configuration (Figure 3) and writes out Test.exe
and Test.pdb to a subfolder (Figure 4).
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture3.png" alt="" />
Figure 3: The Visual Studio output of a
default compiling configuration
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture4.png" alt="" />
Figure 4: Test.exe and Test.pdb are
written to a default subfolder of the code project folder
In the Test.pdb file (Figure 5) there are references to the original
path for the source code files along with other binary information for
use in debugging.
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture5.png" alt="" />
Figure 5: Test.pdb contains binary debug
information and references to the original source code files for use
in debugging
During the compilation, the href="https://docs.microsoft.com/en-us/cpp/build/reference/linker-options?view=vs-2019">linker
program associates the PDB file with the built executable by adding an
entry into the href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_debug_directory">IMAGE_DEBUG_DIRECTORY
specifying the type of the debug information. In this case, the debug
type is CodeView and so the PDB path is embedded under
IMAGE_DEBUG_TYPE_CODEVIEW portion of the file. This enables a debugger
to locate the correct PDB file Test.pdb while debugging Test.exe.
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture6.png" alt="" />
Figure 6: Test.exe as shown in the PEview
utility, which easily parses out the PDB path from the
IMAGE_DEBUG_TYPE_CODEVIEW section of the executable file
The exact format of the debug information may vary depending on
compiler and linker and the modernity of one’s software development
tools. CodeView debug information is stored under href="https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md">IMAGE_DEBUG_TYPE_CODEVIEW
in the following structure:
|
|
DWORD | valign="top"> |
GUID | valign="top"> |
DWORD | valign="top"> |
BYTE | valign="top"> |
Figure 7: Structure of CodeView debug directory information
There are generally two buckets of CodeView PDB paths, those that
are fully qualified directory paths and those that are
partially qualified, that specify the name of the PDB file
only. In both cases, the name of the PDB file with the .pdb extension
is included to ensure the debugger locates the correct PDB for the program.
A partially qualified PDB path would list only the PDB file
name, such as:
Test.pdb
A fully qualified PDB path usually begins with a volume drive
letter and a directory path to the PDB file name such as:
D:\smiller\projects\super_evil_stuff\shellcode\Test\obj\Debug\Test.pdb
Typically, native Windows executables use a partially
qualified PDB path because many of the debug PDB files are
publicly available on the href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/microsoft-public-symbols">Microsoft
public symbol server, so the fully qualified path is unnecessary
in the href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/symbol-path">symbol
path (the PDB path). For the purposes of this research, we will be
mostly looking at fully qualified PDB paths.
In href="https://securelist.com/operation-shadowhammer-a-high-profile-supply-chain-attack/90380/">Operation
Shadowhammer, which has a href="https://content.fireeye.com/apt-41/rpt-apt41">myriad of
connections to APT41, one sample had a simple, yet descriptive
PDB path: “D:\C++\AsusShellCode\Release\AsusShellCode.pdb”
The naming makes perfect sense. The malware was intended to
masquerade as Asus Corporation software, and the role of the malware
was shellcode. The malware developer named the project after the
function and role of the malware itself.
If we accept that the nature of digital data forces developers into
these naming conventions, we figured that these conventions
would hold true across other threat actors, malware families, and
intrusion operations. FireEye’s Advanced Practices team loves to take
seemingly innocuous features of an intrusion set and determine what
about these things is good, bad and ugly. What is normal, and what is
abnormal? What is globally prevalent and what is rare? What are
malware authors doing that is different from what non-malware
developers are doing? What assumptions can we make and measure?
Letting our curiosity take the wheel, we adapted the CodeView debug
information structure into a regular expression (Figure 8) and
developed Yara rules (Figure 9) to survey our data sets. This helped
us identify commonalities and enabled us to see which threat actors
and malware families may be “detectable” based only on features within
PDB path strings.
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture8.png" alt="" />
Figure 8: A Perl-compatible regular
expression (PCRE) adaptation of the PDB7 debug information in an
executable to include a specific keyword
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture9.png" alt="" />
Figure 9: Template Yara rule to search
for executables with PDB files matching a keyword
We surveyed 10+ million samples in our incident response and malware
corpus, and we found plenty of common PDB path keywords that seemed to
transcend different sources, victims, affected regions, impacted
industries, and actor motivations. To help articulate the broad reach
of malware developer commonalities, we detail a handful of the
stronger keywords along with example PDB paths, with represented
malware families and threat groups where at least one sample has the
applicable keyword.
Please note that the example paths and represented malware families
and groups are a selection from the total data set, and not
necessarily correlated, clustered or otherwise related to each other.
This is intended to illustrate the wide presence of PDB paths with
keywords and how malware developers, irrespective of origin, targets
and motivations often end up using some of the same words in their
naming. We believe that this commonality increases the surface area of
malware and introduces new opportunities for detection and hunting.
|
| width="282" valign="top"> |
anti | valign="top"> |
|
attack
| width="252" valign="top"> |
|
backdoor | valign="top"> |
|
bind
| width="252" valign="top"> |
|
bypass
| width="252" valign="top"> |
|
downloader
| width="252" valign="top"> |
|
dropper
| width="252" valign="top"> |
|
exploit
| width="252" valign="top"> | width="282" valign="top"> |
fake
| width="252" valign="top"> |
|
fuck
| width="252" valign="top"> |
|
hack
| width="252" valign="top"> | width="282" valign="top"> |
hide
| width="252" valign="top"> |
|
hook
| width="252" valign="top"> |
|
inject
| width="252" valign="top"> | width="282" valign="top"> |
install
| width="252" valign="top"> |
|
keylog
| width="252" valign="top"> | width="282" valign="top"> |
payload
| width="252" valign="top"> |
|
shell
| width="252" valign="top"> |
|
sleep
| width="252" valign="top"> |
|
spy
| width="252" valign="top"> |
|
trojan
| width="252" valign="top"> | width="282" valign="top"> |
Figure 10: A selection of common keywords in PDB
paths with groups and malware families observed and examples
The keywords that are typically used to describe malware are strong
enough to raise red flags, but there are other common terms or
features in PDB paths that may signal that an executable is compiled
in a non-enterprise setting. For example, any PDB path containing
“Users” directory can tell you that the executable was likely compiled
on Windows Vista/7/10 and likely does not represent an “official” or
“commercial” development environment. The term “Users” is much weaker
or lower in fidelity than “shellcode” but as we demonstrate below,
these terms are indeed present in lots of malware and can be used for
weak detection signals.
|
| width="270" valign="top"> |
Users | valign="top"> | class="code">C:\Users\Yousef\Desktop\MergeFiles\Loader v0\Loader\obj\Release\Loader.pdb |
ConsoleApplication WindowsApplication WindowsFormsApplication
(Visual Studio default project names) | width="208" valign="top"> |
|
New Folder | width="208" valign="top"> |
|
Copy | valign="top"> | class="code">D:\dll_Mc2.1mc\2.4\2.4.2 xor\zhu\dll_Mc - Copy\Release\shellcode.pdb |
Desktop | valign="top"> | class="code">C:\Users\Develop_MM\Desktop\sc_loader\ Release\sc_loader.pdb |
Figure 11: A selection of common terms in PDB
paths with groups and malware families observed and examples
Outside of keywords and terms, we discovered on a few uncommon (to
us) features that may be interesting for future research and detection opportunities.
PDB paths with any non-ASCII characters have a high ratio of malware
to non-malware in our datasets. The strength of this signal is only
because of a data bias in our malware corpus and in our client base.
However, if this data bias is consistent, we can use the presence of
non-ASCII characters in a PDB path as a signal that an executable
merits further scrutiny. In organizations that operate primarily in
the world of ASCII, we imagine this will be a strong signal. Below we
express logic for this technique in Yara:
rule ConventionEngine_Anomaly_NonAscii
{
meta:
author =
"@stvemillertime"
strings:
$pcre
=
/RSDS[\x00-\xFF]{20}[a-zA-Z]:\\[\x00-\xFF]{0,500}[^\x00-\x7F]{1,}[\x00-\xFF]{0,500}\.pdb\x00/
condition:
(uint16(0) == 0x5A4D) and
uint32(uint32(0x3C)) == 0x00004550 and $pcre
}
Each compiled program should only have one PDB path. The presence of
multiple PDB paths in a single object indicates that the object has
subfile executables, from which you may infer that the parent object
has the capability to “drop” or “install” other files. While being a
dropper or installer is not malicious on its own, having an
alternative method of applying those classifications to file objects
may be of assistance in surfacing malicious activity. In this example,
we can also search for this capability using Yara:
rule ConventionEngine_Anomaly_MultiPDB_Triple
{
meta:
author =
"@stvemillertime"
strings:
$anchor = "RSDS"
$pcre =
/RSDS[\x00-\xFF]{20}[a-zA-Z]:\\[\x00-\xFF]{0,200}\.pdb\x00/
condition:
(uint16(0) == 0x5A4D) and
uint32(uint32(0x3C)) == 0x00004550 and #anchor == 3 and #pcre ==
3
}
When a file is compiled the entry for the debug information is in
the IMAGE_DEBUG_DIRECTORY. Similar to seeing multiple PDB paths in a
single file, when we see debug information inside an executable that
does not have a debug directory, we can infer that the file has
subfile executables, and is likely has dropper or installer
functionality. In this rule, we use href="https://buildmedia.readthedocs.org/media/pdf/yara/latest/yara.pdf">Yara’s
convenient PE module to check the relative virtual address (RVA)
of the IMAGE_DIRECTORY_ENTRY_DEBUG entry, and if it is zero we can
presume that there is no debug entry and thus the presence of a
CodeView PDB path indicates that there is a subfile.
rule ConventionEngine_Anomaly_OutsideOfDebug
{
meta:
author =
"@stvemillertime"
description =
"Searching for PE files with PDB path keywords, terms or
anomalies."
strings:
$anchor =
"RSDS"
$pcre =
/RSDS[\x00-\xFF]{20}[a-zA-Z]:\\[\x00-\xFF]{0,200}\.pdb\x00/
condition:
(uint16(0) == 0x5A4D) and
uint32(uint32(0x3C)) == 0x00004550 and $anchor and $pcre and
pe.data_directories[pe.IMAGE_DIRECTORY_ENTRY_DEBUG].virtual_address
== 0
}
In the typical CodeView section, we would see the “RSDS” header, the
16-byte GUID, a 4-byte “age” and then a PDB path string. However,
we’ve identified a significant number of malware samples where the
embedded PDB path area is nulled out. In this example, we can easily
see the CodeView debug structure, complete with header, GUID and age,
followed by nulls to the end of the segment.
00147880: 52 53 44 53 18 c8 03 4e 8c 0c
4f 46 be b2 ed 9e : RSDS...N..OF....
00147890:
c1 9f a3 f4 01 00 00 00 00 00 00 00 00 00 00 00 :
................
001478a0: 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 : ................
001478b0: 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 : ................
001478c0: 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................
There are a few possibilities of how and why a CodeView PDB path may
be nulled out, but in the case of intentional tampering, for the
purposes of removing toolmarks, the easiest way would be to manually
overwrite the PDB path with \x00s. The risk of manual editing and
overwriting via hex editor is that doing so is laborious and may
introduce other static anomalies such as checksum errors.
The next easiest way is to use a utility designed to wipe out debug
artifacts from executables. One stellar example of this is “ href="http://bytepointer.com/tools/index.htm#peupdate">peupdate”
which is designed not only to strip or fabricate the PDB path
information, but can also recalculate the checksum, and eliminate Rich
headers. Below we demonstrate use of peupdate to clear the PDB path.
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture12.png" alt="" />
Figure 12: Using peupdate to clear the
PDB path information from a sample of malware
src="https://www.fireeye.com/content/dam/fireeye-www/blog/images/definitivedossier/Picture13.png" alt="" />
Figure 13: The peupdate tampered malware
as shown in the PEview utility. We see the CodeView section is still
present but the PDB path value has been cleared out
|
| width="306" valign="top"> |
Non-Ascii Characters | width="273" valign="top"> | valign="top"> class="code">I:\RControl\小工具\123\判断加载着\Release\判断加载着.pdb |
Multi Path in Single File | AGENTTESLA, BANKSHOT, BEACON, APT1, APT2, APT17, APT5, APT20, | Single Sample
|
Outside of Debug Section | ABBEYROAD, AGENTTESLA, BEACON, APT5, APT6, APT9, APT10, APT17, APT22, APT24, | valign="top"> |
Nulled Out PDB Paths | width="273" valign="top"> |
|
Figure 14: A selection of anomalies in PDB paths
with groups and malware families observed and examples
The internet is a weird place, and at a big enough scale, you end up
seeing things that you never thought you would. Things that deviate
from the norms, things that shirk the standards, things that utterly
defy explanation. We expect PDB paths to look a certain way, but we’ve
run across several samples that did not, and we’re not always sure
why. Many of these samples below may be results of errors, corruption,
obfuscation, or various forms of intentional manipulation. We’re
demonstrating them here to show that if you are attempting PDB path
parsing or detection, you need to understand the variety of paths in
the wild and prepare for shenanigans galore. Each of these examples
are from confirmed malware samples.
Figure 15: A selection of PDB paths shenanigans
with examples
There are many differences between apex