Step-by-Step Unpacking: PECompact, OEP, Dump and IAT Reconstruction

Author: Diego J D Arias · Platform: Windows · Executable: UnPackme01.exe (32-bit) Tools: x64dbg (x32dbg version) · Detect It Easy v3.21 · PE-bear v0.7.2 · Scylla


This time we unpack a 32-bit executable protected with PECompact. We are going to do four things: understand why the packer behaves the way it does, reach the real OEP, rebuild the IAT that the packer had redirected, and dump the unpacked executable.

Important: Always work in an isolated virtual machine (host-only or disconnected network) with a snapshot that includes memory.


First of all, look at the executable before debugging it

Before opening the debugger, we look at the file with two tools: Detect It Easy (DIE) and PE-bear. We don’t run anything, but these two screenshots already tell us a great deal. Let’s read them carefully, because almost the whole unpacking plan comes from here.

Detect It Easy: who the packer is and what’s underneath

Detect It Easy: showing PECompact, Borland Delphi and the packing heuristics Program window showing the success message

Line by line:

  • PE32, Windows 32-bit, GUI. It is a 32-bit executable with a graphical interface. That is why we use x32dbg (not the 64-bit version) and, since it is a GUI, we expect to find a DialogProc or a WndProc later on.
  • Packer: PECompact (3.02.2 or 3.03.21 beta). DIE recognizes the packer by signature and even gives us an approximate version. It is the most valuable piece of information in the screenshot: we already know what we are up against. PECompact does not virtualize code: a stub decompresses the program in memory and ends with a jump to the original code. So we know what to look for: the stub, the decompression and that final jump. The unknown magic is a header value that DIE can’t associate with a specific version, which is why it gives two possibilities. It doesn’t change the method.
  • Linker: Turbo Linker 2.25 / Compiler: Borland Delphi / Language: Object Pascal. Careful, this refers to the original program, not the packer. DIE deduces it from header data that the packer left intact (for example, the linker version). It is very useful information: we already know there is a Delphi program underneath without having unpacked anything. That means we know what its startup usually looks like (a prologue with push ebp / mov ebp,esp / add esp,-N, register calling convention), and it will help us recognize the OEP when we get there. It also fits the MZP mark we saw at offset 0, typical of Borland executables.
  • (Heur) Protection: Unreadable resources. DIE tries to read the executable’s resources and can’t. In a normal program, .rsrc holds icons, dialogs and strings. Here the real resources are not in plain sight: most likely the packer compresses them and restores them later, and that section is occupied by something else (we’ll confirm this shortly).
  • (Heur) Protection: Section #0 (“CODE”) has RWX. The code section is readable, writable and executable all at once. Normal code is read and execute only. The fact that it is writable indicates that this code is going to rewrite itself, which is exactly what a packer does when it decompresses.
  • (Heur) Packer: Generic [EntryPoint + Imports like PECompact + Section #0 (“CODE”) compressed + PE in resources + High entropy]. DIE reaches the same conclusion by another route, without using the signature: it looks at the entry point, the imports, the compression of CODE, the entropy and data shaped like an executable inside the resources. It is an independent second opinion. And that last point fits what we are about to see: the packer’s stub and compressed data live in .rsrc.

Tip: when the signature and the heuristics agree, you can trust the diagnosis. If DIE only showed a heuristic and no signature, there would be more reason to doubt: it could be an uncommon packer or a modified version.

PE-bear: the sections, where the packer is visible at a glance

PE-bear: showing the executable’s two sections: CODE and .rsrc Program window showing the success message

The section table is the most telling part. Look at these points:

1. There are only two sections. A normal Delphi executable usually has several more (DATA, BSS, .idata, .reloc…). Here the packer merged everything and there is no .idata in sight: the real imports are not where we would expect them, and that tells us in advance that we will have to rebuild the IAT. 2. CODE takes up much more space in memory than on disk.

SectionVAVirtual SizeRaw SizeEntropy
CODE0x10000x5B0000x294008
.rsrc0x5C0000x30000x28005.62

What this table tells us:

  • There are only two sections. There is no .data, .idata or .reloc. That is typical of a packer: the true imports are not in plain sight.
  • CODE has entropy 8, that is, it is completely compressed. It also takes up much more in memory 0x5B000 than on disk 0x29400: it decompresses over itself when it runs.
  • .rsrc is marked as executable ERW and has entropy 5.62. Part of the packer’s stub lives there, uncompressed.
  • The entry point falls inside CODE, the compressed section. That is, the startup can’t be normal code.
  • At offset 0, PE-bear shows MZP instead of MZ. We note it as a curiosity: Delphi/Borland executables also tend to carry that mark.

Tip: an entry point inside an entropy-8 section is almost a confirmation that a stub is decompressing something. The question becomes where that stub ends.

The Windows loader reserves 364 KB for CODE, but the file only has 165 KB. That difference is the space where the real program is going to appear when the packer decompresses it. Add the entropy of 8 that DIE measures for that section, and the conclusion is straightforward: the real code is not in the file, it is generated in memory.

3. Both sections have Characteristics = E0000020. Decoded:

BitMeaning
0x00000020Contains code
0x20000000Executable
0x40000000Readable
0x80000000Writable

For CODE we already expected it: it has to be writable in order to decompress over itself. But the odd one is .rsrc. A normal resources section is just readable data (40000040). The fact that here it is executable and writable gives away that it contains code: it is the packer’s stub. With its intermediate entropy of 5.62, we can see that it mixes uncompressed code and data.

4. The Ptr to Reloc. field of CODE is 32434550. That column should be a pointer to relocations, and it makes no sense here: Num. of Reloc. is 0. It is a text value in disguise. If we read it in hexadecimal, 0x32434550 in little-endian is the bytes 50 45 43 32, which in ASCII spell PEC2. It is the mark PECompact leaves in the header, in a field the loader ignores. It is one more confirmation of the packer, this time by hand.

5. The entry point falls inside CODE. This piece of data comes from the optional header (it isn’t visible in the screenshot): the entry point is 0x40F844, as we will see a bit later, that is RVA 0xF844, which lies within the range of CODE (0x1000 to 0x5C000). If the startup were in a normal code section, it wouldn’t be unusual. But there the section is compressed, so the startup can’t be the original program: it has to be the stub.


Looking for the OEP

The textbook way to start is to place a memory execution breakpoint on 401000 and wait for the program to jump into it.

It doesn’t work here. Since the entry point is inside CODE, the breakpoint fires on the very first instruction, without distinguishing between the packer’s stub and the original code.


The entry point and the SEH trick

We open the sample in x32dbg and look at the first instructions of the entry point:

[ ASM ][Copy]
0040F844 | B8 ECE64500       | mov eax,UnPackme01.45E6EC                     |
0040F849 | 50                | push eax                                      |
0040F84A | 64:FF35 00000000  | push dword ptr fs:[0]                         |
0040F851 | 64:8925 00000000  | mov dword ptr fs:[0],esp                      | installs its own SEH
0040F858 | 33C0              | xor eax,eax                                   |
0040F85A | 8908              | mov dword ptr ds:[eax],ecx                    | write to address 0 -> access violation
0040F85C | 50                | push eax                                      |
0040F85D | 45                | inc ebp                                       |
0040F85E | 43                | inc ebx                                       |
0040F85F | 6F                | outsd                                         |
0040F860 | 6D                | insd                                          |

Let’s read this carefully, because it is the heart of the packer:

  1. The first three instructions set up our own exception handler (SEH): they push the handler’s address 45E6EC and hook it into fs:[0].
  2. xor eax,eax sets eax = 0 and mov [eax],ecx tries to write to address 0. That deliberately triggers an access violation.
  3. Everything that comes after (outsd, insd, arpl…) is junk that is never executed linearly. It is there to break static analysis: IDA doesn’t follow the real flow and the listing looks like meaningless code. The handler lives at 0x45E6EC, inside .rsrc. If we read it, we see that it:
  • overwrites the instruction that failed 40F85A with a jmp rel32 to 0x45E70F,
  • and returns ExceptionContinueExecution. Windows resumes execution at the same address, 40F85A, which is no longer a write but a jump into the stub. It is self-modifying code triggered by an intentional exception.

Tip: when you see fs:[0] modified at the beginning of a program, be suspicious. At an entry point, a custom SEH plus an instruction that is guaranteed to fail is usually the packer’s mechanism for transferring control.


Letting the exception through without losing control

We want to get through the exception and follow the packer’s flow, not ntdll’s. To do that, we do the following:

  1. We place a hardware execution breakpoint on the handler: bphws 45E6EC,x.
  2. We run with F9. The debugger stops at the access violation at 40F85A.
  3. We pass the exception to the program with Shift+F9. The hardware breakpoint fires at 45E6EC.
  4. We place a normal breakpoint at 45E70F, the real start of the stub, and continue with F9. With this we skip all of ntdll’s machinery and land right at the beginning of the stub.

Tip: use hardware breakpoints whenever you can. They don’t modify memory, so a packer that scans its own code looking for 0xCC bytes won’t detect them.


How to Reach the OEP

If we scroll down a little, we can see how the stub restores the original registers with a series of pop instructions and ends with a computed jump: jmp eax at 0x45E7AE. It is the tail jump, the final jump to the original code, and almost every packer ends with one.

[ ASM ][Copy]
0045E79A | 03CA              | add ecx,edx                                   | ecx:EntryPoint, edx:EntryPoint
0045E79C | 68 00800000       | push 8000                                     |
0045E7A1 | 6A 00             | push 0                                        |
0045E7A3 | 57                | push edi                                      | edi:EntryPoint
0045E7A4 | FF11              | call dword ptr ds:[ecx]                       | ecx:EntryPoint
0045E7A6 | 8BC6              | mov eax,esi                                   | eax:EntryPoint, esi:EntryPoint
0045E7A8 | 5A                | pop edx                                       | edx:EntryPoint
0045E7A9 | 5E                | pop esi                                       | esi:EntryPoint
0045E7AA | 5F                | pop edi                                       | edi:EntryPoint
0045E7AB | 59                | pop ecx                                       | ecx:EntryPoint
0045E7AC | 5B                | pop ebx                                       |
0045E7AD | 5D                | pop ebp                                       |
0045E7AE | FFE0              | jmp eax                                       | eax:EntryPoint

We place a hardware breakpoint there bphws 45E7AE,x, run, and look at eax:

eax = 0x40F844

It is the same address where the packer started. What happened is that it restored the program’s original bytes at 40F844, overwriting the fake startup. We take a step (F7) and we are at the OEP.

To confirm it, we look at the code that appeared:

[ ASM ][Copy]
0040F844 | 55                | push ebp                                      |
0040F845 | 8BEC              | mov ebp,esp                                   |
0040F847 | 83C4 F0           | add esp,FFFFFFF0                              |
0040F84A | 53                | push ebx                                      |
0040F84B | B8 74F74000       | mov eax,UnPackme01.40F774                     |
0040F850 | E8 A35AFFFF       | call UnPackme01.4052F8                        |

Now there is a coherent prologue (push ebp / mov ebp,esp / add esp,-10h / push ebx), typical of a program made with Delphi/Borland, and clear strings. We are in the real code.

Tip: a quick way to confirm an OEP is to check whether the code makes sense and whether readable strings from the program show up in the memory dump. A standard compiler prologue is a very good sign.


Rebuilding the IAT

Reaching the OEP is not enough. If we dump the process now, the resulting executable won’t work, because its import table (IAT) doesn’t point to the real APIs.

Let’s look at the problem in Scylla

With the process halted at the OEP, we open Scylla from x32dbg, check that the OEP is 0x40F844 and enter the data manually, the VA and the Size. To find those values, let’s look at the IAT in the x32dbg dump.

[ DUMP x32dbg ][Copy]
0043B190  00000000  ....
0043B194  76A7C7F0  ðǧv  user32.GetKeyboardType
0043B198  76A11870  p.¡v  user32.LoadStringA
0043B19C  76A6D010  .Цv  user32.MessageBoxA
0043B1A0  76A0FFF0  ðÿ v  user32.CharNextA
0043B1A4  00000000  ....
0043B1A8  7674F1A0   ñtv  advapi32.RegQueryValueExA
0043B1AC  7674F360  `ótv  advapi32.RegOpenKeyExA
0043B1B0  7674F270  pòtv  advapi32.RegCloseKey
0043B1B4  00000000  ....
0043B1B8  772BF450  Pô+w  oleaut32.SysFreeString
0043B1BC  772C3D10  .=,w  oleaut32.SysReAllocStringLen
0043B1C0  772C0200  ..,w  oleaut32.SysAllocStringLen
0043B1C4  00000000  ....
0043B1C8  022A0118  ..*.
0043B1CC  022A011F  ..*.
0043B1D0  022A0126  &.*.
0043B1D4  022A012D  -.*.
0043B1D8  00000000  ....
0043B1DC  022A0134  4.*.
0043B1E0  022A013B  ;.*.
0043B1E4  022A0142  B.*.
0043B1E8  022A0149  I.*.
0043B1EC  022A0150  P.*.
0043B1F0  022A0157  W.*.
0043B1F4  022A015E  ^.*.
0043B1F8  022A0165  e.*.
0043B1FC  022A016C  l.*.
0043B200  022A0173  s.*.
0043B204  022A017A  z.*.
0043B208  022A0181  ..*.
0043B20C  022A0188  ..*.
0043B210  022A018F  ..*.
0043B214  022A0196  ..*.
0043B218  022A019D  ..*.
0043B21C  022A01A4  ¤.*.
0043B220  022A01AB  «.*.
0043B224  022A01B2  ².*.
0043B228  022A01B9  ¹.*.
0043B22C  022A01C0  À.*.
0043B230  022A01C7  Ç.*.
0043B234  022A01CE  Î.*.
0043B238  022A01D5  Õ.*.
0043B23C  022A01DC  Ü.*.
0043B240  022A01E3  ã.*.
0043B244  022A01EA  ê.*.
0043B248  022A01F1  ñ.*.
0043B24C  022A01F8  ø.*.
0043B250  022A01FF  ÿ.*.
0043B254  022A0206  ..*.
0043B258  022A020D  ..*.
0043B25C  022A0214  ..*.
0043B260  022A021B  ..*.
0043B264  022A0222  ".*.
0043B268  022A0229  ).*.
0043B26C  022A0230  0.*.
0043B270  022A0237  7.*.
0043B274  00000000  ....
0043B278  76F35170  pQóv  gdi32.TextOutA
0043B27C  76F33E10  .>óv  gdi32.StretchBlt
0043B280  76F36E90  .nóv  gdi32.SetTextColor
0043B284  76F33F50  P?óv  gdi32.SetMapMode
0043B288  76F36FC0  Àoóv  gdi32.SetBkMode
0043B28C  76F36F00  .oóv  gdi32.SetBkColor
0043B290  76F36CA0   lóv  gdi32.SelectObject
0043B294  76F346D0  ÐFóv  gdi32.Polygon
0043B298  76F33DD0  Ð=óv  gdi32.PatBlt
0043B29C  76F35230  0Róv  gdi32.GetTextExtentPoint32A
0043B2A0  76F35F50  P_óv  gdi32.GetStockObject
0043B2A4  76F33EA0   >óv  gdi32.GetObjectA
0043B2A8  76F35880  .Xóv  gdi32.DeleteObject
0043B2AC  76F36890  .hóv  gdi32.DeleteDC
0043B2B0  76F37440  @tóv  gdi32.CreateSolidBrush
0043B2B4  76F352A0   Róv  gdi32.CreateFontA
0043B2B8  76F35E80  .^óv  gdi32.CreateDIBSection
0043B2BC  76F36D90  .móv  gdi32.CreateCompatibleDC
0043B2C0  76F37070  ppóv  gdi32.CreateCompatibleBitmap
0043B2C4  76F37400  .tóv  gdi32.CreateBitmap
0043B2C8  76F36DE0  àmóv  gdi32.BitBlt
0043B2CC  00000000  ....
0043B2D0  6E4C1390  ..Ln  msimg32.AlphaBlend
0043B2D4  00000000  ....
0043B2D8  76A14C20   L¡v  user32.CreateWindowExA
0043B2DC  76A20F90  ..¢v  user32.UpdateWindow
0043B2E0  76A2F0B0  °ð¢v  user32.ShowWindow
0043B2E4  76A13720   7¡v  user32.SetWindowTextA
0043B2E8  76A2F050  Pð¢v  user32.SetWindowPos
0043B2EC  76A147C0  ÀG¡v  user32.SetWindowLongA
0043B2F0  76A2B590  .µ¢v  user32.SetTimer
0043B2F4  76A136F0  ð6¡v  user32.SetDlgItemTextA
0043B2F8  76A2D93C  <Ù¢v  user32.SetCursor
0043B2FC  76A10970  p.¡v  user32.SendMessageA
0043B300  76A10930  0.¡v  user32.SendDlgItemMessageA
0043B304  76A2AFB0  °¯¢v  user32.ReleaseDC
0043B308  76A15820   X¡v  user32.ReleaseCapture
0043B30C  76A2D2A0   Ò¢v  user32.PostQuitMessage
0043B310  76A6D010  .Цv  user32.MessageBoxA
0043B314  76A11870  p.¡v  user32.LoadStringA
0043B318  76A16350  Pc¡v  user32.LoadIconA
0043B31C  76A2CD60  `Í¢v  user32.LoadCursorA
0043B320  76A180F0  ð.¡v  user32.LoadBitmapA
0043B324  76A2E860  `è¢v  user32.KillTimer
0043B328  76A2E7A0   ç¢v  user32.InvalidateRect
0043B32C  76A1A410  .¤¡v  user32.InflateRect
0043B330  76A214F0  ð.¢v  user32.GetWindowRect
0043B334  76A213D0  Ð.¢v  user32.GetSystemMetrics
0043B338  76A80900  ..¨v  user32.GetDlgItemTextA
0043B33C  76A25CE0  à\¢v  user32.GetDlgItem
0043B340  76A2B610  .¶¢v  user32.GetDC
0043B344  76A11E40  @.¡v  user32.GetPhysicalCursorPos
0043B348  76A21770  p.¢v  user32.GetClientRect
0043B34C  76A13A60  `:¡v  user32.FrameRect
0043B350  76A0F990  .ù v  user32.FillRect
0043B354  76A2E110  .á¢v  user32.EndPaint
0043B358  76A17140  @q¡v  user32.EndDialog
0043B35C  76A0EDE0  àí v  user32.DrawTextA
0043B360  76A11D80  ..¡v  user32.DrawIconEx
0043B364  76A0EB10  .ë v  user32.DrawEdge
0043B368  76A4B900  .¹¤v  user32.DialogBoxParamA
0043B36C  774E8870  p.Nw  ntdll.NtdllDialogWndProc_A
0043B370  76A10820   .¡v  user32.CallWindowProcA
0043B374  76A2DD10  .Ý¢v  user32.BeginPaint
0043B378  76A0FFF0  ðÿ v  user32.CharNextA
0043B37C  76A6F5F0  ðõ¦v  user32.CharToOemA
0043B380  00000000  ....
0043B384  022A023E  >.*.
0043B388  00000000  ....
0043B38C  76A14C20   L¡v  user32.CreateWindowExA
0043B390  00000000  ....

We can see that the IAT starts at VA = 0043B194 and ends at 0043B390; the Size is obtained by subtraction (0043B390 - 0043B194), which gives Size = 0x1FC. One detail: the zeros that appear in the table are just separators between DLLs, not lost entries.

Okay, now with the data we have we open Scylla and fill in VA 0043B194 and Size 1FC, then Get Imports, and it shows us the result:

Program window showing the success message

  • 75 valid APIs.
  • Two invalid groups, of 42 and 1 entries, which point to 0x022Axxxx. These addresses are in memory allocated by the packer, not in a DLL. They are import redirections.

Here you can see the complete IAT, both the good entries and the redirected ones.

[ ASM ][Copy]
0040540B | 90                | nop                                           |
0040540C | FF25 64B24300     | jmp dword ptr ds:[43B264]                     |
00405412 | 8BC0              | mov eax,eax                                   |
00405414 | FF25 60B24300     | jmp dword ptr ds:[43B260]                     |
0040541A | 8BC0              | mov eax,eax                                   |
0040541C | FF25 5CB24300     | jmp dword ptr ds:[43B25C]                     |
00405422 | 8BC0              | mov eax,eax                                   |
00405424 | FF25 58B24300     | jmp dword ptr ds:[43B258]                     |
0040542A | 8BC0              | mov eax,eax                                   |
0040542C | FF25 54B24300     | jmp dword ptr ds:[43B254]                     |
00405432 | 8BC0              | mov eax,eax                                   |
00405434 | FF25 50B24300     | jmp dword ptr ds:[43B250]                     |
0040543A | 8BC0              | mov eax,eax                                   |
0040543C | FF25 4CB24300     | jmp dword ptr ds:[43B24C]                     |
00405442 | 8BC0              | mov eax,eax                                   |
00405444 | FF25 48B24300     | jmp dword ptr ds:[43B248]                     |
0040544A | 8BC0              | mov eax,eax                                   |
0040544C | FF25 44B24300     | jmp dword ptr ds:[43B244]                     |
00405452 | 8BC0              | mov eax,eax                                   |
00405454 | FF25 40B24300     | jmp dword ptr ds:[43B240]                     |
0040545A | 8BC0              | mov eax,eax                                   |
0040545C | FF25 3CB24300     | jmp dword ptr ds:[43B23C]                     |
00405462 | 8BC0              | mov eax,eax                                   |
00405464 | FF25 38B24300     | jmp dword ptr ds:[43B238]                     |
0040546A | 8BC0              | mov eax,eax                                   |
0040546C | FF25 34B24300     | jmp dword ptr ds:[43B234]                     |
00405472 | 8BC0              | mov eax,eax                                   |
00405474 | FF25 30B24300     | jmp dword ptr ds:[43B230]                     |
0040547A | 8BC0              | mov eax,eax                                   |
0040547C | FF25 2CB24300     | jmp dword ptr ds:[43B22C]                     |
00405482 | 8BC0              | mov eax,eax                                   |
00405484 | FF25 28B24300     | jmp dword ptr ds:[43B228]                     |
0040548A | 8BC0              | mov eax,eax                                   |
0040548C | FF25 24B24300     | jmp dword ptr ds:[43B224]                     |
00405492 | 8BC0              | mov eax,eax                                   |
00405494 | FF25 20B24300     | jmp dword ptr ds:[43B220]                     |
0040549A | 8BC0              | mov eax,eax                                   |
0040549C | FF25 1CB24300     | jmp dword ptr ds:[43B21C]                     |
004054A2 | 8BC0              | mov eax,eax                                   |
004054A4 | FF25 18B24300     | jmp dword ptr ds:[43B218]                     |
004054AA | 8BC0              | mov eax,eax                                   |
004054AC | FF25 14B24300     | jmp dword ptr ds:[43B214]                     |
004054B2 | 8BC0              | mov eax,eax                                   |
004054B4 | FF25 10B24300     | jmp dword ptr ds:[43B210]                     |
004054BA | 8BC0              | mov eax,eax                                   |
004054BC | FF25 0CB24300     | jmp dword ptr ds:[43B20C]                     |
004054C2 | 8BC0              | mov eax,eax                                   |
004054C4 | FF25 08B24300     | jmp dword ptr ds:[43B208]                     |
004054CA | 8BC0              | mov eax,eax                                   |
004054CC | FF25 04B24300     | jmp dword ptr ds:[43B204]                     |
004054D2 | 8BC0              | mov eax,eax                                   |
004054D4 | FF25 00B24300     | jmp dword ptr ds:[43B200]                     |
004054DA | 8BC0              | mov eax,eax                                   |
004054DC | FF25 FCB14300     | jmp dword ptr ds:[43B1FC]                     |
004054E2 | 8BC0              | mov eax,eax                                   |
004054E4 | FF25 F8B14300     | jmp dword ptr ds:[43B1F8]                     |
004054EA | 8BC0              | mov eax,eax                                   |
004054EC | FF25 F4B14300     | jmp dword ptr ds:[43B1F4]                     |
004054F2 | 8BC0              | mov eax,eax                                   |
004054F4 | FF25 F0B14300     | jmp dword ptr ds:[43B1F0]                     |
004054FA | 8BC0              | mov eax,eax                                   |
004054FC | FF25 ECB14300     | jmp dword ptr ds:[43B1EC]                     |
00405502 | 8BC0              | mov eax,eax                                   |
00405504 | FF25 E8B14300     | jmp dword ptr ds:[43B1E8]                     |
0040550A | 8BC0              | mov eax,eax                                   |
0040550C | FF25 E4B14300     | jmp dword ptr ds:[43B1E4]                     |
00405512 | 8BC0              | mov eax,eax                                   |
00405514 | FF25 E0B14300     | jmp dword ptr ds:[43B1E0]                     |
0040551A | 8BC0              | mov eax,eax                                   |
0040551C | FF25 DCB14300     | jmp dword ptr ds:[43B1DC]                     |
00405522 | 8BC0              | mov eax,eax                                   |
00405524 | FF25 D0B24300     | jmp dword ptr ds:[<&AlphaBlend>]              |
0040552A | 8BC0              | mov eax,eax                                   |
0040552C | FF25 C8B24300     | jmp dword ptr ds:[<&BitBlt>]                  |
00405532 | 8BC0              | mov eax,eax                                   |
00405534 | FF25 C4B24300     | jmp dword ptr ds:[<&CreateBitmap>]            |
0040553A | 8BC0              | mov eax,eax                                   |
0040553C | FF25 C0B24300     | jmp dword ptr ds:[<&CreateCompatibleBitmap>]  |
00405542 | 8BC0              | mov eax,eax                                   |
00405544 | FF25 BCB24300     | jmp dword ptr ds:[<&CreateCompatibleDC>]      |
0040554A | 8BC0              | mov eax,eax                                   |
0040554C | FF25 B8B24300     | jmp dword ptr ds:[<&CreateDIBSection>]        |
00405552 | 8BC0              | mov eax,eax                                   |
00405554 | FF25 B4B24300     | jmp dword ptr ds:[<&CreateFontA>]             |
0040555A | 8BC0              | mov eax,eax                                   |
0040555C | FF25 B0B24300     | jmp dword ptr ds:[<&CreateSolidBrush>]        |
00405562 | 8BC0              | mov eax,eax                                   |
00405564 | FF25 ACB24300     | jmp dword ptr ds:[<&DeleteDC>]                |
0040556A | 8BC0              | mov eax,eax                                   |
0040556C | FF25 A8B24300     | jmp dword ptr ds:[<&DeleteObject>]            |
00405572 | 8BC0              | mov eax,eax                                   |
00405574 | FF25 A4B24300     | jmp dword ptr ds:[<&GetObjectA>]              |
0040557A | 8BC0              | mov eax,eax                                   |
0040557C | FF25 A0B24300     | jmp dword ptr ds:[<&GetStockObject>]          |
00405582 | 8BC0              | mov eax,eax                                   |
00405584 | FF25 9CB24300     | jmp dword ptr ds:[<&GetTextExtentPoint32A>]   |
0040558A | 8BC0              | mov eax,eax                                   |
0040558C | FF25 98B24300     | jmp dword ptr ds:[<&PatBlt>]                  |
00405592 | 8BC0              | mov eax,eax                                   |
00405594 | FF25 94B24300     | jmp dword ptr ds:[<&Polygon>]                 |
0040559A | 8BC0              | mov eax,eax                                   |
0040559C | FF25 90B24300     | jmp dword ptr ds:[<&SelectObject>]            |
004055A2 | 8BC0              | mov eax,eax                                   |
004055A4 | FF25 8CB24300     | jmp dword ptr ds:[<&SetBkColor>]              |
004055AA | 8BC0              | mov eax,eax                                   |
004055AC | FF25 88B24300     | jmp dword ptr ds:[<&SetBkMode>]               |
004055B2 | 8BC0              | mov eax,eax                                   |
004055B4 | FF25 84B24300     | jmp dword ptr ds:[<&SetMapMode>]              |
004055BA | 8BC0              | mov eax,eax                                   |
004055BC | FF25 80B24300     | jmp dword ptr ds:[<&SetTextColor>]            |
004055C2 | 8BC0              | mov eax,eax                                   |
004055C4 | FF25 7CB24300     | jmp dword ptr ds:[<&StretchBlt>]              |
004055CA | 8BC0              | mov eax,eax                                   |
004055CC | FF25 78B24300     | jmp dword ptr ds:[<&TextOutA>]                |
004055D2 | 8BC0              | mov eax,eax                                   |
004055D4 | FF25 74B34300     | jmp dword ptr ds:[<&BeginPaint>]              |
004055DA | 8BC0              | mov eax,eax                                   |
004055DC | FF25 70B34300     | jmp dword ptr ds:[<&CallWindowProcA>]         |
004055E2 | 8BC0              | mov eax,eax                                   |
004055E4 | FF25 78B34300     | jmp dword ptr ds:[<&CharNextA>]               |
004055EA | 8BC0              | mov eax,eax                                   |
004055EC | FF25 7CB34300     | jmp dword ptr ds:[<&CharToOemA>]              |
004055F2 | 8BC0              | mov eax,eax                                   |
004055F4 | FF25 6CB34300     | jmp dword ptr ds:[<&NtdllDialogWndProc_A>]    |
004055FA | 8BC0              | mov eax,eax                                   |
004055FC | FF25 68B34300     | jmp dword ptr ds:[<&DialogBoxParamA>]         |
00405602 | 8BC0              | mov eax,eax                                   |
00405604 | FF25 64B34300     | jmp dword ptr ds:[<&DrawEdge>]                |
0040560A | 8BC0              | mov eax,eax                                   |
0040560C | FF25 60B34300     | jmp dword ptr ds:[<&DrawIconEx>]              |
00405612 | 8BC0              | mov eax,eax                                   |
00405614 | FF25 5CB34300     | jmp dword ptr ds:[<&DrawTextA>]               |
0040561A | 8BC0              | mov eax,eax                                   |
0040561C | FF25 58B34300     | jmp dword ptr ds:[<&EndDialog>]               |
00405622 | 8BC0              | mov eax,eax                                   |
00405624 | FF25 54B34300     | jmp dword ptr ds:[<&EndPaint>]                |
0040562A | 8BC0              | mov eax,eax                                   |
0040562C | FF25 50B34300     | jmp dword ptr ds:[<&FillRect>]                |
00405632 | 8BC0              | mov eax,eax                                   |
00405634 | FF25 4CB34300     | jmp dword ptr ds:[<&FrameRect>]               |
0040563A | 8BC0              | mov eax,eax                                   |
0040563C | FF25 48B34300     | jmp dword ptr ds:[<&GetClientRect>]           |
00405642 | 8BC0              | mov eax,eax                                   |
00405644 | FF25 44B34300     | jmp dword ptr ds:[<&GetPhysicalCursorPos>]    |
0040564A | 8BC0              | mov eax,eax                                   |
0040564C | FF25 40B34300     | jmp dword ptr ds:[<&GetDC>]                   |
00405652 | 8BC0              | mov eax,eax                                   |
00405654 | FF25 3CB34300     | jmp dword ptr ds:[<&GetDlgItem>]              |
0040565A | 8BC0              | mov eax,eax                                   |
0040565C | FF25 38B34300     | jmp dword ptr ds:[<&GetDlgItemTextA>]         |
00405662 | 8BC0              | mov eax,eax                                   |
00405664 | FF25 34B34300     | jmp dword ptr ds:[<&GetSystemMetrics>]        |
0040566A | 8BC0              | mov eax,eax                                   |
0040566C | FF25 30B34300     | jmp dword ptr ds:[<&GetWindowRect>]           |
00405672 | 8BC0              | mov eax,eax                                   |
00405674 | FF25 2CB34300     | jmp dword ptr ds:[<&InflateRect>]             |
0040567A | 8BC0              | mov eax,eax                                   |
0040567C | FF25 28B34300     | jmp dword ptr ds:[<&InvalidateRect>]          |
00405682 | 8BC0              | mov eax,eax                                   |
00405684 | FF25 24B34300     | jmp dword ptr ds:[<&KillTimer>]               |
0040568A | 8BC0              | mov eax,eax                                   |
0040568C | FF25 20B34300     | jmp dword ptr ds:[<&LoadBitmapA>]             |
00405692 | 8BC0              | mov eax,eax                                   |
00405694 | FF25 1CB34300     | jmp dword ptr ds:[<&LoadCursorA>]             |
0040569A | 8BC0              | mov eax,eax                                   |
0040569C | FF25 18B34300     | jmp dword ptr ds:[<&LoadIconA>]               |
004056A2 | 8BC0              | mov eax,eax                                   |
004056A4 | FF25 18B34300     | jmp dword ptr ds:[<&LoadIconA>]               |
004056AA | 8BC0              | mov eax,eax                                   |
004056AC | FF25 14B34300     | jmp dword ptr ds:[<&LoadStringA>]             |
004056B2 | 8BC0              | mov eax,eax                                   |
004056B4 | FF25 10B34300     | jmp dword ptr ds:[<&MessageBoxA>]             |
004056BA | 8BC0              | mov eax,eax                                   |
004056BC | FF25 0CB34300     | jmp dword ptr ds:[<&PostQuitMessage>]         |
004056C2 | 8BC0              | mov eax,eax                                   |
004056C4 | FF25 08B34300     | jmp dword ptr ds:[<&ReleaseCapture>]          |
004056CA | 8BC0              | mov eax,eax                                   |
004056CC | FF25 04B34300     | jmp dword ptr ds:[<&ReleaseDC>]               |
004056D2 | 8BC0              | mov eax,eax                                   |
004056D4 | FF25 00B34300     | jmp dword ptr ds:[<&SendDlgItemMessageA>]     |
004056DA | 8BC0              | mov eax,eax                                   |
004056DC | FF25 FCB24300     | jmp dword ptr ds:[<&SendMessageA>]            |
004056E2 | 8BC0              | mov eax,eax                                   |
004056E4 | FF25 FCB24300     | jmp dword ptr ds:[<&SendMessageA>]            |
004056EA | 8BC0              | mov eax,eax                                   |
004056EC | FF25 F8B24300     | jmp dword ptr ds:[<&SetCursor>]               |
004056F2 | 8BC0              | mov eax,eax                                   |
004056F4 | FF25 F4B24300     | jmp dword ptr ds:[<&SetDlgItemTextA>]         |
004056FA | 8BC0              | mov eax,eax                                   |
004056FC | FF25 F0B24300     | jmp dword ptr ds:[<&SetTimer>]                |
00405702 | 8BC0              | mov eax,eax                                   |
00405704 | FF25 ECB24300     | jmp dword ptr ds:[<&SetWindowLongA>]          |
0040570A | 8BC0              | mov eax,eax                                   |
0040570C | FF25 ECB24300     | jmp dword ptr ds:[<&SetWindowLongA>]          |
00405712 | 8BC0              | mov eax,eax                                   |
00405714 | FF25 E8B24300     | jmp dword ptr ds:[<&SetWindowPos>]            |
0040571A | 8BC0              | mov eax,eax                                   |
0040571C | FF25 E4B24300     | jmp dword ptr ds:[<&SetWindowTextA>]          |
00405722 | 8BC0              | mov eax,eax                                   |
00405724 | FF25 E0B24300     | jmp dword ptr ds:[<&ShowWindow>]              |
0040572A | 8BC0              | mov eax,eax                                   |
0040572C | FF25 DCB24300     | jmp dword ptr ds:[<&UpdateWindow>]            |

You can clearly see that instead of the API’s real address, the IAT has a detour. If we dumped it like this, the executable would point to a region that only exists in the live process and would break when run.

Understanding the redirection

If we double-click one of the invalid slots, we see that each one points to a small 7-byte stub:

[ ASM ][Copy]
022A0000 | B8 20FD4877       | mov eax,<ntdll.RtlDeleteCriticalSection>      | eax:EntryPoint
022A0005 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0007 | B8 70E94977       | mov eax,<ntdll.RtlLeaveCriticalSection>       | eax:EntryPoint
022A000C | FFE0              | jmp eax                                       | eax:EntryPoint
022A000E | B8 70014A77       | mov eax,<ntdll.RtlEnterCriticalSection>       | eax:EntryPoint
022A0013 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0015 | B8 10C94B77       | mov eax,<ntdll.RtlInitializeCriticalSection>  | eax:EntryPoint
022A001A | FFE0              | jmp eax                                       | eax:EntryPoint
022A001C | B8 60F9A475       | mov eax,<kernel32.VirtualFree>                | eax:EntryPoint
022A0021 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0023 | B8 60F8A475       | mov eax,<kernel32.VirtualAlloc>               | eax:EntryPoint
022A0028 | FFE0              | jmp eax                                       | eax:EntryPoint
022A002A | B8 D0F9A475       | mov eax,<kernel32.LocalFree>                  | eax:EntryPoint
022A002F | FFE0              | jmp eax                                       | eax:EntryPoint
022A0031 | B8 0009A575       | mov eax,<kernel32.LocalAlloc>                 | eax:EntryPoint
022A0036 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0038 | B8 4028A575       | mov eax,<kernel32.GetTickCount>               | eax:EntryPoint
022A003D | FFE0              | jmp eax                                       | eax:EntryPoint
022A003F | B8 E0E3A475       | mov eax,<kernel32.QueryPerformanceCounter>    | eax:EntryPoint
022A0044 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0046 | B8 101EA575       | mov eax,<kernel32.GetVersion>                 | eax:EntryPoint
022A004B | FFE0              | jmp eax                                       | eax:EntryPoint
022A004D | B8 B0E3A475       | mov eax,<kernel32.GetCurrentThreadId>         | eax:EntryPoint
022A0052 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0054 | B8 90E4A475       | mov eax,<kernel32.WideCharToMultiByte>        | eax:EntryPoint
022A0059 | FFE0              | jmp eax                                       | eax:EntryPoint
022A005B | B8 E008A575       | mov eax,<kernel32.lstrlenA>                   | eax:EntryPoint
022A0060 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0062 | B8 0095A475       | mov eax,<kernel32.lstrcpynA>                  | eax:EntryPoint
022A0067 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0069 | B8 C01AA575       | mov eax,<kernel32.LoadLibraryExA>             | eax:EntryPoint
022A006E | FFE0              | jmp eax                                       | eax:EntryPoint
022A0070 | B8 D08BA475       | mov eax,<kernel32.GetThreadLocale>            | eax:EntryPoint
022A0075 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0077 | B8 2011A575       | mov eax,<kernel32.GetStartupInfoA>            | eax:EntryPoint
022A007C | FFE0              | jmp eax                                       | eax:EntryPoint
022A007E | B8 F0F9A475       | mov eax,<kernel32.GetProcAddress>             | eax:EntryPoint
022A0083 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0085 | B8 000FA575       | mov eax,<kernel32.GetModuleHandleA>           | eax:EntryPoint
022A008A | FFE0              | jmp eax                                       | eax:EntryPoint
022A008C | B8 D012A575       | mov eax,<kernel32.GetModuleFileNameA>         | eax:EntryPoint
022A0091 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0093 | B8 1086A475       | mov eax,<kernel32.GetLocaleInfoA>             | eax:EntryPoint
022A0098 | FFE0              | jmp eax                                       | eax:EntryPoint
022A009A | B8 B0E4A475       | mov eax,<kernel32.GetLastError>               | eax:EntryPoint
022A009F | FFE0              | jmp eax                                       | eax:EntryPoint
022A00A1 | B8 8023A575       | mov eax,<kernel32.GetCommandLineA>            | eax:EntryPoint
022A00A6 | FFE0              | jmp eax                                       | eax:EntryPoint
022A00A8 | B8 800FA575       | mov eax,<kernel32.FreeLibrary>                | eax:EntryPoint
022A00AD | FFE0              | jmp eax                                       | eax:EntryPoint
022A00AF | B8 5036A575       | mov eax,<kernel32.FindFirstFileA>             | eax:EntryPoint
022A00B4 | FFE0              | jmp eax                                       | eax:EntryPoint
022A00B6 | B8 1036A575       | mov eax,<kernel32.FindClose>                  | eax:EntryPoint
022A00BB | FFE0              | jmp eax                                       | eax:EntryPoint
022A00BD | B8 806DA575       | mov eax,<kernel32.ExitProcess>                | eax:EntryPoint
022A00C2 | FFE0              | jmp eax                                       | eax:EntryPoint
022A00C4 | B8 203AA575       | mov eax,<kernel32.WriteFile>                  | eax:EntryPoint
022A00C9 | FFE0              | jmp eax                                       | eax:EntryPoint
022A00CB | B8 2074A675       | mov eax,<kernel32.UnhandledExceptionFilter>   | eax:EntryPoint
022A00D0 | FFE0              | jmp eax                                       | eax:EntryPoint
022A00D2 | B8 C039A575       | mov eax,<kernel32.SetFilePointer>             | eax:EntryPoint
022A00D7 | FFE0              | jmp eax                                       | eax:EntryPoint
022A00D9 | B8 8039A575       | mov eax,<kernel32.SetEndOfFile>               | eax:EntryPoint
022A00DE | FFE0              | jmp eax                                       | eax:EntryPoint
022A00E0 | B8 600AA575       | mov eax,<kernel32.RtlUnwind>                  | eax:EntryPoint
022A00E5 | FFE0              | jmp eax                                       | eax:EntryPoint
022A00E7 | B8 3039A575       | mov eax,<kernel32.ReadFile>                   | eax:EntryPoint
022A00EC | FFE0              | jmp eax                                       | eax:EntryPoint
022A00EE | B8 500AA575       | mov eax,<kernel32.RaiseException>             | eax:EntryPoint
022A00F3 | FFE0              | jmp eax                                       | eax:EntryPoint
022A00F5 | B8 A01BA575       | mov eax,<kernel32.GetStdHandle>               | eax:EntryPoint
022A00FA | FFE0              | jmp eax                                       | eax:EntryPoint
022A00FC | B8 D037A575       | mov eax,<kernel32.GetFileSize>                | eax:EntryPoint
022A0101 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0103 | B8 0038A575       | mov eax,<kernel32.GetFileType>                | eax:EntryPoint
022A0108 | FFE0              | jmp eax                                       | eax:EntryPoint
022A010A | B8 A035A575       | mov eax,<kernel32.CreateFileA>                | eax:EntryPoint
022A010F | FFE0              | jmp eax                                       | eax:EntryPoint
022A0111 | B8 5033A575       | mov eax,<kernel32.CloseHandle>                | eax:EntryPoint
022A0116 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0118 | B8 50E4A475       | mov eax,<kernel32.TlsSetValue>                | eax:EntryPoint
022A011D | FFE0              | jmp eax                                       | eax:EntryPoint
022A011F | B8 C0E3A475       | mov eax,<kernel32.TlsGetValue>                | eax:EntryPoint
022A0124 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0126 | B8 0009A575       | mov eax,<kernel32.LocalAlloc>                 | eax:EntryPoint
022A012B | FFE0              | jmp eax                                       | eax:EntryPoint
022A012D | B8 000FA575       | mov eax,<kernel32.GetModuleHandleA>           | eax:EntryPoint
022A0132 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0134 | B8 E008A575       | mov eax,<kernel32.lstrlenA>                   | eax:EntryPoint
022A0139 | FFE0              | jmp eax                                       | eax:EntryPoint
022A013B | B8 608CA475       | mov eax,<kernel32.lstrcat>                    | eax:EntryPoint
022A0140 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0142 | B8 203AA575       | mov eax,<kernel32.WriteFile>                  | eax:EntryPoint
022A0147 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0149 | B8 4035A575       | mov eax,<kernel32.WaitForSingleObject>        | eax:EntryPoint
022A014E | FFE0              | jmp eax                                       | eax:EntryPoint
022A0150 | B8 10FAA475       | mov eax,<kernel32.VirtualQuery>               | eax:EntryPoint
022A0155 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0157 | B8 2009A575       | mov eax,<kernel32.SizeofResource>             | eax:EntryPoint
022A015C | FFE0              | jmp eax                                       | eax:EntryPoint
022A015E | B8 600BA575       | mov eax,<kernel32.SetThreadPriority>          | eax:EntryPoint
022A0163 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0165 | B8 B00AA575       | mov eax,<kernel32.SetErrorMode>               | eax:EntryPoint
022A016A | FFE0              | jmp eax                                       | eax:EntryPoint
022A016C | B8 C034A575       | mov eax,<kernel32.ReleaseMutex>               | eax:EntryPoint
022A0171 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0173 | B8 E0F7A475       | mov eax,<kernel32.LockResource>               | eax:EntryPoint
022A0178 | FFE0              | jmp eax                                       | eax:EntryPoint
022A017A | B8 E0ECA475       | mov eax,<kernel32.LoadResource>               | eax:EntryPoint
022A017F | FFE0              | jmp eax                                       | eax:EntryPoint
022A0181 | B8 7010A575       | mov eax,<kernel32.LoadLibraryA>               | eax:EntryPoint
022A0186 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0188 | B8 F021A575       | mov eax,<kernel32.GetWindowsDirectoryA>       | eax:EntryPoint
022A018D | FFE0              | jmp eax                                       | eax:EntryPoint
022A018F | B8 001CA575       | mov eax,<kernel32.GetVersionExA>              | eax:EntryPoint
022A0194 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0196 | B8 4028A575       | mov eax,<kernel32.GetTickCount>               | eax:EntryPoint
022A019B | FFE0              | jmp eax                                       | eax:EntryPoint
022A019D | B8 D08BA475       | mov eax,<kernel32.GetThreadLocale>            | eax:EntryPoint
022A01A2 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01A4 | B8 8038A575       | mov eax,<kernel32.GetTempPathA>               | eax:EntryPoint
022A01A9 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01AB | B8 8086A475       | mov eax,<kernel32.GetSystemDirectoryA>        | eax:EntryPoint
022A01B0 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01B2 | B8 D09AA475       | mov eax,<kernel32.GetStringTypeA>             | eax:EntryPoint
022A01B7 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01B9 | B8 A01BA575       | mov eax,<kernel32.GetStdHandle>               | eax:EntryPoint
022A01BE | FFE0              | jmp eax                                       | eax:EntryPoint
022A01C0 | B8 F0F9A475       | mov eax,<kernel32.GetProcAddress>             | eax:EntryPoint
022A01C5 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01C7 | B8 000FA575       | mov eax,<kernel32.GetModuleHandleA>           | eax:EntryPoint
022A01CC | FFE0              | jmp eax                                       | eax:EntryPoint
022A01CE | B8 D012A575       | mov eax,<kernel32.GetModuleFileNameA>         | eax:EntryPoint
022A01D3 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01D5 | B8 1086A475       | mov eax,<kernel32.GetLocaleInfoA>             | eax:EntryPoint
022A01DA | FFE0              | jmp eax                                       | eax:EntryPoint
022A01DC | B8 B0E4A475       | mov eax,<kernel32.GetLastError>               | eax:EntryPoint
022A01E1 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01E3 | B8 2037A575       | mov eax,<kernel32.GetDiskFreeSpaceA>          | eax:EntryPoint
022A01E8 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01EA | B8 D019A575       | mov eax,<kernel32.GetCPInfo>                  | eax:EntryPoint
022A01EF | FFE0              | jmp eax                                       | eax:EntryPoint
022A01F1 | B8 6008A575       | mov eax,<kernel32.GetACP>                     | eax:EntryPoint
022A01F6 | FFE0              | jmp eax                                       | eax:EntryPoint
022A01F8 | B8 800FA575       | mov eax,<kernel32.FreeLibrary>                | eax:EntryPoint
022A01FD | FFE0              | jmp eax                                       | eax:EntryPoint
022A01FF | B8 B02EA475       | mov eax,<kernel32.FindResourceA>              | eax:EntryPoint
022A0204 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0206 | B8 70BD4C77       | mov eax,<ntdll.RtlExitUserThread>             | eax:EntryPoint
022A020B | FFE0              | jmp eax                                       | eax:EntryPoint
022A020D | B8 806DA575       | mov eax,<kernel32.ExitProcess>                | eax:EntryPoint
022A0212 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0214 | B8 B094A475       | mov eax,<kernel32.EnumCalendarInfoA>          | eax:EntryPoint
022A0219 | FFE0              | jmp eax                                       | eax:EntryPoint
022A021B | B8 D035A575       | mov eax,<kernel32.DeleteFileA>                | eax:EntryPoint
022A0220 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0222 | B8 B013A575       | mov eax,<kernel32.CreateThread>               | eax:EntryPoint
022A0227 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0229 | B8 E033A575       | mov eax,<kernel32.CreateMutexA>               | eax:EntryPoint
022A022E | FFE0              | jmp eax                                       | eax:EntryPoint
022A0230 | B8 A035A575       | mov eax,<kernel32.CreateFileA>                | eax:EntryPoint
022A0235 | FFE0              | jmp eax                                       | eax:EntryPoint
022A0237 | B8 5033A575       | mov eax,<kernel32.CloseHandle>                | eax:EntryPoint
022A023C | FFE0              | jmp eax                                       | eax:EntryPoint
022A023E | B8 A013A575       | mov eax,<kernel32.Sleep>                      | eax:EntryPoint
022A0243 | FFE0              | jmp eax                                       | eax:EntryPoint

The solution: we’ll write a script to read the stubs in x32dbg

The good news is that the API’s real address is inside the stub: it is the immediate of the mov eax, .... So we can walk the IAT, read that immediate from each stub and rewrite the slot with the real API. The IAT is at 43B194, with size 0x1FC (it ends at 43B390).

[ SCRIPT ][Copy]
var $slot
var $stub
var $api
var $n
mov $slot, 43B194
mov $n, 0
loop:
cmp [$slot], 2200000
jb next
cmp [$slot], 2300000
jae next
mov $stub, [$slot]
cmp byte:[$stub], B8
jne next
mov $api, [$stub+1]
mov [$slot], $api
inc $n
next:
add $slot, 4
cmp $slot, 43B390
jb loop
log "slots parcheados: {d:$n}"
ret

How it works, in a nutshell:

  1. It walks every slot of the IAT in 4-byte steps, from 43B194 to 43B390.
  2. It only touches slots whose value falls in the range 0x2200000–0x2300000, the stubs’ area. Healthy slots are left as they are.
  3. It checks that the first byte of the stub is B8, the mov eax, imm32.
  4. It reads the immediate [$stub+1] and writes it into the slot.

Watch out: the range of the stubs 0x0220xxxx depends on your run. Look at which addresses your invalid slots point to and adjust the script’s limits.

We run the script with the process halted at the OEP.

Program window showing the success message

Now we go back to Scylla, fill in the fields and do Get Imports, and just in case Show Invalid:

Program window showing the success message

118 valid APIs, 0 invalid.

The groups that were red now resolve to kernel32.dll (42 + 1 entries).

Tip: before writing a repair script, look at the pattern of the bad slots. If the real API names are in clear text inside the stub, identifying where each slot points is much easier when rebuilding the IAT.


The dump and the fix

With the process still halted at the OEP and the IAT already repaired, we dump from Scylla:

  1. Check that the OEP is 0x40F844 (RVA 0xF844) and that the IAT is at 43B194 with size 0x1FC.
  2. Do Get Imports one last time and confirm there are no invalid entries.
  3. Press Dump and save the file (for example, UnPackme01_dump.exe).
  4. Press Fix Dump and select that file. Scylla generates a copy with the rebuilt imports (UnPackme01_dump_SCY.exe). Always use this file, not the one from the previous step.
  5. (Optional) If you want, open the result in a hex editor or in PE-bear and normalize the signature at offset 0 to MZ. It is cosmetic: the Windows loader doesn’t depend on it. To check that the unpacking worked:
  • When we open the file in Detect It Easy, PECompact no longer appears and it now clearly shows the compiler (Delphi/Borland).

Program window showing the success message

  • When we load it in x32dbg, the entry point is now 0x40F844 and the code is readable, with no stubs.
  • When we run it in the VM, we verify that it starts exactly like the original.

Tip: if the dump won’t open or fails when run, check the IAT first (it’s the most common cause) and then the OEP value in Scylla.


Summary: how we solved it

  1. Hardware execution breakpoint on the SEH handler (45E6EC) and Shift+F9 to pass the exception.
  2. Breakpoint at 45E70F (real start of the stub).
  3. Decompressor (call ecx at 45E76D) and second stage (call edi at 45E78A). We don’t go into details.
  4. Hardware breakpoint at 45E7AE (jmp eax): eax is the OEP.
  5. Patch the IAT with the script (the 0x0220xxxx slots point to mov eax, API / jmp eax stubs).
  6. Scylla: Get Imports, Dump and Fix Dump.

Now what?

If this were malware, with the program already unpacked, the analysis work is only just beginning.