Malware Basics: Manually unpacking 5 malware samples with x32-64dbg (PMA labs)

ChloeOS
13 min readJan 6, 2023

--

This sample comes from the practical malware analysis labs, on chapter 18 Packers and unpacking. You can find the binaries here https://github.com/mikesiko/PracticalMalwareAnalysis-Labs

Why use a packer:
A packer strips the binary and makes it smaller and harder to analyze the original code.

How Packed programs are loaded:

Packers Create a new executable that stores the transformed executable as data and contains an unpacking stub that gets loaded by the OS.

— The unpacking stub is loaded by the OS, then the unpacking stub loads the original code.
— The entry point points to the unpacking stub rather than the original code.

The unpacking stub

  • Unpacks the original executable into memory
  • Resolves all imports
  • Transfers execution to the original entry point (OEP)
  • The instruction that points to OEP is the “tail jump”

UnPacking Illustrated:

Illustration From chapter 18 in practical malware analysis awkwardly re-created

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Lab 18–01:

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -

When you load the binary into IDA it gives a warning that the binary might be packed, If you view the imports section you can see it’s almost empty another indicator that the sample is packed.

Follow the Xrefs on the call to LoadLibraryA and you will end up on a section named UPX2, a customized version of the UPX packer so unpacking with the UPX utility won’t work here.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -

Finding OEP: We can use OllyDbg or x64-dbg to find the original entry point which is the first instruction before the program was packed, We need to find the tail jump, which is the instruction that jumps from the unpacking stub to the OEP.

In IDA the tail jump is at the memory address 0x0409F43, the jmp instruction points to an address that is far away in memory. This is where we will set our break point and dump unpakced program out of memory.

Using Ollydbg:

Open the binary in OllyDbg and find the last instruction before a bunch of invalid null pointers.

Set a breakpoint at the address 00409F43, run the program, it should hit your breakpoint, Then step into (single step) the jmp instruction, The program is now fully unpacked, Finally dump the unpacked binary to a disk by selecting Plugins -> OllyDump -> Dump debugged process with all default options. (I had to use an older version of OllyDbg for the OllyDmp plugin to work..)

Unpacked binary in OllyDbg

IDA import table:


0000000000405000 GetCurrentHwProfileA ADVAPI32
0000000000405004 GetUserNameA ADVAPI32
000000000040500C Sleep KERNEL32
0000000000405010 CreateProcessA KERNEL32
0000000000405014 FlushFileBuffers KERNEL32
0000000000405018 GetStringTypeW KERNEL32
000000000040501C GetCommandLineA KERNEL32
0000000000405020 GetVersion KERNEL32
0000000000405024 ExitProcess KERNEL32
0000000000405028 TerminateProcess KERNEL32
000000000040502C GetCurrentProcess KERNEL32
0000000000405030 UnhandledExceptionFilter KERNEL32
0000000000405034 GetModuleFileNameA KERNEL32
0000000000405038 FreeEnvironmentStringsA KERNEL32
000000000040503C FreeEnvironmentStringsW KERNEL32
0000000000405040 WideCharToMultiByte KERNEL32
0000000000405044 GetEnvironmentStrings KERNEL32
0000000000405048 GetEnvironmentStringsW KERNEL32
000000000040504C SetHandleCount KERNEL32
0000000000405050 GetStdHandle KERNEL32
0000000000405054 GetFileType KERNEL32
0000000000405058 GetStartupInfoA KERNEL32
000000000040505C HeapDestroy KERNEL32
0000000000405060 HeapCreate KERNEL32
0000000000405064 VirtualFree KERNEL32
0000000000405068 HeapFree KERNEL32
000000000040506C RtlUnwind KERNEL32
0000000000405070 WriteFile KERNEL32
0000000000405074 GetLastError KERNEL32
0000000000405078 SetFilePointer KERNEL32
000000000040507C HeapAlloc KERNEL32
0000000000405080 GetCPInfo KERNEL32
0000000000405084 GetACP KERNEL32
0000000000405088 GetOEMCP KERNEL32
000000000040508C VirtualAlloc KERNEL32
0000000000405090 HeapReAlloc KERNEL32
0000000000405094 GetProcAddress KERNEL32
0000000000405098 LoadLibraryA KERNEL32
000000000040509C SetStdHandle KERNEL32
00000000004050A0 MultiByteToWideChar KERNEL32
00000000004050A4 LCMapStringA KERNEL32
00000000004050A8 LCMapStringW KERNEL32
00000000004050AC GetStringTypeA KERNEL32
00000000004050B0 CloseHandle KERNEL32
00000000004050B8 URLDownloadToCacheFileA urlmon

Using X32-64dbg:

I prefer to use x32-64Dbg, OllyDbg is a bit older and isn’t being developed anymore, But I wanted to use Olly at first because that’s what is used in the challenges.

