-
IcedID (also called BokBot) is primarily a banking trojan that dates back to at least 2017. It possesses many standard remote access trojan (RAT) capabilities in addition to functioning as a dropper.
-
Because of its modular architecture, it has been used to deploy second-stage payloads like Conti or Lockbit.
-
Proofpoint writes, "The well-known IcedID version consists of an initial loader which contacts a Loader C2 server, downloads the standard DLL Loader, which then delivers the standard IcedID Bot."
References
Cybereason - Threat Analysis: From IcedID to Domain Compromise
ProofPoint - Fork in the Ice: The New Era of IcedID
⭐ Credit to OALabs for a great tutorial on unpacking IcedID Malware.
Hash Type | File Hash |
---|---|
MD5 | d4abe68c54567b9db2bc35a03ae91bc9 |
SHA1 | 6d9e86c0066b21b02b941034389fe4bd96293961 |
SHA-256 | 0ca2971ffedf0704ac5a2b6584f462ce27bac60f17888557dc8cd414558b479e |
- This sample is an
.exe
which contains a packed IcedID payload inside. It unpacks and executes it in memory. - We can begin by setting breakpoints at the appropriate API calls (
CreateProcessInternalW
,WriteProcessMemory
, andResumeThread
) to step through the unpacking process and review the referenced memory addresses for the final PE payload.
- The fact that we can see an entire PE file being injected into memory suggests it is likely already mapped.
- The concept of a mapped PE file represents the PE file as it exists when loaded into memory by the Windows loader. It reflects the layout of the file as it would be executed by the operating system.
- The sections are aligned to virtual memory, relations are applied, the PE headers and sections are loaded into their respective virtual addresses in memory, etc.
How PE File Mapping Works
-
When a PE file is mapped into memory, the process is typically as follows:
-
Loading the File Into Memory
- The file is read from disk and its structure (headers and sections) is parsed.
- The PE headers (DOS header, NT header, section headers, etc) are analyzed to determine how the file should be laid out in memory.
-
Aligning Sections
- Sections in the PE file are aligned to virtual memory boundaries based on the
SectionAlignment
value in the PE header. - This is different from their raw
FileAlignment
on disk.
- Sections in the PE file are aligned to virtual memory boundaries based on the
-
Setting up the Image Base
- The PE file is mapped into memory at its preferred image base address, as specified in the header.
- If the preferred base in unavailable, relocations are applied to adjust memory references.
-
Applying Relocations
- The relocation table in the PE file is used to modify addresses in the code or data sections to match the actual memory location where the file is loaded.
-
Resolving Imports
- Import tables (i.e. IAT) are processed, and references to external libraries or functions are resolved.
-
Initializing Data
- Data sections are initialized based on the file's contents.
- Zero-initialized sections (i.e.
.bss
) are allocated memory and cleared.
-
Setting Execution Context
- The program's entry point (specified in the PE header) is determined, and the process is set up to execute from this address.
-
If we right-click the memory region under
DUMP 1
(bottom left), we can go the specific memory address that is pointed to by the third Win API call parameter (lpBuffer
), which holds the PE contents.
- Next, we can simply dump the contents at that memory address to a file.
- At this point, we have successfully extracted the PE file that was written into memory. We can perform some triage with PE Studio or PE Bear.
- We can immediately see the classic indications of UPX packing, which tells us there is yet more to this payload that we must uncover.
- We can further confirm that the sample is UPX packed by using TRiD.
- If we attempt an automated unpack with UPX, it returns a checksum error. The unpacking process was unsuccessful. It might be easier to perform manual unpacking on this sample.
![[Pasted image .png]]
- We can confirm if this file is actually mapped into memory by reviewing the contents in a hex editor. If no mapping has been applied, the code will begin at the offset specified on the left (below). Otherwise, if it has been mapped, the code will begin at the offset specified on the right (below).
- As we can see below, this PE file is indeed mapped. But because we have dumped this file to disk, the mapped and unmapped versions will be the same.
- To fix that, the next thing we need to do
is modify the
Raw Addr.
andRaw size
values using PE-Bear.
- Next, let's unpack this PE in the debugger. Fortunately, UPX has a very predictable unpacking pattern. Once the unpacking code has been run, the program will usually make a direct jump to the unpacked code.
- If you have taken the GREM course, this assembly graph from x32dbg will probably be familiar (below, left side).
- We set a breakpoint at the jump and then run the executable.
- After we land at the breakpoint, we
Step Into
, which takes us to the OEP of the unpacked PE.
- Let's dump the executable with the help of the
Scylla
plugin. We'll also fix the IAT.
![[Pasted image 20241118133542.png]]
Analysis of the Final Payload
- After a multi-step process of unpacking, we have our final payload. It's time to open this in a disassembler.
- At the entry point, the specimen builds the stack string
svchost.exe
, which it appears to pass as a parameter to at least two calls.
- From experience, we can reasonably deduce that
svchost.exe
will likely be the target of injection. This matches what HybridAnalysis reported on the same sample.
- One of the very first functions called addresses the PEB. Specifically, it appears to be walking the PEB.
The PEB - A Quick Primer
- The PEB is a struct that holds important metadata about a process.
- For example, it contains pointers to lists of loaded modules in a process, such as:
- InMemoryOrderModuleList: Lists modules in the order they are loaded into memory.
- InLoadOrderModuleList: Lists modules in the order they were loaded.
- InInitializationOrderModuleList: Lists modules in the order they were initialized.
How Malware Can Use the PEB
- Malware may traverse these lists to locate specific modules or API addresses without calling APIs like
LoadLibrary
orGetProcAddress
, which could be detected by monitoring tools. - Malware often uses this technique to locate the base address of
kernel32.dll
orntdll.dll
to resolve function pointers stealthily. - The PEB also contains a flag (
BeingDebugged
) that indicates whether the process is being debugged. Malware may check this flag to detect if it's being analyzed in a debugger. - The
NtGlobalFlag
field in the PEB contains indicators for heap debugging and other diagnostics. Malware may inspect this to determine if debugging or instrumentation is active.
How to Bypass Dynamic API Call Resolution via PEB Walking
- When malware authors choose to explore the PEB to dynamically resolve APIs, we can bypass this measure by allowing the respective function to execute in
x32dbg
and then simply dumping the process to disk and rebuilding the IAT afterwards (as opposed to the beginning of execution or the OEP). - A new import table will be built as the specimen locates the APIs via the PEB.
- In the above function, the malware author is walking the PEB by referencing offsets from
fs:[30]
, which is the location of the PEB. This can be used for several different purposes. The Unprotect Project has a great write-up on this (below).
Reference: https://unprotect.it/technique/heap-flag/
- The above image shows a simple function for obtaining some basic OS information.
- The next function pulls information about the process and privilege level(s).
-
If the process is not running with the appropriate privileges, the author has included a privilege escalation function which uses
runas
in combination withShellExecuteExA
. -
There is an interesting hooking function which writes
e9
(opcode forjmp
) to the address space of theNtCreateUserProcess
API.
- Just after the
jmp
, they write the address of the function.
The Process Injection Sequence
- The IcedID author used a very specific code injection technique - Cisco Talos writes the following:
Once launched, IcedID takes advantage of an interesting technique to inject malicious code into svchost.exe — it does not require starting the target process in a suspended state, and is achieved by only using the following functions:
- kernel32!CreateProcessA
- ntdll!ZwAllocateVirtualMemory
- ntdll!ZwProtectVirtualMemory
- ntdll!ZwWriteVirtualMemory
IcedID's code injection into svchost.exe works as follows:
1. In the memory space of the IcedID process, the function ntdll!ZwCreateUserProcess is hooked.
2. The function kernel32!CreateProcessA is called to launch svchost.exe and the CREATE_SUSPENDED flag is not set.
3. The hook on ntdll!ZwCreateUserProcess is hit as a result of calling kernel32!CreateProcessA. The hook is then removed, and the actual function call to ntdll!ZwCreateUserProcess is made.
4. At this point, the malicious process is still in the hook, the svchost.exe process has been loaded into memory by the operating system, but the main thread of svchost.exe has not yet started.
5. The call to ntdll!ZwCreateUserProcess returns the process handle for svchost.exe. Using the process handle, the functions ntdll!NtAllocateVirtualMemory and ntdll!ZwWriteVirtualMemory can be used to write malicious code to the svchost.exe memory space.
6. In the svchost.exe memory space, the call to ntdll!RtlExitUserProcess is hooked to jump to the malicious code already written
7. The malicious function returns, which continues the code initiated by the call tokernel32!CreateProcessA, and the main thread of svchost.exe will be scheduled to run by the operating system.
8. The malicious process ends.