Skip to content

Latest commit

 

History

History
1022 lines (601 loc) · 33.6 KB

l14-DebugDropper.md

File metadata and controls

1022 lines (601 loc) · 33.6 KB

Lab 14 - Debugging a 64-bit Dropper


Scenario

You’ve been called by a client to examine a sample that was found on one of their developer’s systems. The client thinks this sample could be hiding its true nature of activity.


The goal of this lab is to understand how to both debug and reverse engineer a dropper, which is widely used by different threat actors.


After completing this lab, you will be able to use a debugger such as x64dbg (and perhaps a disassembler, such as IDA Pro) to debug and reverse engineer a malicious 64-bit Dropper that drops a shellcode from its resource section and executes it. You will learn how to go through the code step-by-step and debug the shellcode to understand its true nature. This also is an excellent lab to learn more about 64-bit assembly and shellcode.


192.168.210.10 / AdminELS / Nu3pmkfyX



Tools

  • x64dbg
  • IDA Pro
  • PEStudio


Task 1: PE and Resource info

Question

Gather general information about the sample using different tools (static analysis).


Answer

Static Analysis using PEStudio

Load the sample in PE Studio.

picture 440

  • MD5: 2d20d19b5ba4239a2d2ea7a09fb1979b
  • SHA1: 4e132b88d43e9a135208975dcafc719a0ec22777
  • SHA256: 957e6ea1c709265677fa9f5516bf1c077425791125ec77c396f4092b7db2bc32
  • 64-bit Console Application
  • Windows PE (4d 5a)

Indicator

picture 441


File header

picture 442

  • Potential compiled time: 29 March 2020 04:04:59 UTC

Sections

picture 443

  • Standard non-packed header names

Strings

picture 444

  • 0x1C3A - GetCurentProcessId
  • 0x1C50 - GetCurrentThreadId
  • 0x1C96 - RtlCaptureContext
  • 0x1CAA - RtlLookupFunctionEntry

Imports

picture 445

  • Similar to Strings
  • Imports from kernel32.dll

Manifest

picture 446

  • Does not require Admin privilege

Resources

picture 447

  • Non standard resource

Static Analysis - Resource Hacker

Load the sample in Resource Hacker.

Check the IMG folder:

picture 448

  • Doesn't look like an image!

Next try to export this as a binary data:

picture 449

picture 450


Get the file hash of the output file using PowerShell:

Get-FileHash -Algorithm MD5 .\IMG101.bin

picture 451

  • MD5: EABB4194819818CF0F712D02EA00100E

Check this hash on VirusTotal:

picture 452

  • 15/55 detection
  • Suspicious finding


Task 2: Function calls and Breakpoints

Question

This is a malicious sample that was collected, and you need to figure out what functions are being used and where to place a breakpoint to control the process.


Answer

IDA Pro

Run IDA Pro 64-bit as admin, and load the sample. It brings us to the main function disassembly:

picture 453

  1. FindResourceA is called
  2. SizeofResource is called
  3. LoadResource is called
  4. VirutalAlloc is called
  5. memcpy is called
  6. rbx is called <---- Interesting

X64dbg

Load the sample into x64dbg:

picture 454


Go to the Symobol tab and click dropme.exe on the left:

picture 455

  • Here are the imports and exports of the sample

Double-cliking dropme.exe in the left, you will be brought to the disassembly window. Right click in the window > Search for > Intermodular calls:

picture 456

picture 457

  • These 4 calls are of interest
  • Add breakpoints - Use F2 upon selecting the entry:

picture 458


Going back to the CPU tab, you will see the breakpoints added in RED:

picture 459


Then hit the Start button, which leads us to the first breakpoint EntryPoint.

picture 460

  • The execution is Pause at 0x7FF716E512DC


Task 3: Running the Dropper

Question

Use a debugger to run the dropper and understand what it’s doing.


Answer

Resuming Task 2 and hit the Run button again, the debugger pauses at the FindResourceA call:

picture 461

HRSRC FindResourceA(
  HMODULE hModule,
  LPCSTR  lpName,
  LPCSTR  lpType
);

The parameters that will be passed to FindResourceA:

  • rcx has the value 0 --> hModule
  • rdx has the value 65h (101 in decimal) --> lpName
  • r8 has the value IMG -> lpType