For this binary we have to use x32-dbg and the scylla plugin for dumping the process, which I think comes stand in x32–64dbg now.

The process is basically the same, attach the binary, run it, put a breakpoint on the tail jump, run until it hits your breakpoint, then single step into the JMP instruction.

Once you single step into the JMP instruction the eip will point to the OEP at 0040154F, Now just use Scylla to dump the original program

Make sure the OEP is set to the correct address at 0040154F, And you should be able to view a list of imports in Scylla.

Finally Choose the “Dump” option, Once you save the file to disk, Choose “Fix Dump” on the dumped file, you should see a file, This fixes the PE header so the program is complete and can be analyzed in IDA.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Lab 18–02:

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Using x32–64dbg:

Load the binary in x32dbg, at the address 0x04050E1 there’s a JE instruction that points to a memory address that’s far away

Instead of setting a breakpoint here, trace over the function into the the section in memory where the address 401090 is located.

select trace over and set the condition mem.base(cip) != 405000 where 405000 is the base address of our current section and cip is just the 32 bit reference to EIP, eip works fine here. This method is the equivalent to finding OEP by section hop in OllyDbg.

Once you’re in the unpacked program just repeat the process from 18–01. Scylla -> set OEP to 401090 -> search IAT -> dump binary -> fix dump.

You could set a breakpoint at the jump but it won’t take you into the fully unpacked program, if you keep running the program with the breakpoint enabled you’ll see the functions/imports being used ‘LoadLibraryA and GetProcAddress load the functions’, but tracing over into the section is a much easier way to get the fullly unpacked program with a fully intact IAT.

Full IAT in IDA:

Address Ordinal  Name            Library
0000000000402000 __getmainargs msvcrt
0000000000402004 _controlfp msvcrt
0000000000402008 _except_handler3 msvcrt
000000000040200C __set_app_type msvcrt
0000000000402010 __p__fmode msvcrt
0000000000402014 __p__commode msvcrt
0000000000402018 _exit msvcrt
000000000040201C _XcptFilter msvcrt
0000000000402020 exit msvcrt
0000000000402024 __p___initenv msvcrt
0000000000402028 _initterm msvcrt
000000000040202C __setusermatherr msvcrt
0000000000402030 _adjust_fdiv msvcrt
0000000000402038 VariantInit oleaut32
000000000040203C SysAllocString oleaut32
0000000000402040 SysFreeString oleaut32
0000000000402048 OleInitialize ole32
000000000040204C CoCreateInstance ole32
0000000000402050 OleUninitialize ole32

Since I forgot to check which packer was used, we can load it in PeId to identify the packer. PeId identifies that FSG was used.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Lab 18–03:

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

PeId is able to identify the packer as PeCompact.

Finding OEP:

Running the binary once, it stops at a JMP instruction that points to pushfd which pushes all flags to the stack and pushad which pushes all general registers to the stack.

00405130 <lab18-03.EntryPoint> jmp lab18-03.405138                                              
00405132 push 1577
00405137 ret
00405138 pushfd
00405139 pushad
0040513A call lab18-03.405141

After the push instructions are executed it calls a function at 405141. The call points to an instruction that moves the data on the stack into eax, if we step over the first three instructions, we can examine the data that was pushed onto the stack by selecting the esp register and following in dump. We can set an access hardware breakpoint on the first bytes to catch when the register popped from the stack.

After stepping over the first 3 instructions:

Address on the stack after the PUSH instructions were executed:

right click on esp then select “follow in dump”

right click on the first bytess, breakpoint -> Hardware, Acess-> Dword

After running the binary again our hardware breakpoint lands on a pop instruction, you can also see at 407551 an address that’s far away 401577 gets pushed onto the stack, which looks like our tail jump.

Now just step into the return instruction and we will end up in the original program and we can dump the fully unpacked binary and view the full functionality in IDA.

just repeat the same process as always in Scylla. -> set OEP to 401577 -> IAT Autosearch -> Dump -> Fix Dump

Unpacked Import Table:

Address Ordinal Name Library
0000000000404000 WaitForSingleObject kernel32
0000000000404004 CreateProcessA kernel32
0000000000404008 Sleep kernel32
000000000040400C GetModuleFileNameA kernel32
0000000000404010 GetStringTypeA kernel32
0000000000404014 LCMapStringW kernel32
0000000000404018 LCMapStringA kernel32
000000000040401C MultiByteToWideChar kernel32
0000000000404020 LoadLibraryA kernel32
0000000000404024 GetProcAddress kernel32
0000000000404028 HeapReAlloc kernel32
000000000040402C GetCommandLineA kernel32
0000000000404030 GetVersion kernel32
0000000000404034 ExitProcess kernel32
0000000000404038 TerminateProcess kernel32
000000000040403C GetCurrentProcess kernel32
0000000000404040 UnhandledExceptionFilter kernel32
0000000000404044 FreeEnvironmentStringsA kernel32
0000000000404048 FreeEnvironmentStringsW kernel32
000000000040404C WideCharToMultiByte kernel32
0000000000404050 GetEnvironmentStrings kernel32
0000000000404054 GetEnvironmentStringsW kernel32
0000000000404058 SetHandleCount kernel32
000000000040405C GetStdHandle kernel32
0000000000404060 GetFileType kernel32
0000000000404064 GetStartupInfoA kernel32
0000000000404068 HeapDestroy kernel32
000000000040406C HeapCreate kernel32
0000000000404070 VirtualFree kernel32
0000000000404074 HeapFree kernel32
0000000000404078 RtlUnwind kernel32
000000000040407C WriteFile kernel32
0000000000404080 HeapAlloc kernel32
0000000000404084 GetCPInfo kernel32
0000000000404088 GetACP kernel32
000000000040408C GetOEMCP kernel32
0000000000404090 VirtualAlloc kernel32
0000000000404094 GetStringTypeW kernel32
000000000040409C WSAStartup ws2_32
00000000004040A0 WSASocketA ws2_32
00000000004040A4 gethostbyname ws2_32
00000000004040A8 closesocket ws2_32
00000000004040AC WSACleanup ws2_32
00000000004040B0 htons ws2_32
00000000004040B4 connect ws2_32

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Lab 18–04:

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

PeId Identifies the packer as ASPack

We can lean a bit about how ASPack works in the PMA book;

  • ASPack uses self modifying code

Unpacking Process:

  • Can be manually unpacked using hardware breakpoints
  • Early in the code there’s a PUSHAD instruction
  • Find the stack address that stores the registers
  • Set a hardware breakpoint on one the addresses

When we run the binary in x32dbg, it lands on a pushad instruction, we saw this in the ASPack section in PMA.

Execute this instruction by stepping over, then following the stack address in the dump set a hardware breakpoint on the first bytes.

Address on the stack after the PUSHAD instruction is executed: Set the Hardware access breakpoint on the first bytes.

After running the binary until it hits our hardware breakpoint:

We land on a conditional jmp which points to a push instruction that jumps to an address that’s far away, this is most likely our tail jump, just execute the following instructions using step into until you hit the ret instruction.

After the ret instruction executes we are brought into the original program, the address 00403896 is our OEP

Full Import Table:

Address Ordinal Name Library
000000000040B038 GetModuleFileNameA kernel32
000000000040B03C GetShortPathNameA kernel32
000000000040B040 Sleep kernel32
000000000040B044 WriteFile kernel32
000000000040B048 ReadFile kernel32
000000000040B04C GetLastError kernel32
000000000040B050 GetSystemDirectoryA kernel32
000000000040B054 CreateFileA kernel32
000000000040B058 GetFileTime kernel32
000000000040B05C SetFileTime kernel32
000000000040B060 DeleteFileA kernel32
000000000040B064 CloseHandle kernel32
000000000040B068 CompareStringW kernel32
000000000040B06C CompareStringA kernel32
000000000040B070 CreateProcessA kernel32
000000000040B074 GetFileAttributesA kernel32
000000000040B078 FlushFileBuffers kernel32
000000000040B07C LoadLibraryA kernel32
000000000040B080 GetProcAddress kernel32
000000000040B084 LCMapStringW kernel32
000000000040B088 LCMapStringA kernel32
000000000040B08C VirtualAlloc kernel32
000000000040B090 SetFilePointer kernel32
000000000040B094 GetStringTypeW kernel32
000000000040B098 ExitProcess kernel32
000000000040B09C TerminateProcess kernel32
000000000040B0A0 GetCurrentProcess kernel32
000000000040B0A4 GetTimeZoneInformation kernel32
000000000040B0A8 GetSystemTime kernel32
000000000040B0AC GetLocalTime kernel32
000000000040B0B0 DuplicateHandle kernel32
000000000040B0B4 GetCommandLineA kernel32
000000000040B0B8 GetVersion kernel32
000000000040B0BC SetStdHandle kernel32
000000000040B0C0 GetFileType kernel32
000000000040B0C4 SetHandleCount kernel32
000000000040B0C8 GetStdHandle kernel32
000000000040B0CC GetStartupInfoA kernel32
000000000040B0D0 CreatePipe kernel32
000000000040B0D4 GetExitCodeProcess kernel32
000000000040B0D8 WaitForSingleObject kernel32
000000000040B0DC HeapReAlloc kernel32
000000000040B0E0 HeapAlloc kernel32
000000000040B0E4 GetCPInfo kernel32
000000000040B0E8 GetACP kernel32
000000000040B0EC GetOEMCP kernel32
000000000040B0F0 UnhandledExceptionFilter kernel32
000000000040B0F4 FreeEnvironmentStringsA kernel32
000000000040B0F8 FreeEnvironmentStringsW kernel32
000000000040B0FC WideCharToMultiByte kernel32
000000000040B100 GetEnvironmentStrings kernel32
000000000040B104 GetEnvironmentStringsW kernel32
000000000040B108 GetModuleHandleA kernel32
000000000040B10C GetEnvironmentVariableA kernel32
000000000040B110 GetVersionExA kernel32
000000000040B114 HeapDestroy kernel32
000000000040B118 HeapCreate kernel32
000000000040B11C VirtualFree kernel32
000000000040B120 HeapFree kernel32
000000000040B124 RtlUnwind kernel32
000000000040B128 MultiByteToWideChar kernel32
000000000040B12C GetStringTypeA kernel32
000000000040B130 SetEnvironmentVariableA kernel32

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Lab 18–05:

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

