Skip navigation

Изрядно надоело при старте процесса в WinDbg каждый раз ставить брейк на kernel32!BaseProcessStart (для xp) или kernel32!BaseThreadInitThunk (windows 7) для того, чтобы попасть на точку входа отлаживаемой проги. Для решения задачи был написан такой вот скрипт

http://pastebin.com/mBiRewiq

В качестве бонуса стоппится и на tls калбеках тоже. Запуск скрипта производится из командной строки windbg при первой остановке на ntdll!DbgBreakPoint (LdrpDoDebuggerBreak):

$$><имя_скрипта

Размещать скрипт в каталоге windbg.

Примечание1: после расстановки брейков на калбеках и точке входа происходит запуск (g).

Примечание2: скрипт предназначается только для 64-битных процессов, для 32 битов нужна коррекция

В продолжение вот этой темы.

Как отлаживать 32-х битный код в 64-битной системе

Я упомянула логирование wow64. Это удобно для отладки (например видно сервис из какой таблицы возвратил ошибку), если нет нормальных символов. Разреверсила передаваемые аргументы  функциям в wow64log.dll и написала простой логировщик.

Прошу потестить обладателей 64-битной винды. wow64log.dll должна быть помещена в директорию \windows\system32. Затем запускаете под windbg любое 32-битное приложение и лицезреете лог.  Кстати, небольшое замечание. В wow64log.dll можно импортировать функции только из ntdll, так как иначе приложение будет угроблено.

Вот так выглядит вывод под windbg.

Download

В предыдущей записи было обсуждение каким образом можно установить свой обработчик.  Собственно код+экзе [в посте он бы выглядел просто отвратительно] для 64-бит

http://www.sendspace.com/file/yf28iu

Коротко о том, что я делаю. Получаю адреса лока для LdrpInvertedFunctionTable и самой LdrpInvertedFunctionTable. Для поиска переменных применяю дизассемблер distorm (его либу, кстати надо пересобирать под amd64).Затем в блокирую обращения к таблице. Ищу свой модуль, удаляю инфу о нем из кеша, обнуляю в пе-заголовке IMAGE_DIRECTORY_ENTRY_EXCEPTION. Разблокирую кеш. Добавляю свой обработчик, копирую старую ExceptionDirectory. После этого небольшая проверка на сохранность старых хендлеров и вызов нового.
Тест был на vista,7,xp (64).

Distorm
http://ragestorm.net/distorm/

скрины тестов

win7

winxp

vista

Обработка исключений в x64 [usermode].
Небольшие наброски по теме. Возможно, позже оформлю это в виде полноценной статьи [после внесения необходимых уточнений], если, конечно, кому-то нужно.

Начинается все с функции KiUserExceptionDispatcher. Если у нас 32-битный процесс, то происходит вызов функции
wow64.dll!Wow64PrepareForException

.text:0000000077EF31C0 KiUserExceptionDispatcher: ; DATA XREF: .text:off_77F03698o
.text:0000000077EF31C0 ; .data:RtlpFunctionAddressTableo
.text:0000000077EF31C0 mov rax, cs:Wow64PrepareForException
.text:0000000077EF31C7 test rax, rax
.text:0000000077EF31CA jz short loc_77EF31DB
.text:0000000077EF31CC mov rcx, rsp
.text:0000000077EF31CF add rcx, 4D0h
.text:0000000077EF31D6 mov rdx, rsp
.text:0000000077EF31D9 call rax ; Wow64PrepareForException
.text:0000000077EF31DB
.text:0000000077EF31DB loc_77EF31DB: ; CODE XREF: .text:0000000077EF31CAj
.text:0000000077EF31DB mov rcx, rsp
.text:0000000077EF31DE add rcx, 4D0h

Данная функция преобразует структуру CONTEXT для 32-битного процесса.

Затем вызывается RtlDispatchException.

BOOLEAN
RtlDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PCONTEXT ContextRecord
);

Внутри RtlDispatchException первым делом вызываются все инсталлированные векторные обработчики исключений
text:0000000077EE5F27 mov [rax-28h], r14
.text:0000000077EE5F2B lea r8, RtlpCalloutEntryList
.text:0000000077EE5F32 mov rbx, rdx
.text:0000000077EE5F35 mov [rax-30h], r15
.text:0000000077EE5F39 mov rsi, rcx
.text:0000000077EE5F3C mov [rsp+6A8h+var_668], 0
.text:0000000077EE5F41 call RtlpCallVectoredHandlers