To see what happening within the function FindResourceA, use Step into (F7):

picture 462


After inspecting the insturctions in FindResourceA, press Ctrl+F9 to execute until return.

picture 463

  • We will be paused at the ret instruction of the FindResourceA function

Press F7 to execute the ret instruction:

picture 464

  • We will be at the instruction right after the FindResourceA function call

Note the Registry value on the right:

picture 465

  • rax contains the return value of FindResourceA, which is 0x7FF716E55080
  • This is the handle to the resource

Press F9 to go to the next breakpoint at call SizeofResource:

picture 466


Now the SizeofResource is to be called - let's review the function in Microsoft document:

DWORD SizeofResource(
  HMODULE hModule,
  HRSRC   hResInfo
);

picture 467

  • rcx --> 0 --> hModule
  • rdx --> 0x0FF716E55080 --> hResInfo
  • The function is used to return the size, in bytes, of the target resource

Then press F9 to go to the next breakpoint - call LoadResource:

picture 468

  • rax has the returned value of 0x1FE - 510 in decimal

The next function call will be LoadResource. Check MS document:

HGLOBAL LoadResource(
  HMODULE hModule,
  HRSRC   hResInfo
);

picture 469

  • rcx -> 0 -> hModule
  • rdx -> 0x7FF716E55080 -> hResInfo
  • These parameters will be passed to LoadResource function, and return the handle that can be used to obtain a pointer to the 1st byte of the target resource in memory.

Press F9 to continue the execution until the next breakpoint at call VirutalAlloc:

picture 470


If we right click the RAX value, and click Follow in Dump, we will see the content of IMG resource in the Hex Dump:

picture 471

picture 472


Next the program will call the function VirtualAlloc. Let check the MS document out:

LPVOID VirtualAlloc(
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);
  • The function will allocate memory in the address space of another process.
  • Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process. Memory allocated by this function is automatically initialized to zero.

picture 473

  • rcx --> 0 --> lpAddress
  • rdx --> 0x1FE --> 510 bytes --> dwSize
  • r8 --> 0x1000 (MEM_COMMIT) --> flAllocationType
    • Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed.
  • r9 --> 0x40 --> flProtect

Continue the execution by stepping into the VirtualAlloc function (F7):

picture 474


Execute until we get back to the user's code:

picture 475

  • Add a breakpoint at call rbx as well

picture 476


At this point, if right click the RAX value and Follow in dump, we will see an empty memory region allocated using VirtualAlloc:

picture 477


Next, we are going to execute the memmove function. Again check the MS document:

void *memmove(
   void *dest,
   const void *src,
   size_t count
);

picture 478

  • rcx --> 0x1CEAA550000 --> dest
  • rdx --> 0x7FF16E550B0 --> src
  • r8 --> 0x1FE --> 510 bytes --> count
  • These parameters will be passed to memmove function
  • The return value will be the value of the destination

picture 481

picture 482


Press F7 to step into the memmove function:

picture 483

  • Examine the instructions
  • Press Ctrl + F9 to reach ret instruction
  • Press F9 again to reach the next breakpoint call rbx

picture 484


picture 485

  • rbx is 0x1CEAA50000

Right click rbx value and Follow in dump:

picture 486

  • It now points to a region having the HEX of IMG

Then when we Step Into (F7), we will reach the beginning of the shellcode.

picture 487



Task 4: Debugging the shellcode

Question

Use a debugger to trace through the shellcode step-by-step and understand its true nature.


Answer

Resuming Task 3, let's check the execution flow graph Right Click in the window > Graph:

picture 488

picture 489

ShellCodeFlow


Let's step into the first two instructions:

picture 491

picture 492

  • cld: Clear the direction flag (DF) in the RFLAGS register -> DF=0.
    • DF flag is used to for, which makes string opreations increment of both index registers RSI and RDI
  • and rsp,FFFFFFFFFFFFFFF0: Ensure that RSP is 16-byte aligned since it is a 64-bit PE
  • call 1CEAA550006: Call address resolved at runtime and will push the next address onto the stack

Right click the call address and follow in disassembler, it brings us to the pop rbp instruction:

picture 493


First examine the first 2 instructions:

