Skip to content

Latest commit

 

History

History
197 lines (120 loc) · 12.5 KB

0xf - IcedID.md

File metadata and controls

197 lines (120 loc) · 12.5 KB

IcedID

Background

  • 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

Analysis

⭐ 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, and ResumeThread ) to step through the unpacking process and review the referenced memory addresses for the final PE payload.

Pasted image 20241118125157

  • 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.
  • 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.

Pasted image 20241118130023

  • Next, we can simply dump the contents at that memory address to a file.

Pasted image 20241118130151

  • 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.

Pasted image 20241118130544

  • 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.

Pasted image 20241118130752

  • 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).

Pasted image 20241118131004

  • 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.

Pasted image 20241118132241

  • To fix that, the next thing we need to do is modify the Raw Addr. and Raw size values using PE-Bear.

Pasted image 20241118132641

  • 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).

Pasted image 20241118133052

  • We set a breakpoint at the jump and then run the executable.

Pasted image 20241118133342

  • After we land at the breakpoint, we Step Into, which takes us to the OEP of the unpacked PE.

Pasted image 20241118133433

  • 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.

Pasted image 20241118133542

  • 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.

Pasted image 20241118134916

  • 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 or GetProcAddress, which could be detected by monitoring tools.
  • Malware often uses this technique to locate the base address of kernel32.dll or ntdll.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.

Pasted image 20241119134406

  • 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/

Pasted image 20241119134835

  • The above image shows a simple function for obtaining some basic OS information.

Pasted image 20241119134854

  • The next function pulls information about the process and privilege level(s).

Pasted image 20241119135012

  • If the process is not running with the appropriate privileges, the author has included a privilege escalation function which uses runas in combination with ShellExecuteExA.

  • There is an interesting hooking function which writes e9 (opcode for jmp) to the address space of the NtCreateUserProcess API.

Pasted image 20241119135720

  • 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.