Распаковка шаг за шагом: PECompact, OEP, дамп и восстановление IAT
Автор: Diego J D Arias · Платформа: Windows · Исполняемый файл: UnPackme01.exe (32 бита) Инструменты: x64dbg (версия x32dbg) · Detect It Easy v3.21 · PE-bear v0.7.2 · Scylla
На этот раз мы распаковываем 32-битный исполняемый файл, защищённый PECompact. Мы сделаем четыре вещи: поймём, почему пакер ведёт себя именно так, дойдём до настоящего OEP, восстановим IAT, которую пакер перенаправил, и снимем дамп уже распакованного исполняемого файла.
Важно: Всегда работай в изолированной виртуальной машине (сеть host-only или отключённая) со снимком (snapshot), включающим память.
Первым делом посмотрим на исполняемый файл до отладки
Перед тем как открыть отладчик, смотрим на файл двумя инструментами: Detect It Easy (DIE) и PE-bear. Мы ничего не запускаем, но эти два скриншота уже говорят очень многое. Прочитаем их внимательно, потому что отсюда берётся почти весь план распаковки.
Detect It Easy: кто такой пакер и что под ним
Detect It Easy: показывает PECompact, Borland Delphi и эвристики упаковки

Строка за строкой:
- PE32, Windows 32 бита, GUI. Это 32-битный исполняемый файл с графическим интерфейсом. Поэтому мы используем x32dbg (а не 64-битную версию) и, так как это GUI, дальше ожидаем найти
DialogProcилиWndProc. - Packer: PECompact (3.02.2 или 3.03.21 beta). DIE распознаёт пакер по сигнатуре и даже называет примерную версию. Это самая ценная информация на скриншоте: мы уже знаем, с чем имеем дело. PECompact не виртуализирует код: стаб распаковывает программу в памяти и заканчивается переходом на оригинальный код. Значит, мы знаем, что искать: стаб, распаковку и этот финальный переход.
unknown magic— это значение в заголовке, которое DIE не может сопоставить с конкретной версией, поэтому он называет две возможности. На метод это не влияет. - Linker: Turbo Linker 2.25 / Compiler: Borland Delphi / Language: Object Pascal. Внимание: это относится к исходной программе, а не к пакеру. DIE выводит это из данных заголовка, которые пакер оставил нетронутыми (например, версия линкера). Это очень полезный факт: мы уже знаем, что внутри программа на Delphi, ничего не распаковывая. Это значит, что мы знаем, как обычно выглядит её старт (пролог
push ebp / mov ebp,esp / add esp,-N, соглашение о вызовахregister), и это поможет нам узнать OEP, когда мы до него доберёмся. Это также согласуется с меткойMZPв смещении 0, типичной для исполняемых файлов Borland. - (Heur) Protection: Unreadable resources. DIE пытается прочитать ресурсы исполняемого файла и не может. В обычной программе в
.rsrcлежат иконки, диалоги и строки. Здесь настоящие ресурсы не видны: скорее всего, пакер сжимает их и потом восстанавливает, а эта секция занята чем-то другим (сейчас это проверим). - (Heur) Protection: Section #0 (“CODE”) has RWX. Секция кода одновременно доступна для чтения, записи и исполнения. Обычный код — только чтение и исполнение. То, что она доступна для записи, указывает на то, что этот код будет переписывать сам себя, а именно это и делает пакер при распаковке.
- (Heur) Packer: Generic [EntryPoint + Imports like PECompact + Section #0 (“CODE”) compressed + PE in resources + High entropy]. DIE приходит к тому же выводу другим путём, не используя сигнатуру: смотрит на точку входа, импорты, сжатие CODE, энтропию и данные, похожие на исполняемый файл, внутри ресурсов. Это независимое второе мнение. И последний пункт совпадает с тем, что мы сейчас увидим: стаб и сжатые данные пакера живут в
.rsrc.
Совет: когда сигнатура и эвристика совпадают, диагнозу можно доверять. Если бы DIE показывал только эвристику и никакой сигнатуры, сомнений было бы больше: это может быть редкий пакер или модифицированная версия.
PE-bear: секции, где пакер виден с первого взгляда
PE-bear: показывает две секции исполняемого файла: CODE и .rsrc

Таблица секций — самое красноречивое. Обрати внимание на эти пункты:
1. Секций всего две. Обычный исполняемый файл Delphi обычно содержит ещё несколько (DATA, BSS, .idata, .reloc…). Здесь пакер всё объединил, и .idata нигде не видно: настоящие импорты не там, где мы их ожидали бы, и это заранее говорит нам, что придётся восстанавливать IAT.
2. CODE в памяти занимает гораздо больше места, чем на диске.
| Секция | VA | Virtual Size | Raw Size | Энтропия |
|---|---|---|---|---|
| CODE | 0x1000 | 0x5B000 | 0x29400 | 8 |
| .rsrc | 0x5C000 | 0x3000 | 0x2800 | 5.62 |
Что говорит нам эта таблица:
- Секций всего две. Нет
.data,.idataи.reloc. Это типично для пакера: настоящие импорты не видны. - У CODE энтропия 8, то есть она полностью сжата. К тому же в памяти она занимает гораздо больше
0x5B000, чем на диске0x29400: при запуске она распаковывается поверх самой себя. - .rsrc помечена как исполняемая
ERWи имеет энтропию 5.62. Там живёт часть стаба пакера, несжатая. - Точка входа попадает внутрь CODE, сжатой секции. То есть старт не может быть обычным кодом.
- В смещении 0 PE-bear показывает
MZPвместоMZ. Отмечаем это как любопытный факт: исполняемые файлы Delphi/Borland тоже часто несут эту метку.
Совет: точка входа внутри секции с энтропией 8 — это почти подтверждение того, что какой-то стаб что-то распаковывает. Вопрос превращается в «где этот стаб заканчивается».
Загрузчик Windows резервирует 364 КБ под CODE, а в файле всего 165 КБ. Эта разница — место, где появится настоящая программа, когда пакер её распакует. Добавим энтропию 8, которую DIE измеряет для этой секции, и вывод прямой: настоящего кода в файле нет, он порождается в памяти.
3. У обеих секций Characteristics = E0000020. Расшифровка:
| Бит | Значение |
|---|---|
0x00000020 | Содержит код |
0x20000000 | Исполняемая |
0x40000000 | Доступна для чтения |
0x80000000 | Доступна для записи |
Для CODE мы этого уже ожидали: она должна быть доступна для записи, чтобы распаковываться поверх самой себя. Но странная здесь .rsrc. Обычная секция ресурсов — это только читаемые данные (40000040). То, что здесь она исполняемая и доступная для записи, выдаёт, что в ней есть код: это стаб пакера. По её промежуточной энтропии 5.62 видно, что она смешивает несжатый код и данные.
4. Поле Ptr to Reloc. у CODE равно 32434550. Этот столбец должен быть указателем на релокации, а здесь это не имеет смысла: Num. of Reloc. равно 0. Это текстовое значение под видом числа. Если прочитать его в шестнадцатеричном виде, 0x32434550 в little-endian — это байты 50 45 43 32, которые в ASCII дают PEC2. Это метка, которую PECompact оставляет в заголовке, в поле, которое загрузчик игнорирует. Это ещё одно подтверждение пакера, на этот раз вручную.
5. Точка входа попадает внутрь CODE. Этот факт берётся из опционального заголовка (на скриншоте его не видно): точка входа — 0x40F844, как мы увидим чуть позже, то есть RVA 0xF844, что лежит в диапазоне CODE (от 0x1000 до 0x5C000). Если бы старт находился в обычной секции кода, это было бы нормально. Но здесь секция сжата, так что старт не может быть оригинальной программой: это должен быть стаб.
В поисках OEP
Классический способ начать — поставить точку останова на исполнение в памяти на 401000 и ждать, пока программа перейдёт в неё.
Здесь это не работает. Поскольку точка входа находится внутри CODE, точка останова срабатывает на самой первой инструкции, не отличая стаб пакера от оригинального кода.
Точка входа и трюк с SEH
Открываем образец в x32dbg и смотрим первые инструкции точки входа:
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 | устанавливает собственный SEH
0040F858 | 33C0 | xor eax,eax |
0040F85A | 8908 | mov dword ptr ds:[eax],ecx | запись по адресу 0 -> access violation
0040F85C | 50 | push eax |
0040F85D | 45 | inc ebp |
0040F85E | 43 | inc ebx |
0040F85F | 6F | outsd |
0040F860 | 6D | insd |Разберём это внимательно, потому что это сердце пакера:
- Первые три инструкции устанавливают собственный обработчик исключений (SEH): помещают в стек адрес обработчика
45E6ECи цепляют его вfs:[0]. xor eax,eaxобнуляетeax, аmov [eax],ecxпытается записать по адресу 0. Это намеренно вызывает access violation.- Всё, что идёт дальше (
outsd,insd,arpl…), — это мусор, который никогда не исполняется линейно. Он нужен, чтобы сломать статический анализ: IDA не следует реальному потоку, и листинг выглядит как бессмысленный код. Обработчик находится по адресу0x45E6EC, внутри .rsrc. Если его прочитать, мы увидим, что он:
- перезаписывает инструкцию, которая упала
40F85A, наjmp rel32к0x45E70F, - и возвращает
ExceptionContinueExecution. Windows возобновляет исполнение по тому же адресу,40F85A, который теперь уже не запись, а переход в стаб. Это самомодифицирующийся код, запускаемый намеренным исключением.
Совет: когда увидишь, что
fs:[0]изменяется в начале программы, будь настороже. В точке входа собственный SEH плюс инструкция, которая гарантированно падает, — обычно механизм передачи управления у пакера.
Пропустить исключение, не потеряв контроль
Мы хотим пройти через исключение и следовать потоку пакера, а не ntdll. Для этого делаем следующее:
- Ставим аппаратную точку останова на исполнение на обработчик:
bphws 45E6EC,x. - Запускаем клавишей F9. Отладчик останавливается на access violation по адресу
40F85A. - Передаём исключение программе клавишами Shift+F9. Аппаратная точка останова срабатывает на
45E6EC. - Ставим обычную точку останова на
45E70F, настоящее начало стаба, и продолжаем клавишей F9. Так мы пропускаем всю механику ntdll и попадаем точно в начало стаба.
Совет: используй аппаратные точки останова, когда можешь. Они не изменяют память, поэтому пакер, который сканирует собственный код в поисках байтов
0xCC, их не обнаружит.
Как добраться до OEP
Если пролистать немного вниз, видно, как стаб восстанавливает исходные регистры серией pop и заканчивается вычисляемым переходом: jmp eax по адресу 0x45E7AE. Это tail jump, финальный переход на оригинальный код, и почти любой пакер заканчивается таким.
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Ставим туда аппаратную точку останова bphws 45E7AE,x, запускаем и смотрим на eax:
eax = 0x40F844
Это тот же адрес, с которого стартовал пакер. Что произошло: он восстановил по адресу 40F844 исходные байты программы, перезаписав поддельный старт. Делаем шаг (F7), и мы в OEP.
Для подтверждения смотрим на код, который появился:
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 |Теперь перед нами связный пролог (push ebp / mov ebp,esp / add esp,-10h / push ebx), типичный для программы, написанной на Delphi/Borland, и читаемые строки. Мы в настоящем коде.
Совет: быстрый способ подтвердить OEP — проверить, имеет ли код смысл и появляются ли в дампе памяти читаемые строки программы. Стандартный пролог компилятора — очень хороший признак.
Восстановление IAT
Добраться до OEP недостаточно. Если снять дамп процесса сейчас, получившийся исполняемый файл не заработает, потому что его таблица импортов (IAT) не указывает на настоящие API.
Посмотрим на проблему в Scylla
Когда процесс остановлен на OEP, открываем Scylla из x32dbg, проверяем, что OEP равен 0x40F844, и вводим данные вручную: VA и Size. Чтобы узнать эти данные, посмотрим на IAT в дампе x32dbg.
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 ....Мы видим, что IAT начинается с VA = 0043B194 и заканчивается на 0043B390; Size получается вычитанием (0043B390 - 0043B194), что даёт Size = 0x1FC.
Одна деталь: нули, которые встречаются в таблице, — это просто разделители между DLL, а не потерянные записи.
Хорошо, теперь с имеющимися данными открываем Scylla и заполняем VA 0043B194 и Size 1FC, затем Get Imports, и он показывает нам результат:

- 75 корректных API.
- Две некорректные группы, по 42 и 1 записи, которые указывают на
0x022Axxxx. Эти адреса находятся в памяти, выделенной пакером, а не в DLL. Это перенаправления импортов.
Здесь видна полная IAT: и хорошие записи, и перенаправленные.
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>] |Хорошо видно, что вместо настоящего адреса API в IAT стоит обходной переход. Если снять дамп в таком виде, исполняемый файл будет указывать на область, которая существует только в живом процессе, и сломается при запуске.
Разберёмся с перенаправлением
Если дважды щёлкнуть по одному из некорректных слотов, мы увидим, что каждый указывает на небольшой 7-байтовый стаб:
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Решение: напишем скрипт, который читает стабы, в x32dbg
Хорошая новость в том, что настоящий адрес API находится внутри стаба: это непосредственный операнд (immediate) инструкции mov eax, .... Значит, мы можем пройти по IAT, прочитать этот операнд из каждого стаба и переписать слот настоящим API. IAT находится по адресу 43B194, размер 0x1FC (заканчивается на 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}"
retКак это работает, в двух словах:
- Проходит по каждому слоту IAT с шагом 4 байта, от
43B194до43B390. - Трогает только те слоты, значение которых попадает в диапазон
0x2200000–0x2300000, то есть в область стабов. Здоровые слоты остаются как есть. - Проверяет, что первый байт стаба —
B8, то естьmov eax, imm32. - Читает непосредственный операнд
[$stub+1]и записывает его в слот.
Внимание: диапазон стабов
0x0220xxxxзависит от твоего запуска. Посмотри, на какие адреса указывают твои некорректные слоты, и поправь границы в скрипте.
Запускаем скрипт, пока процесс остановлен на OEP.