picture 494

  • pop rbp: Place the address we have just pushed onto the stack into rbx
  • mov r14, 32335F327377: The value is in fact ws2_32 in reverse order (little endian). The shellcode will be dealing with the ws2_32 library, which is the Winsock API used for network communications and has functions like WSAStartup, WSData, bind, connect, recv, etc. This value is copide to r14

picture 495


Then look into the next 2 instructions:

picture 496

picture 497

picture 498

  • push r14: Push the name of the library 32335F327377 to the stack, including a padding of 0s to make them 8 bytes
  • move r14,rsp: Align the value on the stack since this is a 64-bit program, so we need the addresses to be 8 bytes instead of 4 bytes
    • Done by Padding: 0x000032335F327377
    • Now the value of RSP pointing to the top of the stack holds the value 0x000032335F327377, is now moved to r14
    • This saves the pointer to the ws2_32 string for to be used when doing he LoadLibraryA call

picture 499

picture 500

  • sub rsp, 1A0: Reserve 416 bytes on the stack
    • Actually allocate the sizeof( struct WSAData) and it will be +8 to make sure of the alignment
  • mov r13,rsp: Copy the value in RSP (which holds WSAData structure) to r13, so it could be used for WSAStartup call

Step into the next instruction:

picture 501


struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};

We can breakdown the value 84D2A8C0 D21E 00 02:

  • sin_addr -> 84 D2 A8 C0 -> Reverse -> C0 A8 D2 84 -> 192.168.210.132
  • sin_port -> D2 1E -> Reverse -> 1E D2 --> 7890
  • sin_family -> 02 -> AF_INET -> IPv4
  • sin_zero -> 00 --> sin_zero = 0 or Protocol = 0
    • The first 0 is used mostly for padding to 16-bytes.
    • While if we assume it's for the protocol, then based on MS document;
    • if it is 0, it means that it will be left to the service provider to choose which protocol to use.

Inspect the next 2 instructions:

picture 502

picture 503

  • push r12: Push the value in r12 (sockaddr struct) to the stack

picture 504

  • mov r12,rsp: Save the pointer to sockaddr struct for connect call

Then, examine the next 3 instructions:

picture 505

  • mov rcx,r14: Set the parameter for loading LoadLibraryA