PeId Identifies the packer as Upack or WinUpack.

Loading the binary into IDA there’s only shows two imports, LoadLibraryA and GetProcAddress. Finding and setting a breakpoint on GetProcAddress is probably a good place to start.

First Strategy:

  • Set a breakpoint on GetProcAddress
  • single step over instructions watching for loops that resolve the imports

Second Strategy:

  • Set a breakpoint on GetModuleHandleA or GetCommandLineA (used for WinUpacks GUI front-end)
  • Search backwards for OEP

Before we get the unpacking stub, search for GetProcAddress and set a breakpoint. The goal is to find the last call to GetProcAddress so that were close the tail jump.

After setting the breakpoint keep running the program until it stops at InterNetOpenA.

Restart the program and stop at the last call to GetProcAddress, follow and analyze the address by right-clicking ESI and follow into disassembler. Keep single stepping into instructions until you land at a jump at 408E96 that jumps to 408Eb7. This jump is executed right after a few pop instructions.

The jump goes into a ret instruction

if we single step into the ret, we land at the OEP at 401190 and can get the full import table.

Looking through the disassembly, we can see InternetOpenA which was loaded from the last call to GetProcAddress, GetCommandLineA ,and the URL that’s always in the beginning of the lab exercises.

Now just Dump the program and analyze it in IDA

Import table from IDA:

Address Ordinal Name Library
0000000000404000 CreateServiceA advapi32
0000000000404004 StartServiceCtrlDispatcherA advapi32
0000000000404008 OpenSCManagerA advapi32
0000000000404010 CreateWaitableTimerA kernel32
0000000000404014 SystemTimeToFileTime kernel32
0000000000404018 GetModuleFileNameA kernel32
000000000040401C SetWaitableTimer kernel32
0000000000404020 CreateMutexA kernel32
0000000000404024 ExitProcess kernel32
0000000000404028 OpenMutexA kernel32
000000000040402C WaitForSingleObject kernel32
0000000000404030 CreateThread kernel32
0000000000404034 GetCurrentProcess kernel32
0000000000404038 Sleep kernel32
000000000040403C GetStringTypeA kernel32
0000000000404040 LCMapStringW kernel32
0000000000404044 LCMapStringA kernel32
0000000000404048 GetCommandLineA kernel32
000000000040404C GetVersion kernel32
0000000000404050 TerminateProcess kernel32
0000000000404054 UnhandledExceptionFilter kernel32
0000000000404058 FreeEnvironmentStringsA kernel32
000000000040405C FreeEnvironmentStringsW kernel32
0000000000404060 WideCharToMultiByte kernel32
0000000000404064 GetEnvironmentStrings kernel32
0000000000404068 GetEnvironmentStringsW kernel32
000000000040406C SetHandleCount kernel32
0000000000404070 GetStdHandle kernel32
0000000000404074 GetFileType kernel32
0000000000404078 GetStartupInfoA kernel32
000000000040407C HeapDestroy kernel32
0000000000404080 HeapCreate kernel32
0000000000404084 VirtualFree kernel32
0000000000404088 HeapFree kernel32
000000000040408C RtlUnwind kernel32
0000000000404090 WriteFile kernel32
0000000000404094 HeapAlloc kernel32
0000000000404098 GetCPInfo kernel32
000000000040409C GetACP kernel32
00000000004040A0 GetOEMCP kernel32
00000000004040A4 VirtualAlloc kernel32
00000000004040A8 HeapReAlloc kernel32
00000000004040AC GetProcAddress kernel32
00000000004040B0 LoadLibraryA kernel32
00000000004040B4 MultiByteToWideChar kernel32
00000000004040B8 GetStringTypeW kernel32
00000000004040C0 InternetOpenUrlA wininet
00000000004040C4 InternetOpenA wininet

--

--

ChloeOS

Girl trapped in a dreamscape Operating System that must write and RE malware.