Теперь возвращаемся в Scylla, заполняем поля и нажимаем Get Imports, а на всякий случай ещё и Show Invalid:

118 корректных API, 0 некорректных.
Группы, которые были красными, теперь разрешаются в kernel32.dll (42 + 1 запись).
Совет: прежде чем писать скрипт для починки, посмотри, какой шаблон у плохих слотов. Если названия настоящих API лежат в стабе открытым текстом, определить, куда указывает каждый слот, гораздо проще при восстановлении IAT.
Дамп и фикс
Пока процесс всё ещё остановлен на OEP, а IAT уже исправлена, снимаем дамп из Scylla:
- Проверь, что OEP равен
0x40F844(RVA0xF844), а IAT находится по адресу43B194с размером0x1FC. - Нажми Get Imports ещё раз и убедись, что некорректных записей нет.
- Нажми Dump и сохрани файл (например,
UnPackme01_dump.exe). - Нажми Fix Dump и выбери этот файл. Scylla создаёт копию с восстановленными импортами (
UnPackme01_dump_SCY.exe). Всегда используй именно этот файл, а не файл с предыдущего шага. - (Необязательно) Если хочешь, открой результат в hex-редакторе или в PE-bear и приведи сигнатуру в смещении 0 к
MZ. Это косметика: загрузчик Windows от неё не зависит. Чтобы проверить, что распаковка удалась:
- Когда мы открываем файл в Detect It Easy, PECompact больше не появляется, а компилятор теперь отображается чётко (Delphi/Borland).

- Когда мы загружаем его в x32dbg, точка входа уже
0x40F844, а код читаем, без стабов. - Когда мы запускаем его в виртуальной машине, мы убеждаемся, что он стартует точно так же, как оригинал.
Совет: если дамп не открывается или падает при запуске, сначала проверь IAT (это самая частая причина), а затем значение OEP в Scylla.
Итог: как мы это решили
- Аппаратная точка останова на исполнение на обработчике SEH (
45E6EC) и Shift+F9, чтобы передать исключение. - Точка останова на
45E70F(настоящее начало стаба). - Распаковщик (
call ecxпо адресу45E76D) и вторая стадия (call ediпо адресу45E78A). В детали не вдаёмся. - Аппаратная точка останова на
45E7AE(jmp eax):eax— это OEP. - Исправить IAT скриптом (слоты
0x0220xxxxуказывают на стабыmov eax, API / jmp eax). - Scylla: Get Imports, Dump и Fix Dump.
А что дальше?
Если бы это было вредоносное ПО, то с уже распакованной программой работа по анализу только начинается.