picture 506

  • mov r10d,726774C: Copy the DWORD hash('kernel32.dll', 'LoadLibrary') into r10d (Note 726774C is LoadLibraryA's hash!)

picture 507

  • call rbp: Call the address held in rbp, which is actually the start of the API call and is going to used to call LoadLibraryA("ws2_32")

Before stepping into the next instruction, right click the address of RBP > Follow in Disassembler:

picture 508


picture 509

  • push r9: To save the 4th parameter
  • push r8: To save the 3rd parameter
  • push rdx: To save the 2nd parameter
  • push rcx: To save the 1st parameter
  • push rsi: To save RSI
  • xor rdx,rdx: To zero rdx, which will be used for calculating offsets
  • mov rdx,qword ptr ds:[rdx+60]: Copy the value found in GS using offset RDX + 60h

Note that now our goal is to find the base of the loaded module. From the base, we can find the loaded modules and then load the functions we need from those libraries, especially LoadLibraryA. To achieve that, we will need to traverse a list of Kernel data structures to reach our goal.


When dealing with 64-bit applicatoins, the GS register points to the Thread Environment Block (TEB), also known as Thread Information Block (TIB).

  • This is shown in the instruction mov rdx,qword ptr ds:[rdx+60]

Now using RDX as a pointer, we can access the LDR entry with the +0x18, as shown in the next instruction. The LDR entry points to _PEB_LDR_DATA structure, which we can use to access the loaded modules in the executable.

picture 510


For the _PED_LDR_DATA structure, we can see that we can access the InMemoryOrderModuleList using the offset 0x20:

//0x58 bytes (sizeof) struct _PEB_LDR_DATA 
{
    ULONG Length;                                               // 0x0
    UCHAR Initialized;                                          // 0x4
    VOID* SsHandle;                                             // 0x8
    struct _LIST_ENTRY InLoadOrderModuleList;                   // 0x10
    struct _LIST_ENTRY InMemoryOrderModuleList;                 // 0x20
    struct _LIST_ENTRY InInitializationOrderModuleList;         // 0x30
    VOID* EntryInProgress;                                      // 0x40
    UCHAR ShutdownInProgress;                                   // 0x48
    VOID* ShutdownThreadId;                                     // 0x50
}

InMeoryOrderModuleList is of type _LIST_ENTRY, which is a data structure and its structure is as below:

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

Therefore, the next instruction will bring us to the first module from the InMemoryOrderModuleList:

  • mov rdx,qword ptr ds:[rdx+20]

Traversing the previous list, we are now in the _LDR_DATA_TABLE_ENTRY data structure, but not at offset 0x0 - now at offset 0x10.

The first couple of entries of this data structure is shown:

//0x50 bytes (sizeof)
struct _LDR_DATA_TABLE_ENTRY
{
    struct _LIST_ENTRY InLoadOrderLinks;                                    //0x0
    struct _LIST_ENTRY InMemoryOrderLinks;                                  //0x8
    struct _LIST_ENTRY InInitializationOrderLinks;                          //0x10
    VOID* DllBase;                                                          //0x18
    VOID* EntryPoint;                                                       //0x1c
    ULONG SizeOfImage;                                                      //0x20
    struct _UNICODE_STRING FullDllName;                                     //0x24
    struct _UNICODE_STRING BaseDllName;                                     //0x2c
    ULONG Flags;                                                            //0x34
    USHORT LoadCount;                                                       //0x38
    USHORT TlsIndex;                                                        //0x3a
    union
    {
        struct _LIST_ENTRY HashLinks;                                       //0x3c
        struct
        {
            VOID* SectionPointer;                                           //0x3c
            ULONG CheckSum;                                                 //0x40
        };
    };
    union
    {
        ULONG TimeDateStamp;                                                //0x44
        VOID* LoadedImports;                                                //0x44
    };
    VOID* EntryPointActivationContext;                                      //0x48
    VOID* PatchInformation;                                                 //0x4c
}; 
  • The interesting one is the BaseDllName

The next instruction is:

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

  • By adding REX+0x50, we will be actually landing at *Buffer, which holds the name of the module.
  • Therefore, the instruction will get the pointer to the module's name in Unicode string format

The whole traversing of the data structure is shown below:

picture 511


Step into the next instruction. The following instructions are accessing the MaximumLength value and storing it in RCX. This will be used as the maximum length to be checked.

picture 512

  • movzx rcx,word ptr ds:[rdx+4A] : First set RCX to the length we want to check

picture 513

  • xor r9,r9
  • Clear R9 which will be used to store the hash of the module name

picture 514

  • xor rax,rax
  • Clear RAX

The Buffer, which points to name of the module, is of type WCHAR, which means the module name which the first is the name of the program itself DropMe.exe, will be read or used as shown in the following figures respectively:

  • Follow RDX + 50:

picture 515

  • Beginning of Module Name:

picture 516


Then we get into a loop to check the name of the module if it is lowercase or not - if not the shellcode will normalize all to uppercase.

  • lodsb: this will load a byte from the module name located now at the address DS:RSI into the al register.
    • To validate, just right-click on RSI and follow in dump.
    • After this RSI will be incremented by 1, that’s because of the cld instruction at the beginning of the shellcode

  • cmp al,61: now al is going to be compared with 0x61 to check if the module name is using lower case letters.
    • Note: some versions of Windows use lower case module names

  • jl 1CEAA550037: If the name is not in lowercase, then the code will jump to this location to start the hashing process, since this shellcode is using importing APIs by hash.
    • Note: remember the last two bytes are actually an offset.
    • If the letters are not upper case, then the instruction below will subtract 32 from al which holds the character, and this will lead to normalizing the chars all to upper case

  • sub al,20: The normalizing instruction (lower to upper case letters convertor).
    • Now if we have our letter in upper case, it is time for applying ROT13, which is done in the next instructions.

  • ror r9d,D: rotate right the value in R9 with 0xD which is part of the hash value being calculated

  • add r9d,eax: this is where the shellcode adds the next byte of the name from EAX into r9

Finally, if the shellcode finished reading the name of the module, it will move to the next instruction, otherwise it will jump to the location at offset 002D to continue the normalize, rotate, and adding to R9 loop.

  • loop 1CAA55002D: stay in the loop until the shellcode has read the module name

  • This loop, in our case, will be 22 iterations. This is because its name is in Unicode, so we have 2*length (Dropme.exe) + 2 bytes for the null byte terminator.


Next:

  • push rdx: Save the pointer to the InMemoryOrderModuleList to be used later
  • push r9: R9 contains the calculated hash and the shellcode is also saving for later use

Now it’s time to process the export address table. Since the shellcode already has a pointer in the _LDR_DATA_TABLE_ENTRY through InMemoryOrderModuleList, it can now access the DllBase of this module.

This is done in the next instruction, but before doing that, a quick reminder won’t hurt:

picture 517

  • As we can see, the DllBase is at offset 0x30 and the shellcode already has a pointer at 0x10.

  • mov rdx,qword ptr ds:[rdx+20]: Add 0x20 to get the module base address

Now RDX contains the DllBase (aka BaseAddress) and from there, the shellcode can start parsing different entries in the PE file. Remember, shellcodes do not have PE headers and they are not loaded normally like a PE file - there is no loader.


In the next instruction, the shellcode gets the PE header, which is at offset 0x3C from the base address, which is shown below:

picture 518

  • mov eax,dword ptr ds:[rdx+3C]: Get the PE header
    • Now by adding the base address to the PE header, we can get the beginning of the module’s PE header in memory.

  • add rax,rdx: adding the modules base address
    • We can also see that RAX now points to the beginning of the module’s PE header by rightclicking on RAX and following it in dump below:

picture 519


The next instruction is to check if the module is a true PE64 or not, because if it is not. We can see that the instruction is checking offset 0x118 with the value 0x20B, which represents PE64.

picture 520


picture 521

  • cmp word ptr ds:[rax+18],20B: Check if this module is actually PE64
  • jne 1CEAA5500CB: If not, proceed to the next module

Next, the shellcode checks if there are any export tables and then attempts to get them using a relative address from PE header, which is 0x88. If we check the following, we can see that 0x188 (PE Header+RVA = location of export table):

picture 522


picture 523

  • test rax,rax: Test if no export address table is present

  • je 1CEAA5500CB: If no EAT was present, move on and process the next module

  • In our case, there isn’t any export address table present, so the shellcode will move on to process the next module:

picture 524


  • The reason why the shellcode skips the module is that the shellcode is searching for the modules that can provide it with loading modules and API capabilities, in our case LoadLibraryA.
  • This means it is looking for modules that have exports, and since this module does not have any exports, it means the LoadLibraryA API is not going to be found here, so it moves on to the next module.

picture 525

The next set of instructions are to prepare our jump to processing the next module.

  • pop r9: Pop off the current module's hash
  • pop rdx: Restore the shellcode's position in the module list
  • mov rdx,qword ptr ds:[rdx]: Get the next module
  • jmp 1CEAA550021: Process the new module

Stepping into, it jumps to offset 0x0021:

picture 526


After executing the next three instructions, you should clearly see that it is now processing the NTDLL.DLL module.

This also means that the NTDLL.DLL module is the second module loaded in the linked list (list of loaded modules, remember the FLink and BLink).

If you pay attention closely, you will notice that we have already went through this code before.

picture 527


Once all of this is done, we should now have the module hash computed.

picture 528


We have also seen the next couple of instructions, which are the ones that get the base address, PE header and then check if the module is PE64 or not. We can see the execution of those instructions in the following:

picture 529


  • add rax,rdx: Add the modules base address
  • push rax: Here the shellcode is saving the current modules EAT
  • mov ecx,dword ptr ds:[rax+18]: Get the number of function names
  • mov r8d,dword ptr ds:[rax+20]: Get the RVA of the function names
  • add r8,rdx: Add the modules base address

This is a long loop of iterations which will keep continue until the function required is found, which in our case is LoadLibraryA (technically/internally LoadLibraryExA).

I would advise you to spend some time going through a couple of iterations from the instructions below, until you understand what is going then you can move on to the instruction at offset 009A.

What that means, is once you understand these instructions below and what they are doing, add a breakpoint at offset 009A and hit run.

The code that will be explained below and the next step where we added a breakpoint, is shown in the following:

picture 530



Task 5: Explain the Graph Code Blocks

Question

Generate a graph for the shellcode and explain its blocks. This is a DIY exercise.


Answer