Вызывается функция RtlLookupFunctionEntry, в ней проверяется кэш LdrpInvertedFunctionTable.
Его формат

typedef struct _RTL_INVERTED_FUNCTION_TABLE
{
ULONG Count; // число структур RTL_INVERTED_FUNCTION_TABLE_ENTRY
ULONG MaxCount; // 0xA0 — win xp x64
ULONG Pad[ 0x2 ];
RTL_INVERTED_FUNCTION_TABLE_ENTRY Entries[ ANYSIZE_ARRAY ];
} RTL_INVERTED_FUNCTION_TABLE, * PRTL_INVERTED_FUNCTION_TABLE;

typedef struct _RTL_INVERTED_FUNCTION_TABLE_ENTRY
{
PIMAGE_RUNTIME_FUNCTION_ENTRY ExceptionDirectory; // виртуальный адрес .pdata (обычно)
PVOID ImageBase; // базовый адрес модуля
ULONG ImageSize; // размер образа
ULONG ExceptionDirectorySize; // размер .pdata
} RTL_INVERTED_FUNCTION_TABLE_ENTRY, * PRTL_INVERTED_FUNCTION_TABLE_ENTRY;
Каждый модуль, подгруженный в АП процесса имеет свою запись в RTL_INVERTED_FUNCTION_TABLE. Если для адреса, по которому прозошло исключение, найден обработчик(и) он вызывается. В секции .pdata, на которую ссылаются элементы LdrpInvertedFunctionTable содержится массив структур типа

typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {
DWORD BeginAddress; // начальный адрес контролируемой функции [RVA]
DWORD EndAddress; // конечный адрес контролируемой функции [RVA]
DWORD UnwindInfoAddress; // RVA структуры UNWIND_INFO
} _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;

То есть указывается диапазон контролируемых адресов и привязанный к нему обработчик исключений [UNWIND_INFO].
Теперь чуть подробнее расскажу о LdrpInvertedFunctionTable.

Информация о модуле добавляется в кеш функцией LdrpInsertMemoryTableEntry, которая вызывает RtlInsertInvertedFunctionTable, а та в свою очередь добавляет элемент в таблицу LdrpInvertedFunctionTable.
Первая запись в кеш добавляется при инициализации процесса

.text:0000000077ED54ED
.text:0000000077ED54ED loc_77ED54ED: ; CODE XREF: LdrpInitializeProcess-3C0C1j
.text:0000000077ED54ED bts r8d, 0Eh
.text:0000000077ED54F2 mov rcx, rbx
.text:0000000077ED54F5 mov [rbx+68h], r8d
.text:0000000077ED54F9 call LdrpInsertMemoryTableEntry

Поэтому, кстати говоря, если мы просканим LdrpInvertedFunctionTable, то наш исполняемый модуль там будет присутствовать.
Затем, следующие записи добавляются при подгрузке динамических библиотек.

.text:0000000077F15B9D loc_77F15B9D: ; CODE XREF: LdrpMapDll+66Cj
.text:0000000077F15B9D ; LdrpMapDll+677j …
.text:0000000077F15B9D mov r8d, [rsi+40h]
.text:0000000077F15BA1 mov rdx, [rsi+30h]
.text:0000000077F15BA5 lea rcx, LdrpInvertedFunctionTable
.text:0000000077F15BAC call RtlInsertInvertedFunctionTable

Удаляет элемент функцией RtlRemoveInvertedFunctionTable при выгрузке динамической библиотеки.

Теперь вернемся к RtlLookupFunctionEntry. После проверки кешей вызывается функция RtlpLookupDynamicFunctionEntry.
Она проверяет таблицу RtlpDynamicFunctionTable на предмет обработчиков. В эту таблицу можно добавлять свои таблицы, вызывая функции

BOOLEAN WINAPI RtlInstallFunctionTableCallback(
__in DWORD64 TableIdentifier,
__in DWORD64 BaseAddress,
__in DWORD Length,
__in PGET_RUNTIME_FUNCTION_CALLBACK Callback,
__in PVOID Context,
__in PCWSTR OutOfProcessCallbackDll
);

BOOLEAN WINAPI RtlAddFunctionTable(
__in PRUNTIME_FUNCTION FunctionTable,
__in DWORD EntryCount,
__in DWORD64 BaseAddress,
__in ULONGLONG TargetGp
);

