Распаковка шаг за шагом: 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 в памяти занимает гораздо больше места, чем на диске.

СекцияVAVirtual SizeRaw SizeЭнтропия
CODE0x10000x5B0000x294008
.rsrc0x5C0000x30000x28005.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 и смотрим первые инструкции точки входа:

[ ASM ][Копировать]
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                                          |

Разберём это внимательно, потому что это сердце пакера:

  1. Первые три инструкции устанавливают собственный обработчик исключений (SEH): помещают в стек адрес обработчика 45E6EC и цепляют его в fs:[0].
  2. xor eax,eax обнуляет eax, а mov [eax],ecx пытается записать по адресу 0. Это намеренно вызывает access violation.
  3. Всё, что идёт дальше (outsd, insd, arpl…), — это мусор, который никогда не исполняется линейно. Он нужен, чтобы сломать статический анализ: IDA не следует реальному потоку, и листинг выглядит как бессмысленный код. Обработчик находится по адресу 0x45E6EC, внутри .rsrc. Если его прочитать, мы увидим, что он:
  • перезаписывает инструкцию, которая упала 40F85A, на jmp rel32 к 0x45E70F,
  • и возвращает ExceptionContinueExecution. Windows возобновляет исполнение по тому же адресу, 40F85A, который теперь уже не запись, а переход в стаб. Это самомодифицирующийся код, запускаемый намеренным исключением.

Совет: когда увидишь, что fs:[0] изменяется в начале программы, будь настороже. В точке входа собственный SEH плюс инструкция, которая гарантированно падает, — обычно механизм передачи управления у пакера.


Пропустить исключение, не потеряв контроль

Мы хотим пройти через исключение и следовать потоку пакера, а не ntdll. Для этого делаем следующее:

  1. Ставим аппаратную точку останова на исполнение на обработчик: bphws 45E6EC,x.
  2. Запускаем клавишей F9. Отладчик останавливается на access violation по адресу 40F85A.
  3. Передаём исключение программе клавишами Shift+F9. Аппаратная точка останова срабатывает на 45E6EC.
  4. Ставим обычную точку останова на 45E70F, настоящее начало стаба, и продолжаем клавишей F9. Так мы пропускаем всю механику ntdll и попадаем точно в начало стаба.

Совет: используй аппаратные точки останова, когда можешь. Они не изменяют память, поэтому пакер, который сканирует собственный код в поисках байтов 0xCC, их не обнаружит.


Как добраться до OEP

Если пролистать немного вниз, видно, как стаб восстанавливает исходные регистры серией pop и заканчивается вычисляемым переходом: jmp eax по адресу 0x45E7AE. Это tail jump, финальный переход на оригинальный код, и почти любой пакер заканчивается таким.

[ ASM ][Копировать]
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.

Для подтверждения смотрим на код, который появился:

[ ASM ][Копировать]
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.

[ DUMP 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: и хорошие записи, и перенаправленные.

[ ASM ][Копировать]
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-байтовый стаб:

[ ASM ][Копировать]
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).

[ SCRIPT ][Копировать]
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

Как это работает, в двух словах:

  1. Проходит по каждому слоту IAT с шагом 4 байта, от 43B194 до 43B390.
  2. Трогает только те слоты, значение которых попадает в диапазон 0x2200000–0x2300000, то есть в область стабов. Здоровые слоты остаются как есть.
  3. Проверяет, что первый байт стаба — B8, то есть mov eax, imm32.
  4. Читает непосредственный операнд [$stub+1] и записывает его в слот.

Внимание: диапазон стабов 0x0220xxxx зависит от твоего запуска. Посмотри, на какие адреса указывают твои некорректные слоты, и поправь границы в скрипте.

Запускаем скрипт, пока процесс остановлен на OEP.

Окно программы с сообщением об успехе

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

Окно программы с сообщением об успехе

118 корректных API, 0 некорректных.

Группы, которые были красными, теперь разрешаются в kernel32.dll (42 + 1 запись).

Совет: прежде чем писать скрипт для починки, посмотри, какой шаблон у плохих слотов. Если названия настоящих API лежат в стабе открытым текстом, определить, куда указывает каждый слот, гораздо проще при восстановлении IAT.


Дамп и фикс

Пока процесс всё ещё остановлен на OEP, а IAT уже исправлена, снимаем дамп из Scylla:

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

Окно программы с сообщением об успехе

  • Когда мы загружаем его в x32dbg, точка входа уже 0x40F844, а код читаем, без стабов.
  • Когда мы запускаем его в виртуальной машине, мы убеждаемся, что он стартует точно так же, как оригинал.

Совет: если дамп не открывается или падает при запуске, сначала проверь IAT (это самая частая причина), а затем значение OEP в Scylla.


Итог: как мы это решили

  1. Аппаратная точка останова на исполнение на обработчике SEH (45E6EC) и Shift+F9, чтобы передать исключение.
  2. Точка останова на 45E70F (настоящее начало стаба).
  3. Распаковщик (call ecx по адресу 45E76D) и вторая стадия (call edi по адресу 45E78A). В детали не вдаёмся.
  4. Аппаратная точка останова на 45E7AE (jmp eax): eax — это OEP.
  5. Исправить IAT скриптом (слоты 0x0220xxxx указывают на стабы mov eax, API / jmp eax).
  6. Scylla: Get Imports, Dump и Fix Dump.

А что дальше?

Если бы это было вредоносное ПО, то с уже распакованной программой работа по анализу только начинается.