Windows PE Malware Analysis Part I
Introduction
In this article I will be examining a Windows executable malware specimen. Based on a couple reports from automated malware sandboxes, the specimen drops multiple files to disk and calls out to multiple domains and IP addresses.
The purpose of this article is to give readers a look into malware reverse engineering using static analysis, behavioral analysis, and code analysis. This is Part 1 of several more articles to come.
The specimen in question is a Windows PE (Portable Executable) named setup.exe
. You can find it’s hashes below.
MD5: d1b2c8ddca2f8dd02e2c132153055084
SHA-1: 21c011ac7406eef048c175f5887e4eb885c050d6
SHA256: 506c2f513d64242fcb20ccff8c26c0ed1755fe9120b984c29ba224b311d635c3
I pulled this malware from Any.Run which contains nearly 75,000 malware samples at the time of this article. This particular specimen was uploaded on October 29th, 2021; however, the earliest upload was on October 21st, 2021.
VirusTotal shows 47 out of 68 vendors have flagged this as malware and the last analysis ran was October 28th, 2021. You can see some of the features that VirusTotal identified such as detect-debug-environment
, direct-cpu-clock-access
and long-sleeps
.
VirusTotal also displays that the creation time of this malware was October 18th, 2021.
Over the course of this series, rather than simply relying on automated sandbox analysis, let’s take a deeper look at what this malware does and try to identify some characteristics such as:
-
Obfuscation
-
Anti-reverse engineering
-
Command and Control
-
Encryption
…and more.
Environment
I will be using two virtual machines to complete my analysis. The first is a REMnux linux virtual machine and the other is the Windows based FlareVM by FireEye.
REMnux is an Ubuntu linux machine that is pre-loaded with tons of malware analysis tools. It can also be installed through a script on any compatible version of Ubuntu.
FlareVM comes as a PowerShell script that utilizes the Chocolatey package manager for Windows to download all the necessary tools for malware analysis, incident response and penetration testing. This will require your own Windows virtual machine for installation.
Static Analysis
Usually, I like to begin with static analysis because it gives me a preliminary look at what to expect from the specimen. It is the easiest step of the reverse engineering process in my opinion since it requires very little setup.
Static analysis includes using tools like file
, strings
, pedump
and disassemblers like IDA Pro
by Hex-Rays or Ghidra by the NSA. For this step, I will be using the REMnux VM.
File command
First, I will run the file
command to determine the architecture and confirm that the file is a Windows PE.
The results show that it is indeed a Windows PE, and it is of the x86 Intel architecture or a 32 bit program.
NOTE: Most malware is 32 bit to ensure it can run on both 32 and 64 bit systems.
PEdump
Next, I will run pedump
to get a breakdown of the specimen’s headers, imports, sections and more. Below is the output with some truncated lines.
=== MZ Header ===
signature: "MZ"
bytes_in_last_block: 144 0x90
blocks_in_file: 3 3
num_relocs: 0 0
header_paragraphs: 4 4
min_extra_paragraphs: 0 0
max_extra_paragraphs: 65535 0xffff
ss: 0 0
sp: 184 0xb8
checksum: 0 0
ip: 0 0
cs: 0 0
reloc_table_offset: 64 0x40
overlay_number: 0 0
reserved0: 0 0
oem_id: 0 0
oem_info: 0 0
reserved2: 0 0
reserved3: 0 0
reserved4: 0 0
reserved5: 0 0
reserved6: 0 0
lfanew: 272 0x110
=== DOS STUB ===
00000000: 0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68 |........!..L.!Th|
00000010: 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f |is program canno|
00000020: 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 |t be run in DOS |
00000030: 6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00 |mode....$.......|
=== RICH Header ===
ID VER COUNT DESCRIPTION
103 6b14 10
105 6b14 146
104 6b14 18
104 6f0b 17
103 6f0b 22
105 6f0b 44
101 6b14 3
1 0 93 [---] Unmarked objects
109 6fc6 3
ff 6fc6 1 [RES] VS2019 v16.5.5 build 28614
97 0 1
102 6fc6 1 [LNK] VS2019 v16.5.5 build 28614
=== PE Header ===
signature: "PE\x00\x00"
# IMAGE_FILE_HEADER:
Machine: 332 0x14c x86
NumberOfSections: 5 5
TimeDateStamp: "2021-10-18 09:26:14"
PointerToSymbolTable: 0 0
NumberOfSymbols: 0 0
SizeOfOptionalHeader: 224 0xe0
Characteristics: 258 0x102 EXECUTABLE_IMAGE, 32BIT_MACHINE
# IMAGE_OPTIONAL_HEADER32:
Magic: 267 0x10b 32-bit executable
LinkerVersion: 14.25
SizeOfCode: 89600 0x15e00
SizeOfInitializedData: 326144 0x4fa00
SizeOfUninitializedData: 0 0
AddressOfEntryPoint: 39488 0x9a40
BaseOfCode: 4096 0x1000
BaseOfData: 94208 0x17000
ImageBase: 4194304 0x400000
SectionAlignment: 4096 0x1000
FileAlignment: 512 0x200
OperatingSystemVersion: 6.0
ImageVersion: 0.0
SubsystemVersion: 6.0
Reserved1: 0 0
SizeOfImage: 425984 0x68000
SizeOfHeaders: 1024 0x400
CheckSum: 0 0
Subsystem: 2 2 WINDOWS_GUI
DllCharacteristics: 49472 0xc140 DYNAMIC_BASE, NX_COMPAT, 0x4000
TERMINAL_SERVER_AWARE
SizeOfStackReserve: 1048576 0x100000
SizeOfStackCommit: 4096 0x1000
SizeOfHeapReserve: 1048576 0x100000
SizeOfHeapCommit: 4096 0x1000
LoaderFlags: 0 0
NumberOfRvaAndSizes: 16 0x10
=== DATA DIRECTORY ===
EXPORT rva:0x 0 size:0x 0
IMPORT rva:0x 1d3d8 size:0x 28
RESOURCE rva:0x 20000 size:0x 45db8
EXCEPTION rva:0x 0 size:0x 0
SECURITY rva:0x 0 size:0x 0
BASERELOC rva:0x 66000 size:0x 1430
DEBUG rva:0x 1c390 size:0x 1c
ARCHITECTURE rva:0x 0 size:0x 0
GLOBALPTR rva:0x 0 size:0x 0
TLS rva:0x 0 size:0x 0
LOAD_CONFIG rva:0x 1c3b0 size:0x 40
Bound_IAT rva:0x 0 size:0x 0
IAT rva:0x 17000 size:0x 12c
Delay_IAT rva:0x 0 size:0x 0
CLR_Header rva:0x 0 size:0x 0
rva:0x 0 size:0x 0
=== SECTIONS ===
NAME RVA VSZ RAW_SZ RAW_PTR nREL REL_PTR nLINE LINE_PTR FLAGS
.text 1000 15d02 15e00 400 0 0 0 0 60000020 R-X CODE
.rdata 17000 6a72 6c00 16200 0 0 0 0 40000040 R-- IDATA
.data 1e000 1854 a00 1ce00 0 0 0 0 c0000040 RW- IDATA
.rsrc 20000 45db8 45e00 1d800 0 0 0 0 40000040 R-- IDATA
.reloc 66000 1430 1600 63600 0 0 0 0 42000040 R-- IDATA DISCARDABLE
=== RESOURCES ===
FILE_OFFSET CP LANG SIZE TYPE NAME
0x37430 0 0x2000 180224 DLL #124
0x1dd50 0 0x2000 4465 ICON #1
0x1eec8 0 0x2000 67624 ICON #2
0x2f6f0 0 0x2000 16936 ICON #3
0x33918 0 0x2000 9640 ICON #4
0x35ec0 0 0x2000 4264 ICON #5
0x36f68 0 0x2000 1128 ICON #6
0x373d0 0 0x2000 90 GROUP_ICON #101
0x1da70 0 0x2000 732 VERSION #1
0x63430 0 0x409 392 MANIFEST #1
=== IMPORTS ===
MODULE_NAME HINT ORD FUNCTION_NAME
KERNEL32.dll 475 ReadFile
KERNEL32.dll 630 lstrcatA
KERNEL32.dll 278 GetModuleHandleA
KERNEL32.dll 50a SetCurrentDirectoryA
KERNEL32.dll 279 GetModuleHandleExA
KERNEL32.dll c6 CreateFileA
KERNEL32.dll 639 lstrcpyA
KERNEL32.dll 89 CloseHandle
KERNEL32.dll 24e GetFileSize
KERNEL32.dll 264 GetLastError
KERNEL32.dll 2b1 GetProcAddress
KERNEL32.dll 34c HeapFree
KERNEL32.dll 616 WriteFile
KERNEL32.dll 63f lstrlenA
KERNEL32.dll 63c lstrcpynA
KERNEL32.dll 615 WriteConsoleW
KERNEL32.dll 44f QueryPerformanceCounter
KERNEL32.dll 534 SetLastError
KERNEL32.dll 362 InitializeCriticalSectionAndSpinCount
KERNEL32.dll 5a2 TlsAlloc
KERNEL32.dll 5a4 TlsGetValue
KERNEL32.dll 5a5 TlsSetValue
KERNEL32.dll 5a3 TlsFree
KERNEL32.dll 2ec GetSystemTimeAsFileTime
KERNEL32.dll 27b GetModuleHandleW
KERNEL32.dll 5b1 UnhandledExceptionFilter
KERNEL32.dll 571 SetUnhandledExceptionFilter
KERNEL32.dll 21a GetCurrentProcess
KERNEL32.dll 590 TerminateProcess
KERNEL32.dll 389 IsProcessorFeaturePresent
KERNEL32.dll 382 IsDebuggerPresent
KERNEL32.dll 2d3 GetStartupInfoW
KERNEL32.dll 21b GetCurrentProcessId
KERNEL32.dll 21f GetCurrentThreadId
KERNEL32.dll 366 InitializeSListHead
KERNEL32.dll 4d5 RtlUnwind
KERNEL32.dll 464 RaiseException
KERNEL32.dll 130 EncodePointer
KERNEL32.dll 134 EnterCriticalSection
KERNEL32.dll 3c1 LeaveCriticalSection
KERNEL32.dll 113 DeleteCriticalSection
KERNEL32.dll 1ae FreeLibrary
KERNEL32.dll 3c7 LoadLibraryExW
KERNEL32.dll 161 ExitProcess
KERNEL32.dll 27a GetModuleHandleExW
KERNEL32.dll 277 GetModuleFileNameW
KERNEL32.dll 2d5 GetStdHandle
KERNEL32.dll 525 SetFilePointerEx
KERNEL32.dll 251 GetFileType
KERNEL32.dll 348 HeapAlloc
KERNEL32.dll 3b5 LCMapStringW
KERNEL32.dll 178 FindClose
KERNEL32.dll 17e FindFirstFileExW
KERNEL32.dll 18f FindNextFileW
KERNEL32.dll 38f IsValidCodePage
KERNEL32.dll 1b5 GetACP
KERNEL32.dll 29a GetOEMCP
KERNEL32.dll 1c4 GetCPInfo
KERNEL32.dll 1d9 GetCommandLineA
KERNEL32.dll 1da GetCommandLineW
KERNEL32.dll 3f3 MultiByteToWideChar
KERNEL32.dll 602 WideCharToMultiByte
KERNEL32.dll 23a GetEnvironmentStringsW
KERNEL32.dll 1ad FreeEnvironmentStringsW
KERNEL32.dll 2b7 GetProcessHeap
KERNEL32.dll 54e SetStdHandle
KERNEL32.dll 2da GetStringTypeW
KERNEL32.dll 1ff GetConsoleMode
KERNEL32.dll 1a2 FlushFileBuffers
KERNEL32.dll 203 GetConsoleOutputCP
KERNEL32.dll 351 HeapSize
KERNEL32.dll 34f HeapReAlloc
KERNEL32.dll ce CreateFileW
KERNEL32.dll 10c DecodePointer
=== VERSION INFO ===
# VS_FIXEDFILEINFO:
FileVersion : 10001.1.15.1
ProductVersion : 10001.1.15.1
StrucVersion : 0x10000
FileFlagsMask : 0x3f
FileFlags : 4
FileOS : 0x50000
FileType : 1
FileSubtype : 0
# StringTable 0c0a04b0:
CompanyName : "TrySearch"
FileDescription : "TrySearch"
FileVersion : "10001.1.15.1"
InternalName : "TrySearch.exe"
LegalCopyright : "Copyright (C) 2021 TrySearch"
OriginalFilename : "TrySearch.exe"
ProductName : "TrySearch"
ProductVersion : "10001.1.15.1"
VarFileInfo : [ 0xc0a, 0x4b0 ]
=== Packer / Compiler ===
MS Visual C++ v8.0
Looking at the SECTIONS
portion of the binary, we see that each section appears to be intact or normal looking, meaning there are no signs that this malware is packed. Typically, if malware is packed or compressed, the section names will be renamed to something different. For example, UPX, a common software packer, renames the headers to UPX0, UPX1, UPX2, etc.
If the malware were packed with UPX, it would look something like below…
The RESOURCES
portion of the binary typically holds things like images and icons. In this binary, we see that there appears to be a DLL (Dynamic Link Library) at offset 0x37430
. This is highly suspicious and typical of dropper malware.
The IMPORTS
section of the output shows all the dynamically loaded Windows APIs or function calls that this binary uses. At first glance, it looks as if it only uses functions from kernel32.dll
. There may be other function calls used that are statically linked. We can verify this later using another tool.
If we scroll down a bit on the imports list, we notice that this binary uses the API IsDebuggerPresent
. This can be indicative of an anti-reverse engineering technique used by malware authors to prevent the specimen from being debugged. We can confirm this through disassembly and code analysis later.
There are a lot of file manipulation APIs such as ReadFile
, CreateFileA
, and WriteFile
which are potential indicators of dropper malware as well.
The last observation made from the pedump
output is the PACKER / COMPILER
subsection indicating that the specimen was compiled with MS Visual C++ v8.0
. This version of MS Visual C++ came out in 2005 which makes it old.
If the malware were packed with UPX, this section would look something like below…
Strings
Running the strings command on the binary will output just over 4500 lines. We can confirm this with the command string Setup.bin | wc -l
which will pipe the strings output into the wc
(word count) command and filter based on the line count alone.
While it may be helpful to scroll through all the string output, having a targeted approach can help identify some more potential features of this malware.
For example, we can pipe our strings output into grep
and filter out everything but our desired string. Below I have grepped for the string .dll
to locate other potential DLLs in use.
The string ole32.dll
seems interesting as this DLL is used for embedding hyperlinks and documents in programs. Other interesting DLL strings are user32.dll
, gdi32.dll
and gdiplus.dll
which contain graphics and windowing APIs.
One hypothesis is that this malware masquerades as a legitimate program providing some form of a GUI (graphical user interface). We will be able to validate this during behavioral analysis.
Grepping for the string This program
yields two DOS stubs which is not typical for a Windows PE. This could mean that there is another executable embedded in the original PE file that will be extracted and dropped to the filesystem upon execution. This also strengthens our hypothesis of the DLL found in the .rsrc
section.
If we grep for the string http
to find potential Command and Control nodes, we find a few URLs. One URL that stands out is https://github.com/ControlzEx/ControlzEx
which is a GitHub repository for Controlz. Based on the README, it appears to be a .NET framework supporting UI (user interface) features.
If we look back at the strings output for DLLs, we notice that there is a ControlzEx.dll
listed as well.
Some other strings we could search for would be domain names, anything related to encryption, and anything related to downloading.
PEsec
We can use the tool pesec
to check the security features of this PE. This would be more useful for exploit development, but I like to check and see if ASLR (Address Space Layout Randomization) is enabled so I am better prepared when performing code analysis alongside a disassembler. If ASLR is enabled, I will rebase the binary’s address in IDA Pro to match that of the running process. Based on the output of pescan
, ASLR is enabled. We will save this info for later.
Part I Conclusion
In this article, we began our analysis with some initial recon of automated sandbox reports and then moved on to our own static analysis by identifying various header fields, sections, and imports. We discovered that the specimen is not packed, potentially uses various GUI DLLs, potentially tries to perform anti-debugging, and appears to contain a DLL in its .rsrc
section which is highly suspicious. This is not an exhaustive approach to static analysis but it’s a good start and introduction.
This concludes Part 1 of this series. Stayed tuned for Part II where we will disassemble the binary and perform analysis using IDA Pro
.