.text:0000000077F3A1D3 dyn_look: ; CODE XREF: RtlLookupFunctionEntry+B1j
.text:0000000077F3A1D3 mov rdx, rsi
.text:0000000077F3A1D6 mov rcx, rbx
.text:0000000077F3A1D9 call RtlpLookupDynamicFunctionEntry
.text:0000000077F3A1DE mov r8, rax
.text:0000000077F3A1E1 jmp loc_77EE5BDA

При этом надо учитывать, что динамическая таблица действительна только для кода, который не принадлежит исполняемому модулю
или он не имеет exception directory. Так я лично проводила следующий тест. Вызывала функцию RtlInstallFunctionTableCallback для
своего модуля с необнуленной exception directory. Затем, удостоверившись в успехе выполнения функции, вызывала исключение, калбек не срабатывал.
После обнуления exception directory в pe-файле на диске callback стал работать. Того же эффекта можно добиться если мы выделим память с помощью VirtualAlloc и вызовем
исключение в этом выделенном регионе. Кстати косвенно об этом говорится у ms

http://msdn.microsoft.com/en-us/library/ms680595(v=VS.85).aspx

«This function is useful for very dynamic code». Именно эта фраза и навела меня на мысль, что для кода у которого есть exception directory она может быть неприменима.

Насчет динамического обнуления exception directory. Предположительно нужно сначала удалить запись о модуле из кэша LdrpInvertedFunctionTable, затем обнулить exception directory.
Однако, я пока нормального способа поиска LdrpInvertedFunctionTable не представляю. Учитывая ссылки внутри ntdll на эту переменную:

Up o LdrpInsertMemoryTableEntry+9E
Up o RtlInsertInvertedFunctionTable+896
Up o LdrUnloadDll-19287
Up o RtlRemoveInvertedFunctionTable+1E
Up r RtlRemoveInvertedFunctionTable+51
Up w RtlRemoveInvertedFunctionTable+5C
Up o LdrUnloadDll-19124
Up o LdrpMapDll+36E05
Up r RtlLookupFunctionTable+4C413
Up r RtlInsertInvertedFunctionTable+36F67
Up w RtlInsertInvertedFunctionTable+37079
Up r RtlRemoveInvertedFunctionTable+33711
Up o LdrUnloadDll+1D80E

Из RtlDispatchException вызывается функция RtlpExecuteHandlerForException, обычно для эксепшнов вызывается

kernel32!__C_specific_handler

Все тесты проводились на win xp x64

Руки дошли до плагина (https://lhc645.wordpress.com/2010/02/26/плагин-к-livekd/).

Исправила багу при первом обращении к плагину. + добавила команду didt для входов idt.

скачать (fixed:26.03.2010 — теперь на code.google.com =))

http://code.google.com/p/dgdt/downloads/list

Была задача добавления tls директории в файлы PE32+. В win xp 64 все нормально, а в windows 7 — софт падал. Виновником оказался флаг IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE  в OptionalHeader.DllCharacteristics. То бишь файл поддерживает ASLR. Соответственно, сброс этого флага решает проблему и файлы с новой tls загружаются нормально.

В комментариях к этой записи обсуждалось, что дескать команда !pcr слегка глючит на x64.

https://lhc645.wordpress.com/2009/09/04/idt-x64-win-xp-theory/

Я там сначала пропатчила, а вот пару дней назад решила сделать исправление в виде плагина.  Ну и еще хотелось сделать просмотр входов GDT. Т.к. каждый раз вводить dt _KGDTENTRY64 адрес довольно утомительно.

скопировать dgdt.dll нужно в каталог \Debugging Tools for Windows (x64)\winext

затем подгрузить расширение командой .load dgdt

!dgdt номер — информация о дескрипторе №(номер) (считая от 0)

!pcr2 номер_процессора — информация о pcr на процессоре с заданным номером

Выгружается расширение командой .unload

сама dll

http://www.sendspace.com/file/fwynjl

Краткая справка. Механизм ASLR — Address Space Layout Randomization (случайное расположение в адресном пространстве) появился впервые в XP и затрагивал PEB и TEB only. Начиная с Vista сабж уже стал применяться к базам загрузки исполняемых файлов и длл (в частности системных  ntdll, kernel32 etc), а также к стеку и куче. Для XP можно было для получения такой возможности юзать какой-нибуть аналог линуксового PaX под винду (BufferShield, например).

ASLR разрабатывался как средство для предотвращения атак типа ret-to-libc (адрес искомой функции при рандомной базе загрузки либы становится трудновычислим).

