Сейчас на форуме: hgdagon, asfa, bartolomeo (+4 невидимых) |
![]() |
eXeL@B —› Программирование —› Как потокобезопасно подменить стек не трогая стек исходного потока |
Посл.ответ | Сообщение |
|
Создано: 19 января 2007 10:47 · Личное сообщение · #1 Задача такая:Делаю перехват Api путем инжекта jmp на тело своего обработчика. В обработчике нужно потокобезопасно сохранить регистры исходного потока, подменить стек на свой, выполнить действия по обработке, восстановить стек и регистры. Более подробно алгоритм представляется следующим: 1)потокобезопасно запоминаем регистры использующиеся для подмены стека, 2)меняем стек (StackBase,StackLimit,Esp) 3)pusha 4)тело основного обработчика 5)popa 6)восстанавливаем старый стек 7)восстанавливаем используемые регистры при подмене стека 8)jmp на api. Сообственно интересует конкретная реализация пунктов 1,2,6,7, причем главное условие сделать это нужно не трогая вообще стек исходного потока (любые операции со стеком исходного потока запрещены!). Естественно заранее еще до установки перехвата выделена память в обьеме достаточном для хранения потоковых данных и своих стеков? Собственно этот вопрос тоже задавался на wasm.ru но пока по subj ответов не получил. Может у кого будут мысли по данному поводу. Заранее спасибо. ![]() |
|
Создано: 19 января 2007 11:07 · Личное сообщение · #2 |
|
Создано: 19 января 2007 11:26 · Личное сообщение · #3 |
|
Создано: 19 января 2007 11:36 · Личное сообщение · #4 |
|
Создано: 19 января 2007 12:38 · Личное сообщение · #5 Тут главная задача не перехват АПИ а перехват взаимодействия длл модулей программы между собой, подсчет статистики вызовов анализ параметров и т.д. А данный сабж нужен чтоб свести к нулю воздействие на исполняемую программу (программа может отслеживать модификацию стека, можно вызвать переполнение и т.д.) ![]() |
|
Создано: 19 января 2007 14:31 · Поправил: s0larian · Личное сообщение · #6 Goldy пишет: А данный сабж нужен чтоб свести к нулю воздействие на исполняемую программу Не.... ты зря морочешь себе яйца. Если софтина вызывает API, то гарантия только в том, что когда-нить EIP будет показывать на след. инструкцию, стек будет подчищен от аргументов, и твоя часть стека (более высокие адреса) не будет попорчена. Что и как происходит с оставшимся местом на стеке - не известно и не важно. Более того, что б это проверить надо задекларировать char stack[1024]; и это читать - что есть бред - это ведь зависит от версии винды, версии SP, версии IE, MFC и т.д. Ну а по поводу потоков - то что на стеке - уже само собой видимо только потоку. Твой код перехвата будет работать в контексте (то есть на стеке) вызывающего потока. ![]() |
|
Создано: 19 января 2007 14:49 · Личное сообщение · #7 |
|
Создано: 20 января 2007 00:00 · Личное сообщение · #8 Можешь попытаться объявить массив двордов, после получения управления запомнить в своей переменной esp, поменять esp на свой массив, потом уже 3-5, восстанавливать esp. При необходимости копировать сзначения изначального стека в свой. Второй вариант-копировать изначальный стек в свой массив, работать в общем стеке, а потом скопировать из массива обратно в общий. ![]() |
|
Создано: 20 января 2007 00:40 · Личное сообщение · #9 |
|
Создано: 20 января 2007 03:04 · Личное сообщение · #10 |
|
Создано: 20 января 2007 10:40 · Личное сообщение · #11 Archer->Главный вопрос был как это сделать потокобезопасно! В твоем варианте это не проходит! S_T_A_S_->Это я уже давно прочел ![]() Вобще мне нужно 4 байта для сохранения одного регистра и это место должно быть или в TLS или вычисляться динамически в моем куске памяти в зависемости от того под каким потоком мы работаем. Остальной процесс подмены стека и т.д. понятен. ![]() |
|
Создано: 20 января 2007 10:48 · Личное сообщение · #12 Goldy пишет: Вобще мне нужно 4 байта для сохранения одного регистра и это место должно быть или в TLS или вычисляться динамически в моем куске памяти в зависемости от того под каким потоком мы работаем. А в чем проблема? ESP для каждого потока свой, новый стек ведь тоже - так и сохраняй старый esp в новом стеке. Или я чего-то не понял? ![]() |
|
Создано: 20 января 2007 12:43 · Личное сообщение · #13 Проблема в том как не используя регистров вычислить динамически в зависемости от индефикатора потока нужный кусок памяти для сохранения регистра, причем нужно это сделать не меняя ни одного регистра так как стек текущего потока по условию трогать нельзя тоесть push отпадает. Или можно воспользоваться местом в TLS потока но для этого нужно знать какой элемент структуры можно безопасно затереть без потери функционала (пока еще не нашел). Условно ситуция: Поток A-вызывает функцию F Поток В-вызывает функцию F Что получается если в момент вызова A если мы используем статический адрес для сохранения esp например mov [Addr],esp выполнили команду переключились на поток B опять выполнили команду mov [Addr],esp в результате работа потока A нарушена так как восстановление esp будет из другого адреса. Для исключения этой ситуации поток A должен сохранять esp в Addr1,B в Addr2 и т.д. Тоесть адрес должен меняться взависемости от потока под которым работает функция. Теперь надеюсь смысл понятен? ![]() |
|
Создано: 20 января 2007 12:58 · Личное сообщение · #14 |
|
Создано: 20 января 2007 18:22 · Поправил: S_T_A_S_ · Личное сообщение · #15 Goldy А создание процесса ты можешь контроллировать? Впринципе, твою задачу можно заметно упростить, если место под запасной стек резервировать непосредственно ниже стека треда. Тогда в хуке нужно быдет лишь отнять от esp некоторую константу. Хотя вся эта затея с подменой стека ИМХО довольно сомнительна, там что, реально есть серьёзные проверки? Скорее всего достаточно закоммитить нижнюю половину стека треда и подправить лимит, что бы ОС туда не намусорила (а часто на стеке юзается не больше нескольких десятков К, хотя в хзаголовке PE стоит 1M) ![]() |
|
Создано: 01 февраля 2007 02:02 · Поправил: entusiast · Личное сообщение · #16 HoBleen, иногда по непонятным причинам перестает работать Win32 API в перехватчиках. У меня такое было, когда я перехватом распаковывал Ил-2. Пришлось динамически менять стек. Goldy, примерно так. Приблизительно (!), ибо исходник сдох с переноской. ![]() pushad VirtualAlloc(NULL,1000h,MEM_COMMIT,PAGE_READWRITE) push eax add eax,1000h pop dword ptr [eax] // 'remember' buffer for furhter VirtualFree sub eax,4 mov [eax],esp sub eax,4 // 'remember' old stack Это подготовка буфера к юзу в качестве стека. Дальше, если тебе нужны параметры исходной ф-ции,надо скопировать кус стека в буфер от eax вниз. или же иными словами, при SZ=4+sizeof Parameters от eax-SZ вверх. Исходный вычислить от текущего esp т.е. что-то вроде mov esi,esp add esi,0x1C // skip pushad if unneeded mov edi,eax mov ecx,50h // 50h байт может оказаться достаточно. Если мало - увеличь sub edi,ecx shr ecx,2 rep movsd // скопировали кусок старого стека. sub edi,50h // откатили edi обратно вниз. (***) mov esp,edi // все. Стек переадресован. Call OurAPIReplaceFunction// получит первым параметром RetAddress вызванной, далее, как положено. После возврата, стек указывает в наш буфер. Надо восстановить. mov esi,esp and esi,1000h add esi,0FF8h mov esp,[esi]// по идее, если нет ошибок, стек должен восстановиться. push eax // сохраняем результат add esi,4 push MEM_RELEASE push 0 push dword ptr[esi] call VirtualFree pop eax // взяли в еах рензультат mov [esp+....],eax // вместо .... смещение в стеке такое, чтоб после popad eax попал в еах. popad retn (***) - Надо уточнять смещения. Я постоянно промахиваюсь на +/-1 после блочных операций, мог слажать. Идея понятна? PS. Это работает для __stdcall & __cdecl, где параметры передаются только стеком. для __фастколл надо передавать и контекст и стек. ![]() |
|
Создано: 01 февраля 2007 03:50 · Личное сообщение · #17 Хех сам алгоритм переадресации стека у меня и так давно написан и работает. Ты лучше скажи как это сделать вобще не трогая стека исследуемой программы. А у тебя первой же командой pushad затирается нижний байты старого стека, которые могут отслеживаться программой ![]() Идея S_T_A_S_ наиболее предпочтительна, но при этом приходится жертвовать большим кол-вом памяти в случае большого числа потоков. ![]() |
|
Создано: 01 февраля 2007 08:14 · Личное сообщение · #18 Goldy, понятно, сорри. Я не углядел условия о неизменности стека. но, кстати, даже обращение к TLS все-таки требует "порчи" стека регистров... Т.е. даже если ты знаешь индекс, то TLSSetValue все равно через стек работает.... А насчет идеи S_T_A_S, то вопрос- как не трогая ничего определить ThreadID? Даже, если есть обходной путь без вызовов АПИ (а он, кстати, есть?), то.... а что.... можно попробовать ковырнуть TEB, если я, конечно, правильно понял эту структуру. Там есть поля Spare2-Spare4 ULONG каждый. Может, так выйдет..... Смотрю ntundoc, структуры TEB и NT_TIB - похоже, все-таки, это оно доступно через селектор fs:.... Попробуй, может, выйдет чего...... Тогда можно в Spare записать нужный адрес при создании потока.... Но тут у меня практики никакой - с этими структами я только в области SEH и IsDebuggerPresent общался. ![]() |
|
Создано: 01 февраля 2007 08:32 · Личное сообщение · #19 Goldy Можешь с помощью эмуляции RtlEnterCriticalSection (Через Lock inc без использования стека) использовать способ и для многопоточного приложения - статическая область памяти используется как временный стек для создания нового стека. После xxxAlloc заменяешь стек и освобождаешь этот временный, после этого другой поток может использовать его для создания своего стека. Думаю, основной момент понятен? ![]() ![]() |
![]() |
eXeL@B —› Программирование —› Как потокобезопасно подменить стек не трогая стек исходного потока |