В данном топике будут рассматриваться всевозможные патчи кода, как частный случай это хотпатч и сплайсинг. Все вопросы по патчам задаются теперь именно тут и нигде больше, не зависимо от имён апи, мода и пр. - В данном случае патч - изменение кода в памяти в пределах кодовых секций модулей(R/E). Далее краткое описание. 1. Защита. Обычно кодосекции защишены от записи. В юзермоде используется сервис NtProtectVirtualMemory, либо обёртка для него VirtualProtect(): Код (Text): push esp ;адрес переменной, в нее возвращается "старый" режим доступа push 40h ;режим доступа (нам нужен 40h) push 2000h ;размер области памяти в байтах push eax ;адрес области памяти, чьи атрибуты страниц нужно изменить call VirtualProtect Второй способ это замена страниц на R/W проекцию, но это большинству не нужно. В ядре возможности гораздо большие - можно отключить защиту записи сбросом бита WP(10000000000000000B) в регистре управления Cr0: Код (Text): mov eax, cr0 mov ebx, eax ;сохраняем начальное значение WP and eax,0xFFFEFFFF ;сбрасываем WP mov cr0, eax ;загружаем в CR0 Можно изменить флаг RW в PTE. Легальный и надёжный способ - описать регион через MDL и изменить его атрибуты посредством MmProtectMdlSystemAddress. Также можно создать вторую PTE для физической страницы и писать во вторую проекцию. 2. Хотпатч. Большинство функций имеют в своём начале двубайтную холостую инструкцию(mov edi,edi), перед которой имеется 5 инструкций nop(0x90). Вначале на место нопов записывается безусловное ветвление(может быть процедурным), после этого в пролог записывается короткое безусловное ветвление(2 байта) на место первой инструкции. Это поддерживает ядро. http://www.codebreakers-journal.com/content/view/113/27/ В общем случае это запись короткого безусловного ветвления(мост) на ветвление на хэндлер(near, far) в пределах +/-127 байт. o Как сделать такое же в своем модуле? Ключ компилятора cl: /hotpatch (генерит mov edi,edi) Ключ линкера ms link: /functionpadmin[:space], где space - минимальное число нопов между функциями. 3. Сплайсинг. Записывается в начало процедуры, либо в необходимое место кода ветвление на хэндлер. При этом возникают проблемы потоками. Если ветвление занимает размер болеший чем размер изменяемой инструкции, то изза изменённых следующих инструкций потоки прерванные на них далее отработают не корректно. Чтобы избежать этой ситуации используется енум всех потоков процесса(U-mode) и поправка их Ip. Потоки енумяться и Ip каждого проверяется на вхождение в изменяемый диапазон кода. Далее можно поступить несколькими способами: o Отпустить поток и выполнить не большую задержку. o Скопировать оригинальный код в буфер и при вхождении Ip в изменяемый диапазон выполнять редирект потока на буфер. Тоесть просто прибавить к Ip разность базы буфера и целевой процедуры. http://www.wasm.ru/article.php?article=apihook_1 4. Останов потоков и их енум. Для останова можно использовать два сервиса - NtSuspendThread(апи SuspendThread()) и NtSuspendProcess. Первый сервис замораживает поток, второй все потоки в процессе. Для разморозки соответственно *Resume. Для перечисления потоков используются слепки. http://msdn.microsoft.com/en-us/library/ms686832(v=VS.85).aspx 5. Ветвление. Можно записать инструкцию Jmp или Call. Во втором случае обработчик получит адрес, откуда он был вызван. Необходимо если например хэндлер общий у разных процедур. 6. Брейки. Однобайтные инструкции генерирующие исключения. Хэндлер является диспетчером исключений. http://www.wasm.ru/article.php?article=user-mode-exceptions http://www.wasm.ru/series.php?sid=7 http://www.wasm.ru/article.php?article=veh http://www.wasm.ru/forum/viewtopic.php?pid=381157#p381157 7. Дизассемблер длин используется для енума инструкций, лучший этот: http://www.eof-project.net/sources/Malum/VirXasm32_v1.5adv/src/VirXasm32b.asm http://files.virustech.org/indy/Code/XcptIp/VirXasm32b.asm 8. Использование стековых фреймов. В начале процедуры может не быть например инструкции подходящей для записи ветвления. При этом оно записывается выше(глубже в процедуре). Параметры процедуры и локальные переменные адресуются обычно регистром Ebp. Он адресует цепочку стековых фреймов и не меняется в пределах процедуры, таким образом доступ к параметрам процедуры выполняется через него. Например структура, которую будет адресовать регистр Ebp в любом месте процедуры LdrGetDllHandleEx(): Код (Text): NTSTATUS NTAPI LdrGetDllHandleEx( IN ULONG Flags, IN PCWSTR DllPath OPTIONAL, IN PULONG DllCharacteristics OPTIONAL, IN PCUNICODE_STRING DllName, OUT PVOID *DllHandle OPTIONAL ); Код (Text): STACK_FRAME struct Next PVOID ? ; Следующий фрейм. Ip PVOID ? ; Адрес возврата из процедуры. STACK_FRAME ends FN struct Frame STACK_FRAME <> Flags ULONG ? DllPath PCWSTR ? DllCharacteristics PULONG ? DllName PCUNICODE_STRING ? DllHandle PVOID ? FN ends 9. Атомарность. Запись выполняется не побайтно, а сразу 4 или 8 байт изменяются посредством инструкций cmpxchg и cmpxchg8b. http://www.intel.com/Assets/PDF/manual/253666.pdf Используете поиск тут по словам "сплайсинг", "патч".
Как быть с контролем целостности? Пусть один поток выполняет некий код, другой поток (не обязательно этого же процесса) контролирует целостность этого кода.
medstrax1 Создайте если нужно топик в соответствующем разделе, говорю что непосредственно здесь руткиты не обсуждают. Тут новички будут задавать вопросы и решать свои проблемы. Сколько раз можно повторять.
А как-нибуть еще можно изменять атрибуты памяти кроме как через NtProtectVirtualMemory? Можно-вить "пересобрать" секцию ZwCreateSection/ZwMapViewOfSection где находится нужный нам адрес, и прям в вышеупомянутых апи уже задать нужные атрибуты?
Flasher Из юзермода только этот сервис позволяет изменять атрибуты страниц. Это не изменение атрибутов, а освобождение памяти и аллокация по этому адресу.
Перехватываю функцию в DLL. Нашел точный адрес функции, имею ее исходный код. Но т.к. функция static, компилятор ее оптимизировал. Вот изначальная функция. Код (Text): static void HookCG_RGBForSaberColor( saber_colors_t color, vec3_t rgb ) { switch( color ) { case SABER_RED: VectorSet( rgb, 1.0f, 0.2f, 0.2f ); break; case SABER_ORANGE: VectorSet( rgb, 1.0f, 0.5f, 0.1f ); break; case SABER_YELLOW: VectorSet( rgb, 1.0f, 1.0f, 0.2f ); break; case SABER_GREEN: VectorSet( rgb, 0.2f, 1.0f, 0.2f ); break; case SABER_BLUE: VectorSet( rgb, 0.2f, 0.4f, 1.0f ); break; case SABER_PURPLE: VectorSet( rgb, 0.9f, 0.2f, 1.0f ); break; } } saber_colors_t = int, vec3_t = float[3]. Как видно, функция возвращает цвет (почему-то в массиве float). Моя задача - добавить еще один вариант. Вот как она выглядит в дизассемблированном виде от IDA. Код (Text): _text:20136500 CG_RGBForSaberColor proc near ; CODE XREF: sub_0_201365A0+D0p _text:20136500 ; CG_DoSaber+E3p _text:20136500 cmp ecx, 5 ; switch 6 cases _text:20136503 ja short locret_0_20136583 ; default _text:20136505 jmp ds:off_0_20136584[ecx*4] ; switch jump _text:2013650C _text:2013650C loc_0_2013650C: ; DATA XREF: _text:off_0_20136584o _text:2013650C mov ecx, 2.0e-1 ; jumptable 20136505 case 0 _text:20136511 mov dword ptr [eax], 1.0 _text:20136517 mov [eax+4], ecx _text:2013651A mov [eax+8], ecx _text:2013651D retn _text:2013651E ; --------------------------------------------------------------------------- _text:2013651E _text:2013651E loc_0_2013651E: ; CODE XREF: CG_RGBForSaberColor+5j _text:2013651E ; DATA XREF: _text:off_0_20136584o _text:2013651E mov dword ptr [eax], 1.0 ; jumptable 20136505 case 1 _text:20136524 mov dword ptr [eax+4], 5.0e-1 _text:2013652B mov dword ptr [eax+8], 1.0e-1 _text:20136532 retn _text:20136533 ; --------------------------------------------------------------------------- _text:20136533 _text:20136533 loc_0_20136533: ; CODE XREF: CG_RGBForSaberColor+5j _text:20136533 ; DATA XREF: _text:off_0_20136584o _text:20136533 mov dword ptr [eax], 1.0 ; jumptable 20136505 case 2 _text:20136539 mov dword ptr [eax+4], 1.0 _text:20136540 mov dword ptr [eax+8], 2.0e-1 _text:20136547 retn _text:20136548 ; --------------------------------------------------------------------------- _text:20136548 _text:20136548 loc_0_20136548: ; CODE XREF: CG_RGBForSaberColor+5j _text:20136548 ; DATA XREF: _text:off_0_20136584o _text:20136548 mov ecx, 2.0e-1 ; jumptable 20136505 case 3 _text:2013654D mov [eax], ecx _text:2013654F mov dword ptr [eax+4], 1.0 _text:20136556 mov [eax+8], ecx _text:20136559 retn _text:2013655A ; --------------------------------------------------------------------------- _text:2013655A _text:2013655A loc_0_2013655A: ; CODE XREF: CG_RGBForSaberColor+5j _text:2013655A ; DATA XREF: _text:off_0_20136584o _text:2013655A mov dword ptr [eax], 2.0e-1 ; jumptable 20136505 case 4 _text:20136560 mov dword ptr [eax+4], 4.0000001e-1 _text:20136567 mov dword ptr [eax+8], 1.0 _text:2013656E retn _text:2013656F ; --------------------------------------------------------------------------- _text:2013656F _text:2013656F loc_0_2013656F: ; CODE XREF: CG_RGBForSaberColor+5j _text:2013656F ; DATA XREF: _text:off_0_20136584o _text:2013656F mov dword ptr [eax], 8.9999998e-1 ; jumptable 20136505 case 5 _text:20136575 mov dword ptr [eax+4], 2.0e-1 _text:2013657C mov dword ptr [eax+8], 1.0 _text:20136583 _text:20136583 locret_0_20136583: ; CODE XREF: CG_RGBForSaberColor+3j _text:20136583 retn ; default _text:20136583 CG_RGBForSaberColor endp Как мне объяснили, функция получает значения через регистры, color через ecx, rgb через eax. Поэтому я сделал заголовок новой фунции как void __fastcall HookCG_RGBForSaberColor( saber_colors_t color, int, vec3_t rgb ), в итоге программа начала глючить конкретно, притом вне зависимости от порядка значений. Перехват осуществляется при помощи подгруженной DLL. Вот ее исходный код. Код (Text): enum { SABER_RED, SABER_ORANGE, SABER_YELLOW, SABER_GREEN, SABER_BLUE, SABER_PURPLE, SABER_RGB, NUM_SABER_COLORS }; typedef int saber_colors_t; typedef float vec_t; typedef vec_t vec2_t[2]; typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; typedef vec_t vec5_t[5]; #undef QDECL #define QDECL __cdecl #pragma pack (push,1) struct jmp_far{ BYTE instr_push; //здесь будет код инструкции push DWORD arg; //аргумент push BYTE instr_ret; //здесь будет код инструкции ret }; #pragma pack (pop) BYTE old[6],oldCG_RGBForSaberColor[6]; //область для хранения 6-ти затираемых байт начала функции const DWORD adr_Sys_LoadDll = 0x440250, adr_CG_RGBForSaberColor=0x20136500; DWORD written; //вспомогательная переменная jmp_far jump,jumpCG_RGBForSaberColor; //здесь будет машинный код инструкции перехода #define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z)) #ifdef _MANAGED #pragma managed(push, off) #endif //void * QDECL OrigSys_LoadDll( const char *name, char *fqpath , int (QDECL **entryPoint)(int, ...), // int (QDECL *systemcalls)(int, ...) ); //&OrigSys_LoadDll=adr_Sys_LoadDll; //static void HookCG_RGBForSaberColor( saber_colors_t color, vec3_t rgb ) void __fastcall HookCG_RGBForSaberColor( saber_colors_t color, int, vec3_t rgb ) { switch( color ) { case SABER_RED: VectorSet( rgb, 0.2f, 1.0f, 0.5f ); break; case SABER_ORANGE: VectorSet( rgb, 1.0f, 0.2f, 0.6f ); break; case SABER_YELLOW: VectorSet( rgb, 1.0f, 1.0f, 0.2f ); break; case SABER_GREEN: VectorSet( rgb, 0.2f, 1.0f, 0.2f ); break; case SABER_BLUE: VectorSet( rgb, 0.2f, 0.4f, 1.0f ); break; case SABER_PURPLE: VectorSet( rgb, 0.9f, 0.2f, 1.0f ); break; } } //Собственно подмена функций в dll void InterceptFunctions(void){ jumpCG_RGBForSaberColor.instr_push = 0x68; jumpCG_RGBForSaberColor.arg = (DWORD)&HookCG_RGBForSaberColor; jumpCG_RGBForSaberColor.instr_ret = 0xC3; WriteProcessMemory(GetCurrentProcess(), (void*)adr_CG_RGBForSaberColor, (void*)&jumpCG_RGBForSaberColor, sizeof(jmp_far), &written); }
Zlyden Тоесть вам нужно увеличить на 1 это значение: Код (Text): _text:20136500 cmp ecx, 5 ; switch 6 cases и добавить хэндлер в таблицу 0x20136584, всё это делается атомарно, в отличие от сплайсинга(4). Не понятно для чехо сохранять пролог, если вы заменяете функцию полностью. Скорее всего откуда происходит вызов этой функи известен, тогда достаточно изменить ссылку на процедуру. Это не описание проблемы. Вероятно не верная модель вызова.
Уважаемый тов. Clerk так какой метод наиболее продвинут, о котором естественно наверняка тут не указано ( ) ?
Clerk Спасибо, но я еще вчера разобрался. И пост вроде бы отредактировал, написал, что разобрался и помощь больше не нужна, но тут все как было. Решил проблему так - убрал у функции арги вообще. Вместо этого я создал локальные переменные int color, float * rgb, затем при помощи ассемблерной вставки вытащил их значения из регистров. Теперь работает. Перехват конкретно этой функции предпочтителен, т.к. над ней еще предстоит поработать. Хотя мне еще предстоит сделать перехват похожего ветвления внутри функции. Вообще, у меня возникла новая проблема. Я тружусь над перехватом другой функции. Собственно, заменять ее мне не нужно, мне нужны две переменные из ее стека. Вот как выглядит перехват: Код (Text): void HookCG_AddSaberBlade( int one, int two, int three, int four, int five, int six, int saberNum, int bladeNum ) { //Восстановление первых вшестии байт WriteProcessMemory(GetCurrentProcess(), (void*)adr_CG_AddSaberBlade, (void*)oldCG_AddSaberBlade, 6, &written); bnum=bladeNum; snum=saberNum; //Вызов функции с теми же переменными ((void (__cdecl*)( int, int, int, int, int, int, int, int ) adr_CG_AddSaberBlade)( one, two, three, four, five, six, saberNum, bladeNum); //Возвращаем хук WriteProcessMemory(GetCurrentProcess(), (void*)adr_CG_AddSaberBlade, (void*)&jumpCG_AddSaberBlade, sizeof(jmp_far), &written); } Функция несколько другая, чем я ожидал. Нужные мне переменные содержаться в последних двух аргументах, остальные - четырехбайтные переменные, для чего нужны, точно не знаю. Полагаю, в основном это указатели на объекты классов, three всегда 0, в функции не используется. В общем, я их получаю и передаю дальше как int, процессору вроде как нет разницы. Но почему-то вызываемая функция работает некорректно. По идее она отвечает за прорисовку лезвия меча, но после перехвата не рисует, появляются глюки в атаке. Функция вызывается из двух мест. Код (Text): mov ecx, [esp+430h+var_420] push ecx mov ecx, [esi+1C4h] push edi lea edx, [esp+438h+var_418] push edx mov edx, [esp+43Ch+var_3F0] lea eax, [esp+43Ch+var_3B0] push eax movsx eax, word ptr [ecx+3E4h] push edx push 0 push esi push esi call CG_AddSaberBlade add esp, 20h jmp loc_0_2013FCE8 Код (Text): mov edx, [esp+430h+var_420] push edx mov edx, [esi+1C4h] push edi lea eax, [esp+438h+var_418] push eax movsx eax, word ptr [edx+3E2h] lea ecx, [esp+43Ch+var_3B0] push ecx mov ecx, [esp+440h+var_3F0] push ecx push edi push esi push esi call CG_AddSaberBlade add esp, 20h jmp short loc_0_2013FCE8
Zlyden Их не нужно восстанавливать, а следует записать в конец буфера ветвление на код, после вашего джампа. При наличии ветвлений в начале процедуры их нужно поправить в буфере, например так http://www.wasm.ru/forum/viewtopic.php?pid=385497#p385497. Также лучше использовать не пару push/ret, а Jmp near, так как он короче на 1 байт. Тоесть в случае с вашим первым кодом исходная функа: Код (Text): CG_RGBForSaberColor: x00: cmp ecx,5 x03: ja short loc_x83 x05: ... xXX: ... Перехваченная: Код (Text): CG_RGBForSaberColor: push offset HookCG_RGBForSaberColor retn ... xXX: И хэндлер: Код (Text): HookCG_RGBForSaberColor: x00: cmp ecx,5 x03: ja CG_RGBForSaberColor.x83 x05: ... xXX: push offset CG_RGBForSaberColor.xXX ret
Clerk Наверно, вы меня не поняли, но CG_RGBForSaberColor я уже удачно перехватил, а восстанавливаю первые шесть байт совершенно другой функции, с ней возникли проблемы при заборе данных из стека. Насчет того, что jmp_near лучше jmp_far, не уверен. Я уже разок решил использовать ее вместо jmp_far, сделал все по инструкции, из адреса цели вычел адрес функции, добавил пять, получил бсод. Тогда я решил, что это наверняка из-за того, что функция-цель находится до перехватываемой функции, а jmp_near не воспринимает отрицательные значения (не знаю точно, но уверен). В общем, с тех пор только push/ret
Zlyden Это вы не поняли. У вас не корректный подход к решению задачи. Код CG_RGBForSaberColor был примером.
Clerk А, спасибо, теперь понял. Ваш метод опробую, у меня еще предстоит одно ветвление заменить. А с CG_AddSaberBlade разобрался. В чем ошибка, не нашел, зато нашел, где хранятся все эти уже извлеченные данные стека .
Clerk Мне не понятно куда можно сохранить действительный адрес возврата в многопоточном приложении. Алгоритм вкратце: 1. При инжекте: в начало перехватываемой функции вставляю jmp на свою функцию подмены адреса возврата. 2. В функции подмены: меняю в стеке действительный адрес возврата на адрес моей функции перехватчика. и вот тут действительный адрес нужно куда-то сохранить чтоб им можно было воспользоваться в функе перехвата для передачи управления вызывающему коду после отработки перехвата. Если я ложу адрес в глобальную переменную, то каждый новый поток которому понадобилась перехватываемая функа приходит со своим адресом возврата и соотв. в переменной остаётся адрес последнего вызывающего потока. и получается сколько бы потоков не вошло в функцию, все они будут возвращаться по адресу последнего вошедшего. А как дотянуться до локальной переменной из другой функции я не представляю. Тогда я попробовал сохранять этот адрес относительно ESP. Ониже не могут быть равны у разных потоков ? mov [esp-12364],eax Смещение 12364, почти от фонаря, просто в отладчике подсмотрел что эта область заполнена нулями и никаких следов её использования не обнаружил. Работает. Но очень не устойчиво. Я вобще не уверен что туда можно чтото писать.
SZ А почему не сделать просто call внутри своей функции, с дублированием фрейма стека? Или Вам нужен перехват функций с неизвестным кол-вом параметров? Тогда можно просто дублировать достаточно большой кусок, чтобы фрейма точно хватило.
SZ #14. Можно использовать следующие способы: o TLS. Это инфа связанная с потоком. Может быть локальной или глобальной. Если локальна, то используется тот факт, что регистр Fs адресует сегмент уникальный для потока с его окружением(TEB). Во втором способе создаётся список, в котором сохраняются описатели поток-ссылка(адрес возврата). Это решение требует использование нотификаторов, при первом вызове кода описатель сохраняется в список, затем извлекается. Память выделяется при создании потока или первом вызове кода, освобождается при завершении потока или когда выполняется возврат на оригинальный код. o Использование контекста, который передаётся в хэндлер. Это например свободные поля в стековых фреймах. Не универсально. o Использование дна стека. Эта часть стека не используется. Например я использовал следующее макро: Код (Text): $GET_ENVIRONMENT macro Reg32 mov Reg32,fs:[TEB.Tib.StackBase] mov Reg32,dword ptr [Reg32 - 4] endm o Механизм исключений. Универсально. Старший бит ссылки взводится. Это приведёт к возникновению исключения при возврате, так как выход за пределы сегмента/U-space. Диспетчер исключений сбрасывает старший бит ссылки и выполняет возврат. o Использование переменных ядра. Это конфиг связанный с текущим обьектом(ETHREAD), например ThreadQuerySetWin32StartAddress, проецирование ядерной памяти и пр. Крайне не желательно для применения.
Booster Вот произошёл вызов перехватываемой функи [esp]=Адрес возврата позади (esp+), дыра в 1028 байт оставленная прологом функи для буфера(?) впереди (esp-), место для функций которые будут вызываться из перехватываемой. Вот я и не понимаю как тут воткнуться чтоб мне не испортили мои данные (esp-) или я не испортил чужие (esp+). Clerk Из перечисленных вами способов мне наверное подойдут "TLS" и "дно стека". Но вряд ли с TLS получиться, мне это не осилить. На всякий случай запощу проблемный кодес Код (Text): NAKED void HookRecv() { __asm { __emit 0x8B __emit 0x41 __emit 0x08 jmp [pPreHookRecv] ; для вставки: прыжок на пресплайс. __emit 0xCC ;................ ; Перехватчик ; После отработки целевой функции управление передаётся сюда, адрес в pofHookRecv. push [esp-12364-4] ; Замена подставного на действительный адрес возврата. pushad ; Сохранение контекста mov eax,[esp+32+4] ; Берём адрес возвращённых данных, mov pData, eax ; сохраняем. mov eax,[eax] cmp al,0x38 ; а что там ? je skiprecv ; если '8' пропустить } //................ полезная нагрузка, работа с данными *pData //................ __asm { skiprecv: popad ; Восстановление контекста ret ; возврат } } //================================================================================= NAKED void PreHookRecv() // Пресплайс { __asm { pushad // Произошёл вызов перехватываемой функции, её адрес в eax. mov eax,pofHookRecv // Загрузка адреса перехватчика:=>pofHookRecv xchg eax,[esp+32+0] // Меняем местами: подставной<===>действительный, +32 поправка на pushad mov [esp+32-12364],eax // Сохранение действительного адреса возврата popad jmp eax // Передача управления целевой функции } } буду с макросом разбираться.