> For the complete documentation index, see [llms.txt](https://docs.hex-rays.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hex-rays.com/add-ons/malware/how-tos/manually-unpack-upx.md).

# Manually Unpack UPX with the Isolated System Emulator

In this how-to we are going to manually unpack a UPX sample using the Isolated System Emulator (ISE, from now on). We're going to use the sample with SHA256 hash `B1715924A653879892E556DC0087446DF43328CAE7A1D16639A604E914C5B812`. While unpacking UPX is simple in general, this tutorial aims to help with manually unpacking binaries that use different packers; UPX is only used as a simple use case.

## Prerequisites

This tutorial assumes ISE is already configured and skips this part. For more information about how to configure ISE, see [Isolated System Emulation](/add-ons/malware/concepts/isolated-system-emulation.md#configuration).

## Set a breakpoint on the tail call

When we open the aforementioned binary in IDA it will show the single function it was able to create, `start` at 0x00430A40. Now, let's put a breakpoint in the very first instruction at the entry point and just press the "Start isolated system emulator" button as shown in the screenshot:

![Breakpoint at the entry point before starting ISE](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-23a5d82452bee5bf06b685c9705378862a9b3732%2Fupx-unpack-1.png?alt=media)

Execution will stop at that instruction. Now, let's take a look at the control flow graph for this function:

![Control flow graph of the start function](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-35fb315816677284b9147a240cce70b292c0ae59%2Fupx-unpack-2.png?alt=media)

The highlighted basic block at the bottom left is the exit node containing the jump to the Original Entry Point (OEP) in the binary. Let's put another breakpoint here and press <kbd>F9</kbd>, or **Debugger → Continue Process**, to let IDA resume execution until the breakpoint is reached. At some point it will stop, as shown in the next screenshot:

![Execution stopped at the tail jump breakpoint](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-005f9e1432e8d9b674629c23d295f86fb4617244%2Fupx-unpack-3.png?alt=media)

Now, let's press <kbd>F7</kbd>, or **Debugger → Step Into**. It will jump to the OEP as shown in the screenshot below.

![Execution at the Original Entry Point](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-c8867512a695727ea9f569617d6438fdf55f8815%2Fupx-unpack-4.png?alt=media)

Press <kbd>P</kbd> to create a function at this point, and then rename it by pressing <kbd>N</kbd> and using a new name: `OriginalEntryPoint`. Any name works, but `OriginalEntryPoint` is the default name the Generic Unpacker gives to the OEP. The next step is to take a memory snapshot so the bytes in the IDA database reflect the changes the unpacking code did in memory by doing **Debugger → Take Memory Snapshot**, selecting only "Loader Segments", and then pressing OK:

![Take memory snapshot dialog with Loader segments selected](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-95a97d844eca1f7133f79e518a2f11c0dd743a16%2Fupx-unpack-5.png?alt=media)

After this we can stop the debugger, no need to keep it running any more. At this point we have the bytes of the unpacked binary in the IDA database, but there are various other little tasks that we have to manually fix. Be sure that you are at the first instruction of the `OriginalEntryPoint` function, then go to the Python prompt in IDA and type the following:

```python
idaapi.apply_startup_sig(here(), "pe")
```

This will run the same code that IDA does when first opening a binary, trying to apply startup signatures (FLIRT) for this binary. Startup signatures do more than applying FLIRT signatures normally, which only renames the library functions they match: they also set the compiler, find the `main` function, and load the corresponding type library and library signatures. The result is shown in the next screenshot:

![Type library and compiler mismatch dialog](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-5b08d423b7ac8547487ccbd7f7982fc70986c9e8%2Fupx-unpack-6.png?alt=media)

IDA detected that the initially used type library and chosen compiler were wrong: we're dealing with a binary made with Borland C++ Builder. So, in the dialog IDA shows, first choose "Change compiler", and then, when asked again, click on "Unload library". IDA will change the compiler, load the appropriate type library, apply FLIRT signatures where appropriate, and do code analysis in some parts of the binary. After it finishes, it will show an IDA database with multiple discovered functions, some symbol names, etc., as shown in the screenshot below:

![Database after applying the startup signatures](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-208d48f36f4b70840972a7c7196d1b3dbdf8c208%2Fupx-unpack-7.png?alt=media)

There are portions of the database that were not 100% analyzed, and in order to do a second pass we are going to execute the following code in the Python prompt:

```python
idc.plan_and_wait(idc.get_inf_attr(INF_MIN_EA), idc.get_inf_attr(INF_MAX_EA))
```

This code will reanalyze the whole database, from the minimum address in the database, to the last available address. In case you are curious: no, it's not the same as the "Reanalyze Program" option, it does more things. After running it, the list of discovered functions jumps from 401 functions to 447, not bad. We can just jump to the symbol `main`, discovered by the startup signatures, and start analyzing the binary.

## Recovering the Import Address Table

One more little thing was not completely handled in the previous steps: discovering the Import Address Table (IAT). We are going to recover it to know which Windows APIs it is calling. First, let's put breakpoints in the UPX unpacker stub at 00430B5A and 00430B6F, the instructions shown below:

```nasm
UPX1:00430B56                 push    eax             ; lpLibFileName
UPX1:00430B57                 add     edi, 8
UPX1:00430B5A                 call    dword ptr [esi+300A4h]
UPX1:00430B60                 xchg    eax, ebp
UPX1:00430B61
UPX1:00430B61 loc_430B61:                             ; CODE XREF: start+13E↓j
UPX1:00430B61                 mov     al, [edi]
UPX1:00430B63                 inc     edi
UPX1:00430B64                 or      al, al
UPX1:00430B66                 jz      short loc_430B44
UPX1:00430B68                 mov     ecx, edi
UPX1:00430B6A                 push    edi
UPX1:00430B6B                 dec     eax
UPX1:00430B6C                 repne scasb
UPX1:00430B6E                 push    ebp
UPX1:00430B6F                 call    dword ptr [esi+300A8h]
UPX1:00430B75                 or      eax, eax
```

These are the functions the UPX unpacker stub uses for loading DLLs and resolving Windows APIs. After setting up the breakpoints, run ISE again by pressing the "Start isolated system emulator" button, then press <kbd>F9</kbd> to let it run until the first breakpoint is hit. The target of that function call is "LoadLibraryA" as shown below:

```nasm
pc_win32_upx.pe:004310A4 off_4310A4 dd offset kernel32_LoadLibraryA
pc_win32_upx.pe:004310A4                                         ; DATA XREF: start+11A↑r
pc_win32_upx.pe:004310A8 dd offset kernel32_GetProcAddress
pc_win32_upx.pe:004310AC dd offset kernel32_VirtualProtect
pc_win32_upx.pe:004310B0 dd offset kernel32_VirtualAlloc
pc_win32_upx.pe:004310B4 dd offset kernel32_VirtualFree
pc_win32_upx.pe:004310B8 dd offset kernel32_ExitProcess
pc_win32_upx.pe:004310BC dd 0
pc_win32_upx.pe:004310C0 dd offset user32_wsprintfA
pc_win32_upx.pe:004310C4 dd 0
```

There are other offsets to functions I manually created (by pressing <kbd>O</kbd> on each instruction) to show what is in that area. Now, we are going to select this area and then go to **File → Script file...** and in the dialog that opens we will select the script `$IDA_DIR/idc/renimp.idc` (a script to rename entries of a dynamically built import table). After running it the selected area will look like this:

```nasm
pc_win32_upx.pe:004310A4 ; HMODULE (__stdcall *LoadLibraryA)(LPCSTR lpLibFileName)
pc_win32_upx.pe:004310A4 LoadLibraryA dd offset kernel32_LoadLibraryA
pc_win32_upx.pe:004310A4                                         ; DATA XREF: start+11A↑r
pc_win32_upx.pe:004310A8 ; FARPROC (__stdcall *GetProcAddress)(HMODULE hModule, LPCSTR lpProcName)
pc_win32_upx.pe:004310A8 GetProcAddress dd offset kernel32_GetProcAddress
pc_win32_upx.pe:004310AC ; BOOL (__stdcall *VirtualProtect)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect)
pc_win32_upx.pe:004310AC VirtualProtect dd offset kernel32_VirtualProtect
pc_win32_upx.pe:004310B0 ; LPVOID (__stdcall *VirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
pc_win32_upx.pe:004310B0 VirtualAlloc dd offset kernel32_VirtualAlloc
pc_win32_upx.pe:004310B4 ; BOOL (__stdcall *VirtualFree)(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType)
pc_win32_upx.pe:004310B4 VirtualFree dd offset kernel32_VirtualFree
pc_win32_upx.pe:004310B8 ; void (__stdcall *ExitProcess)(UINT uExitCode)
pc_win32_upx.pe:004310B8 ExitProcess dd offset kernel32_ExitProcess
pc_win32_upx.pe:004310BC dd 0
pc_win32_upx.pe:004310C0 wsprintfA dd offset user32_wsprintfA
pc_win32_upx.pe:004310C4 dd 0
```

As we can see, it created offsets with proper names and function prototypes for these functions. But these are just the ones used by UPX itself to unpack the binary. Let's press <kbd>F9</kbd> to continue execution until we reach the breakpoint at 00430B6F:

```nasm
UPX1:00430B6F call    ds:(GetProcAddress - dword_401000)[esi]
UPX1:00430B75 or      eax, eax
UPX1:00430B77 jz      short loc_430B80
UPX1:00430B79 mov     [ebx], eax
UPX1:00430B7B add     ebx, 4
UPX1:00430B7E jmp     short loc_430B61
```

This is the code resolving function names and storing them where EBX points. Let's remember the value of EBX at this time (0042B12C) and let the binary run freely until it reaches the OEP. When the breakpoint is hit go to the previously mentioned address, where EBX pointed, as it will be the beginning of the IAT:

```nasm
UPX1:0042B12C unk_42B12C db 0D0h                      ; DATA XREF: sub_40D234↑r
UPX1:0042B12D db  23h ; #
UPX1:0042B12E db    0
UPX1:0042B12F db  77h ; w
UPX1:0042B130 unk_42B130 db 0F0h                      ; DATA XREF: sub_40D23A↑r
UPX1:0042B131 db  2Bh ; +
UPX1:0042B132 db    0
UPX1:0042B133 db  77h ; w
UPX1:0042B134 unk_42B134 db 0F0h                      ; DATA XREF: sub_40D240↑r
UPX1:0042B135 db  2Ch ; ,
UPX1:0042B136 db    0
UPX1:0042B137 db  77h ; w
UPX1:0042B138 unk_42B138 db 0D0h                      ; DATA XREF: sub_40D246↑r
UPX1:0042B139 db  2Fh ; /
UPX1:0042B13A db    0
UPX1:0042B13B db  77h ; w
UPX1:0042B13C unk_42B13C db 0F0h                      ; DATA XREF: sub_40D24C↑r
UPX1:0042B13D db  35h ; 5
UPX1:0042B13E db    0
UPX1:0042B13F db  77h ; w
UPX1:0042B140 unk_42B140 db  70h ; p                  ; DATA XREF: sub_40D252↑r
UPX1:0042B141 db  36h ; 6
UPX1:0042B142 db    0
UPX1:0042B143 db  77h ; w
UPX1:0042B144 unk_42B144 db  90h                      ; DATA XREF: sub_40D258↑r
UPX1:0042B145 db  3Ah ; :
UPX1:0042B146 db    0
UPX1:0042B147 db  77h ; w
```

As in the previous case, we need to create an offset for every instruction, by going to each one and pressing <kbd>O</kbd>, and then run the renimp.idc script. After manually doing all of this, it will look like this:

```nasm
UPX1:0042B12C ; BOOL (__stdcall *CloseHandle)(HANDLE hObject)
UPX1:0042B12C CloseHandle dd offset kernel32_CloseHandle
UPX1:0042B12C                                         ; DATA XREF: j_CloseHandle↑r
UPX1:0042B130 ; HANDLE (__stdcall *CreateFileA)(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
UPX1:0042B130 CreateFileA dd offset kernel32_CreateFileA
UPX1:0042B130                                         ; DATA XREF: j_CreateFileA↑r
UPX1:0042B134 ; HANDLE (__stdcall *CreateFileW)(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
UPX1:0042B134 CreateFileW dd offset kernel32_CreateFileW
UPX1:0042B134                                         ; DATA XREF: j_CreateFileW↑r
UPX1:0042B138 ; BOOL (__stdcall *CreateProcessA)(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
UPX1:0042B138 CreateProcessA dd offset kernel32_CreateProcessA
UPX1:0042B138                                         ; DATA XREF: j_CreateProcessA↑r
UPX1:0042B13C ; void (__stdcall *DeleteCriticalSection)(LPCRITICAL_SECTION lpCriticalSection)
UPX1:0042B13C DeleteCriticalSection dd offset kernel32_DeleteCriticalSection
UPX1:0042B13C                                         ; DATA XREF: j_DeleteCriticalSection↑r
UPX1:0042B140 ; BOOL (__stdcall *DeleteFileA)(LPCSTR lpFileName)
UPX1:0042B140 DeleteFileA dd offset kernel32_DeleteFileA
UPX1:0042B140                                         ; DATA XREF: j_DeleteFileA↑r
UPX1:0042B144 ; void (__stdcall *EnterCriticalSection)(LPCRITICAL_SECTION lpCriticalSection)
UPX1:0042B144 EnterCriticalSection dd offset kernel32_EnterCriticalSection
UPX1:0042B144                                         ; DATA XREF: j_EnterCriticalSection↑r
UPX1:0042B148 ; void (__stdcall *ExitProcess_0)(UINT uExitCode)
UPX1:0042B148 ExitProcess_0 dd offset kernel32_ExitProcess
UPX1:0042B148                                         ; DATA XREF: j_ExitProcess_0↑r
UPX1:0042B14C ; UINT (__stdcall *GetACP)()
UPX1:0042B14C GetACP dd offset kernel32_GetACP        ; DATA XREF: j_GetACP↑r
UPX1:0042B150 ; BOOL (__stdcall *GetCPInfo)(UINT CodePage, LPCPINFO lpCPInfo)
UPX1:0042B150 GetCPInfo dd offset kernel32_GetCPInfo  ; DATA XREF: j_GetCPInfo↑r
UPX1:0042B154 ; LPSTR (__stdcall *GetCommandLineA)()
UPX1:0042B154 GetCommandLineA dd offset kernel32_GetCommandLineA
UPX1:0042B154                                         ; DATA XREF: j_GetCommandLineA↑r
UPX1:0042B158 ; DWORD (__stdcall *GetCurrentDirectoryA)(DWORD nBufferLength, LPSTR lpBuffer)
UPX1:0042B158 GetCurrentDirectoryA dd offset kernel32_GetCurrentDirectoryA
UPX1:0042B158                                         ; DATA XREF: j_GetCurrentDirectoryA↑r
UPX1:0042B15C ; DWORD (__stdcall *GetCurrentThreadId)()
UPX1:0042B15C GetCurrentThreadId dd offset kernel32_GetCurrentThreadId
UPX1:0042B15C                                         ; DATA XREF: j_GetCurrentThreadId↑r
UPX1:0042B160 ; UINT (__stdcall *GetDriveTypeA)(LPCSTR lpRootPathName)
UPX1:0042B160 GetDriveTypeA dd offset kernel32_GetDriveTypeA
UPX1:0042B160                                         ; DATA XREF: j_GetDriveTypeA↑r
UPX1:0042B164 ; LPSTR (__stdcall *GetEnvironmentStrings)()
UPX1:0042B164 GetEnvironmentStrings dd offset kernel32_GetEnvironmentStrings
UPX1:0042B164                                         ; DATA XREF: j_GetEnvironmentStrings↑r
UPX1:0042B168 ; BOOL (__stdcall *GetExitCodeProcess)(HANDLE hProcess, LPDWORD lpExitCode)
UPX1:0042B168 GetExitCodeProcess dd offset kernel32_GetExitCodeProcess
UPX1:0042B168                                         ; DATA XREF: j_GetExitCodeProcess↑r
UPX1:0042B16C ; DWORD (__stdcall *GetFileAttributesA)(LPCSTR lpFileName)
UPX1:0042B16C GetFileAttributesA dd offset kernel32_GetFileAttributesA
UPX1:0042B16C                                         ; DATA XREF: j_GetFileAttributesA↑r
UPX1:0042B170 ; DWORD (__stdcall *GetFileAttributesW)(LPCWSTR lpFileName)
UPX1:0042B170 GetFileAttributesW dd offset kernel32_GetFileAttributesW
UPX1:0042B170                                         ; DATA XREF: j_GetFileAttributesW↑r
UPX1:0042B174 ; DWORD (__stdcall *GetFileType)(HANDLE hFile)
UPX1:0042B174 GetFileType dd offset kernel32_GetFileType
UPX1:0042B174                                         ; DATA XREF: j_GetFileType↑r
UPX1:0042B178 ; DWORD (__stdcall *GetFullPathNameA)(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer, LPSTR *lpFilePart)
UPX1:0042B178 GetFullPathNameA dd offset kernel32_GetFullPathNameA
UPX1:0042B178                                         ; DATA XREF: j_GetFullPathNameA↑r
UPX1:0042B17C ; DWORD (__stdcall *GetLastError)()
UPX1:0042B17C GetLastError dd offset kernel32_GetLastError
UPX1:0042B17C                                         ; DATA XREF: j_GetLastError↑r
UPX1:0042B180 ; void (__stdcall *GetLocalTime)(LPSYSTEMTIME lpSystemTime)
UPX1:0042B180 GetLocalTime dd offset kernel32_GetLocalTime
UPX1:0042B180                                         ; DATA XREF: j_GetLocalTime↑r
UPX1:0042B184 ; DWORD (__stdcall *GetLogicalDrives)()
UPX1:0042B184 GetLogicalDrives dd offset kernel32_GetLogicalDrives
UPX1:0042B184                                         ; DATA XREF: j_GetLogicalDrives↑r
UPX1:0042B188 ; DWORD (__stdcall *GetModuleFileNameA)(HMODULE hModule, LPSTR lpFilename, DWORD nSize)
UPX1:0042B188 GetModuleFileNameA dd offset kernel32_GetModuleFileNameA
UPX1:0042B188                                         ; DATA XREF: j_GetModuleFileNameA↑r
UPX1:0042B18C ; HMODULE (__stdcall *GetModuleHandleA)(LPCSTR lpModuleName)
UPX1:0042B18C GetModuleHandleA dd offset kernel32_GetModuleHandleA
UPX1:0042B18C                                         ; DATA XREF: j_GetModuleHandleA↑r
UPX1:0042B190 ; UINT (__stdcall *GetOEMCP)()
UPX1:0042B190 GetOEMCP dd offset kernel32_GetOEMCP    ; DATA XREF: j_GetOEMCP↑r
UPX1:0042B194 ; FARPROC (__stdcall *GetProcAddress_0)(HMODULE hModule, LPCSTR lpProcName)
UPX1:0042B194 GetProcAddress_0 dd offset kernel32_GetProcAddress
UPX1:0042B194                                         ; DATA XREF: j_GetProcAddress_0↑r
UPX1:0042B198 ; HANDLE (__stdcall *GetProcessHeap)()
UPX1:0042B198 GetProcessHeap dd offset kernel32_GetProcessHeap
UPX1:0042B198                                         ; DATA XREF: j_GetProcessHeap↑r
UPX1:0042B19C ; DWORD (__stdcall *GetShortPathNameA)(LPCSTR lpszLongPath, LPSTR lpszShortPath, DWORD cchBuffer)
UPX1:0042B19C GetShortPathNameA dd offset kernel32_GetShortPathNameA
UPX1:0042B19C                                         ; DATA XREF: j_GetShortPathNameA↑r
UPX1:0042B1A0 ; void (__stdcall *GetStartupInfoA)(LPSTARTUPINFOA lpStartupInfo)
UPX1:0042B1A0 GetStartupInfoA dd offset kernel32_GetStartupInfoA
UPX1:0042B1A0                                         ; DATA XREF: j_GetStartupInfoA↑r
UPX1:0042B1A4 ; HANDLE (__stdcall *GetStdHandle)(DWORD nStdHandle)
UPX1:0042B1A4 GetStdHandle dd offset kernel32_GetStdHandle
UPX1:0042B1A4                                         ; DATA XREF: j_GetStdHandle↑r
UPX1:0042B1A8 ; BOOL (__stdcall *GetStringTypeW)(DWORD dwInfoType, LPCWSTR lpSrcStr, int cchSrc, LPWORD lpCharType)
UPX1:0042B1A8 GetStringTypeW dd offset kernel32_GetStringTypeW
UPX1:0042B1A8                                         ; DATA XREF: j_GetStringTypeW↑r
UPX1:0042B1AC ; DWORD (__stdcall *GetVersion)()
UPX1:0042B1AC GetVersion dd offset kernel32_GetVersion ; DATA XREF: j_GetVersion↑r
UPX1:0042B1B0 ; BOOL (__stdcall *GetVersionExA)(LPOSVERSIONINFOA lpVersionInformation)
UPX1:0042B1B0 GetVersionExA dd offset kernel32_GetVersionExA
UPX1:0042B1B0                                         ; DATA XREF: j_GetVersionExA↑r
UPX1:0042B1B4 ; void (__stdcall *GlobalMemoryStatus)(LPMEMORYSTATUS lpBuffer)
UPX1:0042B1B4 GlobalMemoryStatus dd offset kernel32_GlobalMemoryStatus
UPX1:0042B1B4                                         ; DATA XREF: j_GlobalMemoryStatus↑r
UPX1:0042B1B8 ; LPVOID (__stdcall *HeapAlloc)(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes)
UPX1:0042B1B8 HeapAlloc dd offset kernel32_HeapAlloc  ; DATA XREF: j_HeapAlloc↑r
UPX1:0042B1BC ; BOOL (__stdcall *HeapFree)(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem)
UPX1:0042B1BC HeapFree dd offset kernel32_HeapFree    ; DATA XREF: j_HeapFree↑r
UPX1:0042B1C0 ; void (__stdcall *InitializeCriticalSection)(LPCRITICAL_SECTION lpCriticalSection)
UPX1:0042B1C0 InitializeCriticalSection dd offset kernel32_InitializeCriticalSection
UPX1:0042B1C0                                         ; DATA XREF: j_InitializeCriticalSection↑r
UPX1:0042B1C4 ; int (__stdcall *LCMapStringA)(LCID Locale, DWORD dwMapFlags, LPCSTR lpSrcStr, int cchSrc, LPSTR lpDestStr, int cchDest)
UPX1:0042B1C4 LCMapStringA dd offset kernel32_LCMapStringA
UPX1:0042B1C4                                         ; DATA XREF: j_LCMapStringA↑r
UPX1:0042B1C8 ; void (__stdcall *LeaveCriticalSection)(LPCRITICAL_SECTION lpCriticalSection)
UPX1:0042B1C8 LeaveCriticalSection dd offset kernel32_LeaveCriticalSection
UPX1:0042B1C8                                         ; DATA XREF: j_LeaveCriticalSection↑r
UPX1:0042B1CC ; HMODULE (__stdcall *LoadLibraryA_0)(LPCSTR lpLibFileName)
UPX1:0042B1CC LoadLibraryA_0 dd offset kernel32_LoadLibraryA
UPX1:0042B1CC                                         ; DATA XREF: j_LoadLibraryA_0↑r
UPX1:0042B1D0 ; int (__stdcall *MultiByteToWideChar)(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar)
UPX1:0042B1D0 MultiByteToWideChar dd offset kernel32_MultiByteToWideChar
UPX1:0042B1D0                                         ; DATA XREF: j_MultiByteToWideChar↑r
UPX1:0042B1D4 ; void (__stdcall *RaiseException)(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR *lpArguments)
UPX1:0042B1D4 RaiseException dd offset kernel32_RaiseException
UPX1:0042B1D4                                         ; DATA XREF: j_RaiseException↑r
UPX1:0042B1D8 ; BOOL (__stdcall *ReadFile)(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
UPX1:0042B1D8 ReadFile dd offset kernel32_ReadFile    ; DATA XREF: j_ReadFile↑r
UPX1:0042B1DC RtlUnwind dd offset kernel32_RtlUnwind  ; DATA XREF: j_RtlUnwind↑r
UPX1:0042B1E0 ; BOOL (__stdcall *SetConsoleCtrlHandler)(PHANDLER_ROUTINE HandlerRoutine, BOOL Add)
UPX1:0042B1E0 SetConsoleCtrlHandler dd offset kernel32_SetConsoleCtrlHandler
UPX1:0042B1E0                                         ; DATA XREF: j_SetConsoleCtrlHandler↑r
UPX1:0042B1E4 ; DWORD (__stdcall *SetFilePointer)(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod)
UPX1:0042B1E4 SetFilePointer dd offset kernel32_SetFilePointer
UPX1:0042B1E4                                         ; DATA XREF: j_SetFilePointer↑r
UPX1:0042B1E8 ; UINT (__stdcall *SetHandleCount)(UINT uNumber)
UPX1:0042B1E8 SetHandleCount dd offset kernel32_SetHandleCount
UPX1:0042B1E8                                         ; DATA XREF: j_SetHandleCount↑r
UPX1:0042B1EC ; void (__stdcall *SetLastError)(DWORD dwErrCode)
UPX1:0042B1EC SetLastError dd offset kernel32_SetLastError
UPX1:0042B1EC                                         ; DATA XREF: j_SetLastError↑r
UPX1:0042B1F0 ; DWORD (__stdcall *TlsAlloc)()
UPX1:0042B1F0 TlsAlloc dd offset kernel32_TlsAlloc    ; DATA XREF: j_TlsAlloc↑r
UPX1:0042B1F4 ; BOOL (__stdcall *TlsFree)(DWORD dwTlsIndex)
UPX1:0042B1F4 TlsFree dd offset kernel32_TlsFree      ; DATA XREF: j_TlsFree↑r
UPX1:0042B1F8 ; LPVOID (__stdcall *TlsGetValue)(DWORD dwTlsIndex)
UPX1:0042B1F8 TlsGetValue dd offset kernel32_TlsGetValue
UPX1:0042B1F8                                         ; DATA XREF: j_TlsGetValue↑r
UPX1:0042B1FC ; BOOL (__stdcall *TlsSetValue)(DWORD dwTlsIndex, LPVOID lpTlsValue)
UPX1:0042B1FC TlsSetValue dd offset kernel32_TlsSetValue
UPX1:0042B1FC                                         ; DATA XREF: j_TlsSetValue↑r
UPX1:0042B200 ; LPVOID (__stdcall *VirtualAlloc_0)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
UPX1:0042B200 VirtualAlloc_0 dd offset kernel32_VirtualAlloc
UPX1:0042B200                                         ; DATA XREF: j_VirtualAlloc_0↑r
UPX1:0042B204 ; BOOL (__stdcall *VirtualFree_0)(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType)
UPX1:0042B204 VirtualFree_0 dd offset kernel32_VirtualFree
UPX1:0042B204                                         ; DATA XREF: j_VirtualFree_0↑r
UPX1:0042B208 ; DWORD (__stdcall *VirtualQuery)(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, DWORD dwLength)
UPX1:0042B208 VirtualQuery dd offset kernel32_VirtualQuery
UPX1:0042B208                                         ; DATA XREF: j_VirtualQuery↑r
UPX1:0042B20C ; DWORD (__stdcall *WaitForSingleObject)(HANDLE hHandle, DWORD dwMilliseconds)
UPX1:0042B20C WaitForSingleObject dd offset kernel32_WaitForSingleObject
UPX1:0042B20C                                         ; DATA XREF: j_WaitForSingleObject↑r
UPX1:0042B210 ; int (__stdcall *WideCharToMultiByte)(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
UPX1:0042B210 WideCharToMultiByte dd offset kernel32_WideCharToMultiByte
UPX1:0042B210                                         ; DATA XREF: j_WideCharToMultiByte↑r
UPX1:0042B214 ; BOOL (__stdcall *WriteFile)(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
UPX1:0042B214 WriteFile dd offset kernel32_WriteFile  ; DATA XREF: j_WriteFile↑r
UPX1:0042B218 dd 0
UPX1:0042B21C dd 0
UPX1:0042B220 dd 0
UPX1:0042B224 dd 0
UPX1:0042B228 dd 0
UPX1:0042B22C ; BOOL (__stdcall *EnumThreadWindows)(DWORD dwThreadId, WNDENUMPROC lpfn, LPARAM lParam)
UPX1:0042B22C EnumThreadWindows dd offset user32_EnumThreadWindows
UPX1:0042B22C                                         ; DATA XREF: j_EnumThreadWindows↑r
UPX1:0042B230 ; int (__stdcall *MessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
UPX1:0042B230 MessageBoxA dd offset user32_MessageBoxA
UPX1:0042B230                                         ; DATA XREF: j_MessageBoxA↑r
UPX1:0042B234 wsprintfA_0 dd offset user32_wsprintfA  ; DATA XREF: j_wsprintfA_0↑r
UPX1:0042B238 dd 0
```

Now, last but not least, select **Debugger → Take Memory Snapshot**, choosing only "Loader Segments", so our changes are saved into the IDA database *before stopping the debugger*. After taking the memory snapshot it's safe to stop it.

## Let the Generic Unpacker finish a manual unpack

Most of the work after reaching the OEP, like taking the memory snapshot, rebuilding the IAT and reanalyzing the database, is the same for every packer. The Generic Unpacker has a heuristic, `already-unpacked`, that does not unpack anything and runs only these steps. It is useful for packers the Generic Unpacker cannot unpack by itself: you do the packer-specific part by hand, and let the Generic Unpacker do the rest.

To use it, follow the steps in [Set a breakpoint on the tail call](#set-a-breakpoint-on-the-tail-call) until you reach the OEP with <kbd>F7</kbd>, and do not stop the debugger. Then press <kbd>Shift</kbd> + <kbd>P</kbd>, or choose **Extensions → Malware Analysis → Unpacking → Unpack now\...**, and select `already-unpacked` in the heuristic dialog. The Generic Unpacker uses the running debugger session: it finds the IAT and renames the API offsets, takes the memory snapshot, reanalyzes the whole database and looks for the original entry point with the startup signatures. If you already renamed the OEP function, it keeps your name.

## Compare with the automated unpacker

Manually unpacking packed samples, while possible, is a bit cumbersome as there are multiple little steps that must be followed. The Generic Unpacker, part of the Malware Analysis add-on, handles the steps mentioned before, and much more, automatically. Let's reopen the binary and start from scratch. After IDA's auto-analysis is done, the Generic Unpacker, if configured to do so, will ask us if we want to unpack the binary, as it looks packed:

![Generic Unpacker asking to unpack the binary](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-545bf4bdd7a47fcf0a63ab7231fe1dbab43a0296%2Fupx-unpack-8.png?alt=media)

We can choose "Yes" here or, alternatively, we can say "No" and run the Generic Unpacker later by pressing its shortcut, <kbd>Shift</kbd> + <kbd>P</kbd>, or choosing **Extensions → Malware Analysis → Unpacking → Unpack now\...**. When the Generic Unpacker is executed it will ask for confirmation if we really want to run it, as it will destroy almost everything in this database; we can safely choose "Yes" in this case, and then another dialog will be shown:

![Choose unpacking heuristic dialog with run-to-exit-point selected](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-5c31aca7d3fc526a96e4cb3b929c74e778560ac6%2Fupx-unpack-9.png?alt=media)

In this dialog we can choose which heuristic we want to use for the binary to unpack. In general, `run-until-timeout` is a good default heuristic, but the Generic Unpacker detected that this packer might benefit from another heuristic, `run-to-exit-point`, and offers it by default. Indeed, for UPX it is the best heuristic we can choose. Now, just press OK and wait. At some point the Generic Unpacker will finish its work and will show a database like the following one:

![Database after running the Generic Unpacker](https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-492a8f320268db5346418eed59cc0bdb38d2eadd%2Fupx-unpack-10.png?alt=media)

Everything that we did before manually is automatically done by the Generic Unpacker: it discovers, renames, and adds to the entry points list the OEP; discovers the IAT, applies offsets and renames entries; it also reanalyzes the whole database, forces function creation whenever it decides it is appropriate; applies FLIRT signatures, etc.

It's simpler to use the Generic Unpacker than to do it by hand, but IDA lets you do it manually as well, and now you know how to do it either way.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.hex-rays.com/add-ons/malware/how-tos/manually-unpack-upx.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
