Пропустить навигацию

Обработка исключений в 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

Комментариев: 3

  1. Надо же. Не далее как 2 недели назад делали общий разбор этой темы. Хорошая синхронизация, лол.

    >_RTL_INVERTED_FUNCTION_TABLE
    Структуру с настоящими именами полей можно взять в wrk или приватных символах висты (да-да, 32х-битной): http://pastebin.com/WrdTVzBV

    >ULONG MaxCount; // 0xA0 – win xp x64
    ULONG MaximumSize; // 0×200 – w7 sp0 x64, wVi sp1 x32

    >Предположительно нужно сначала удалить запись о модуле из кэша LdrpInvertedFunctionTable
    Похоже на то. Посмотрев на это поближе, построили такой путь для общего случая:
    1) Сделать копию таблицы функций модуля и добавить в неё нужные элементы, упорядочить по желанию.
    2) Исправить Exception Directory модуля (необходимо для случая с Overflow, когда наш модуль не поместился в LdrpInvertedFunctionTable).
    3) Добавить исправленную таблицу функций в качестве динамической.
    4) Искать в секции данных ntdll структуру _INVERTED_FUNCTION_TABLE_ENTRY, соответствующую нашему модулю. Эта структура будет являться частью массива LdrpInvertedFunctionTable.TableEntry[0xA0|0x200].
    5) Если структура не найдена, то можно попытаться выполнить поиск ещё несколько раз – возможно нам не повезло и LdrpInvertedFunctionTable была подвергнута изменениям во время поиска из-за выгрузки модулей другими потоками. Если так и не получится её найти, то, вероятно, у нас случай с Overflow и нашего модуля нет в таблице. В этом случае больше ничего не нужно делать – всё должно работать. Можно даже удалить наш элемент из списка динамических таблиц.
    6) Если структура найдена, то нужно просто обнулить поле _INVERTED_FUNCTION_TABLE_ENTRY.FunctionTable. Т.к. защёлки для таблицы не используем, то она может измениться другими потоками – поэтому для безопасного обнуления можно воспользовать lock cmpxchg8b (в случае фейла на последнем моменте придётся вернуться к пункту 4).
    7) Пункт 6 безопасен, но не 100% надёжен. Другой поток мог считать старое значение нашей FunctionTable и перезаписать новое (нулевое), если он в это время перемещал данные таблицы. Это привело бы к отмене нашего фикса. Здесь “?”. Разве что подождать немного и проверить, не откатились ли изменения? Всё-таки не получается пока fool-proof решения.

    Xref: http://www.nynaeve.net/?p=113 [warn: определения некоторых структур слегка изменились с тех пор].

    • По поводу 6-го пункта
      1) На xp x64: Воспользоваться RtlAcquireResourceExclusive для ресурса
      LdrpInvertedFunctionTableResource, как это делается в ntdll, тогда пункт 6 отпадает. Только возникает опять же проблема поиска переменной LdrpInvertedFunctionTableResource.
      BOOLEAN
      RtlAcquireResourceExclusive(
      IN PRTL_RESOURCE Resource,
      IN BOOLEAN Wait
      );
      2) На win7 x64. LdrpInvertedFunctionTableSRWLock с помощью RtlAcquireSRWLockExclusive

  2. Да, материал нужен и интересен. Особенно тем у кого пока нет времени расковырять x64 самому, а иметь четкое представление нужно.


Добавить комментарий

Fill in your details below or click an icon to log in:

Логотип WordPress.com

You are commenting using your WordPress.com account. Log Out / Изменить )

Фотография Twitter

You are commenting using your Twitter account. Log Out / Изменить )

Фотография Facebook

You are commenting using your Facebook account. Log Out / Изменить )

Connecting to %s

Follow

Get every new post delivered to your Inbox.