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

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
DialogProcor aWndProclater 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 magicis 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,registercalling convention), and it will help us recognize the OEP when we get there. It also fits theMZPmark 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,
.rsrcholds 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

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.
| Section | VA | Virtual Size | Raw Size | Entropy |
|---|---|---|---|---|
| CODE | 0x1000 | 0x5B000 | 0x29400 | 8 |
| .rsrc | 0x5C000 | 0x3000 | 0x2800 | 5.62 |
What this table tells us:
- There are only two sections. There is no
.data,.idataor.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
0x5B000than on disk0x29400: it decompresses over itself when it runs. - .rsrc is marked as executable
ERWand 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
MZPinstead ofMZ. 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:
| Bit | Meaning |
|---|---|
0x00000020 | Contains code |
0x20000000 | Executable |
0x40000000 | Readable |
0x80000000 | Writable |
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:
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:
- The first three instructions set up our own exception handler (SEH): they push the handler’s address
45E6ECand hook it intofs:[0]. xor eax,eaxsetseax = 0andmov [eax],ecxtries to write to address 0. That deliberately triggers an access violation.- 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 at0x45E6EC, inside .rsrc. If we read it, we see that it:
- overwrites the instruction that failed
40F85Awith ajmp rel32to0x45E70F, - 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:
- We place a hardware execution breakpoint on the handler:
bphws 45E6EC,x. - We run with F9. The debugger stops at the access violation at
40F85A. - We pass the exception to the program with Shift+F9. The hardware breakpoint fires at
45E6EC. - 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
0xCCbytes 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.
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:EntryPointWe 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:
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.
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:

- 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.
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:
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:EntryPointThe 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).
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}"
retHow it works, in a nutshell:
- It walks every slot of the IAT in 4-byte steps, from
43B194to43B390. - It only touches slots whose value falls in the range
0x2200000–0x2300000, the stubs’ area. Healthy slots are left as they are. - It checks that the first byte of the stub is
B8, themov eax, imm32. - It reads the immediate
[$stub+1]and writes it into the slot.
Watch out: the range of the stubs
0x0220xxxxdepends 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.

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

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:
- Check that the OEP is
0x40F844(RVA0xF844) and that the IAT is at43B194with size0x1FC. - Do Get Imports one last time and confirm there are no invalid entries.
- Press Dump and save the file (for example,
UnPackme01_dump.exe). - 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. - (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).

- When we load it in x32dbg, the entry point is now
0x40F844and 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
- Hardware execution breakpoint on the SEH handler (
45E6EC) and Shift+F9 to pass the exception. - Breakpoint at
45E70F(real start of the stub). - Decompressor (
call ecxat45E76D) and second stage (call ediat45E78A). We don’t go into details. - Hardware breakpoint at
45E7AE(jmp eax):eaxis the OEP. - Patch the IAT with the script (the
0x0220xxxxslots point tomov eax, API / jmp eaxstubs). - 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.
