Disclaimer
Эту заметку я обещал еще ранней весной написать для третьего номера отличного журнала No Bunkum. Но получилось как обычно - сначала я был дико занят, а потом похоже у авторов издания бензина хватило только на два номера. Надеюсь они не сильно обидятся, если я опубликую эти малосвязные жалкие заметки в своем блогеIntro
"Зачем, зачем же еще один сплайсер !" - я прямо слышу ваши трагичные стенания, когда вы читаете это. В самом деле, несмотря на то что платформа win x64 еще не слишком стара, в интернете уже можно найти некоторое количество вполне работающих сплайсеров - например Mini Hook-Engine. Тем не менее я решил что мне лично нужен свой самобытный, который бы- умел работать как в user mode, так и в kernel mode (кстати сорцы последнего я вам не дам - мало ли чего)
- был лишен некоторых дефектов существующих сплайсеров
- в силу врожденной вредности и синдрома Not Invented Here
- в ней слишком мало места для вставки переходника - вторая же инструкция имеет обращение к глобальной переменной относительно RIP и будучи перенесенной в память по другому адресу будет выполнена неправильно
- такие ситуации не обрабатываются - можно было вернуть ошибку "Cannot hook"
- это далеко не единственная ситуация, когда сплайсинг невозможен - например могут попасться другие инструкции, работающие с адресацией относительно RIP. На ум сразу приходит семейство jmp/jXX и call
- умел анализировать код, который сплайсит и говорить вердикт - можно/не можно. Даже в user mode крайне неприятно когда угнанная функция падает при выполнении, а уж в kernel mode это фатально
- все же смог выполнить сплайсинг этой несчастной MessageBoxW - мы же должны быть хоть чуть-чуть лучше аналогов, не так ли ? И кстати эта задача вполне достижима - нужно только изыскать внутренние резервы
- в военное время синус может принимать значение 3. В данном случае изысканными резервами вполне может служить неиспользуемое пространство между функциями. Дело в том что нам нужно например 8 байт для хранения адреса перехода. А под x64 функции обычно выравнены на 16 (почти 50% вероятность успеха) или на 32 байта (почти 75% вероятность успеха)
Код сплайсера лежит целиком в паре файлов - splicer_x64.cpp и написанный на ассемблере ms.nas
ms.nas
Я решил что было бы недурно иметь следующую структуру функций-перехватчиков - в начале идет структура x64_thunk, описывающая сам hook, затем общий для всех перехватчиков код. Структура x64_thunk (описана в файле cmn_stub.inc) имеет следующие поля:- orig_addr - содержит адрес исходной функции - мало ли вам захочется сделать unsplicing например
- size - число перемещенных байт исходной функции. Как видите при желании имея эти два поля можно сделать и обратную операцию
и вобще это не я и ничего не было - hook_addr - адрес функции-перехватчика. Описана как typedef void (*x64_hook)(PVOID *stack, struct x64_thunk *this_hook)
- jmp_addr - адрес перемещенных байт, куда управление попадает после вызова hook_addr. К концу этого набора байт добавлен переходник куда-нть в середину оригинальной функции
- user_data - просто указатель, может использоваться для чего угодно. Например для передачи данных в x64_hook
dyn_hook:
; грузим в rax адрес нашей структуры x64_thunk
; которая находится непосредственно перед dyn_hook,
; относительно RIP, который во время исполнения
; указывает на адрес следующей инструкции (.next)
lea rax, qword [rip + hook_s - .next]
.next
; проверим x64_thunk.hook_addr - если равен нулю - не вызывать перехватчик
mov r11, [rax + hook_addr]
test r11, r11
jz .skip
; сохраним регистровые аргументы в shadow stack space
mov [rsp + 0x8], rcx
mov [rsp + 0x10], rdx
mov [rsp + 0x18], r8
mov [rsp + 0x20], r9
; перед вызовом x64_thunk.hook_addr заполним аргументы
; поместим первый аргумент в rcx - stack addr
; поместим второй аргумент в rdx - указатель на x64_thunk
mov rcx, rsp
lea rdx, qword [rip + hook_s - .next2]
.next2:
; сделаем новый stack shadow space
sub rsp, 32
; вызов x64_thunk.hook_addr
call r11
; восстановим stack shadow space
add rsp, 32
; восстановим оригинальное значение регистров
mov rcx, [rsp + 0x8]
mov rdx, [rsp + 0x10]
mov r8, [rsp + 0x18]
mov r9, [rsp + 0x20]
; снова грузим адрес структуры x64_thunk - ведь rax наверняка был испорчен
; внутри
x64_thunk.hook_addr
lea rax, qword [rip + hook_s - .skip]
.skip:
; и улетаем на x64_thunk.jmp_addr
jmp [rax + jmp_addr]
Как видите никакой черной магии здесь нет и все довольно просто (если вы знаете x64 calling convention)
Warning ! как вы могли бы заметить, я использую в этом переходнике shadow stack space сплайснутой функции, а также сохраняю только регистры с аргументами - т.е. этот переходник годится только для сплайсинга кода в самом начале функций. Если вы хотите иметь возможность сплайсинга в середину - вам потребуется написать собственные версии переходников, которые бы сохраняли все нужные регистры
splicer_x64.cpp
Содержит все остальное - некоторое количество разнообразных вариаций передачи управления, класс x64_stoled_bytes_storage для хранения тел переходников и перемещенных кусков сплайснутых функций (на самом деле они хранятся сразу же после переходника из ms.nas) и собствено код сплайсера - состоит из двух методов:- check(PBYTE addr, int is_start) - проверяет может ли быть успешен сплайсинг некоторой функции по адресу addr. is_start - не является ли этот адрес началом некоторой функции, тогда мы можем поискать в aligned bytes дополнительное место. В случае успеха заносит в переменную класса x64_splicer.m_copy_bytes число перемещаемых байт
- hook(PBYTE addr, x64_hook func) - делает собственно сам сплайсинг. func - это ваша функция-перехватчик
main.cpp
В качестве демонстрационного примера был написан простой примерчик использования данного сплайсера в user mode - просто ставит сплайсом хуки на 4 функции из user32.dll:- MessageBoxA
- MessageBoxW (надо же проверить что yes, we can)
- GetDesktopWindow
- GetShellWindow
проэкт скомпилился тока в release-mode, в дебуг-моде не линкуется. запустил на win7_x64 в консоли под админом, сначала выплыла консольная мессага "GetDesktopWindow not found", затем системная "splicer.exe has stopped working". закомментил запуск всех хученных функций - появилась мессага "user32.dll not found". чертовщина!!!
ОтветитьУдалить> в консоли
ОтветитьУдалитьпопробуй загрузить в начале примера user32.dll явно через LoadLibrary
на что линкер в debug-mode ругается ?
привет. к сожалению, на рабочем компе стоит WinXP_32, компилировать не получится, расскажу для чего мне нужен сплайсинг x64. я автор небольшой сетевой утилиты: http://netmontools.com,
ОтветитьУдалитьтак вот для захвата Ethernet фреймов я использую слегка изменённый и переименованный драйвер ndisprot.sys из WDK. по понятным причинам на Win7_x64 лрайвер не встаёт, т.к. нет цифровой подписи. в то же время в каждой Win7_x64 системе установлен микрософтовский ndisprot.sys. вот если бы его хукнуть, при помощи вашего сплайсера! моих познаний хватило только на пересборку сэмплов из WDK.
ой-вэй как все запущенно
ОтветитьУдалитьво первых я настоятельно рекомендую не лезть в kernel-mode, если познаний хватает только на пересборку примеров из wdk
во вторых - даже чтобы сплайснуть уже готовый драйвер в kernel mode - нужно уже иметь свой код в kernel mode
в третьих - сплайсинг - в общем случае дико ненадежная технология, и должна применяться только в случаях, когда ничего больше не помогает. В данном случае есть альтернативы - например подписать свой драйвер либо для висты/w7 использовать windows filtering platform из user-mode
спасибо. отрезвляет. в моём случае есть ещё альтернатива - использовать библиотеку WinPCap, они как-то умудрились подписать свой драйвер.
ОтветитьУдалить