Довелось столкнуться с сабжем и заинтересовало как оно работает.

Как оказалось, выбор базы загрузки exe происходит следующим образом (ring0)

NtCreateSection ->  MmCreateSection -> MiRelocateImage (MiRelocateImageAgain) -> MiSelectImageBase

Основная работа по выбору базы загрузки проводится в функции MiSelectImageBase. В зависимости от того с чем имеем дело, с dll или с exe имеем разный алгоритм.  «Случайность» для базы во втором случае достигается путем использования rdtsc. Для dll при вычилениях используется переменная MiImageBias (причем при ее инициализации в функции (MiInitSystem->)MiInitializeRelocations тоже юзается rdtsc)

Чтобы мне не повторяться, псевдокод функции MiSelectImageBase можно посмотреть в статье  (как оказалось, сабж полностью расковыряли до меня) Bypassing Browser Memory Protections

http://taossa.com/archive/bh08sotirovdowd.pdf

В ней кстати не только про ASLR (дока описывает также механизмы GS, SafeSEH и DEP).

заметка  proc

Продолжаю писать про особенности 64-битного режима.
На этот раз, речь пойдет об относительной адресации и соответственно, специфики работы с всяческими неэкспортируемыми символами в ядре. Итак,

RIP-relative addressing

Адрес формируется путем прибавления смещения к адресу следующей инструкции. Пример.
Секция .data ntoskrnl, переменная PsInvertedFunctionTable.

.data:00000000005748B0 PsInvertedFunctionTable dd 0

Берем какую-нибудь функцию, в которой есть ссылки на искомую переменную. Пусть это будет RtlInsertInvertedFunctionTable.

.text:0000000000417CB0 mov [rsp+arg_0], rcx
.text:0000000000417CB5 sub rsp, 48h
.text:0000000000417CB9 mov ecx, cs:PsInvertedFunctionTable
.text:0000000000417CBF mov [rsp+48h+var_10], rsi

Однако, как раньше мы не можем взять адрес переменной из инструкции mov ecx (как это было на x86). У нас есть только 32-х битное смещение данной переменной относительно следующей за mov ecx инструкции.
Значит, чтобы получить нужный нам адрес

PsInvertedFunctionTableAddress = 0000000000417CBF (адрес следующей инструкции) + 0x15CBF1

0x15CBF1 — есть смещение (опкод mov ecx,смещение — 8B 0D F1 CB 15 00)

Получаем 00000000005748B0 — адрес PsInvertedFunctionTable.

заметка endp

Доступен only 2-х байтовый вариант, однобайтовая недоступна (в compatibility mode доступна).

Example

Instruction     | Opcode
______________________
inc cl             | FE C1

inc cx            | 66 FF C1

inc ecx          | FF C1

inc rcx          | 48 FF C1

Подробнее следует сказать о REX — префиксах (в варианте с rcx юзается)

Но сначала о формате команды, чтобы знать где префикс REX располагается. Значит инструкция может быть от 1 до 15 байт

Состоит из следующих полей (от старшего к младшему)

  • Legacy Prefix
  • REX Prefix
  • Opcode (1-2 байта)
  • ModRM
  • SIB
  • Смещение (1,2,4 или 8 байт)
  • Непосредственный операнд (1,2,4 или 8 байт)

REX — префиксы (40h-4fh). Они позволяют

  1. использовать дополнительные регистры общего назначения (GPR) r8-r15 и xmm регистры (xmm8-xmm15).
  2. использовать 64-битный размер операнда
  3. позволяют использовать cr8-cr15 регистры и dr8-dr15

Формат REX префикса
Название биты значение/описание

  • 7-4 0100
  • REX.W 3 Размер операнда (0 — по умолчанию, 1 — 64 бита)
  • REX.R 2 Старший бит поля reg байта режима адресации (он же ModRM), позволяет  использовать 16 регистров (поле reg — 3 байта + 1 байт, 4 байта => 16 регистров)
  • REX.X 1 расширение поля index байта SIB (если без аббревиатуры и по русски — байт масштаба, индекса и базы) -старший бит, также для того, чтобы можно было юзать 16 регистров
  • REX.B 0 старший бит поля r/m байта ModRM, SIB.base или поля reg опкода — для доступа к 16-ти регистрам

Хм, ну после того, как становиться известен диапазон rex префиксов — очевидно почему нельзя использовать однобайтовые inc ;)
Подробнее об этом обо всем в AMD64 Programmer’s Manual, Volume 3: General-Purpose and System Instructions