Доброго времени суток. Я экспериментирую с переключением режима процессора(x86/x64) с помощью ret far и смены сегмента в регистре cs (0x33/0x23) Прикладываю минимальный код для воспроизведения проблемы(компилятор MSVC, Debug - x86, C++): Код (Text): #include <windows.h> using namespace std; #define ASM(opcode) _asm _emit opcode #define X64PopR10 ASM(0x41) ASM(0x5A) #define X64PopR8 ASM(0x41) ASM(0x58) #define X64PopR9 ASM(0x41) ASM(0x59) #define syscall ASM(0xF) ASM(0x5) #define X64CodeStart \ { \ ASM(0x6A) ASM(0x33) /* push 0x33 (X64 Segment) */ \ ASM(0xE8) ASM(0x00) ASM(0x00) ASM(0x00) ASM(0x00) /* call $+5 */ \ ASM(0x83) ASM(0x04) ASM(0x24) ASM(0x05) /* add dword ptr [esp], 0x5 */ \ ASM(0xCB) /* ret far */ \ } #define X64CodeEnd \ { \ ASM(0xE8) ASM(0x00) ASM(0x00) ASM(0x00) ASM(0x00) /* call $+5 */ \ ASM(0xC7) ASM(0x44) ASM(0x24) ASM(0x04) ASM(0x23) ASM(0x00) ASM(0x00) ASM(0x00) /* mov dword ptr [rsp + 4], 0x23 (x32 Segment)*/ \ ASM(0x48) ASM(0x83) ASM(0x04) ASM(0x24) ASM(0x0E) /* add qword ptr [rsp], 0xE */ \ ASM(0xCB) /* ret far */ \ } void* Test() { INT64 pQueryData = (INT64)VirtualAlloc(nullptr, 1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); uintptr_t espSave{ 0 }; _asm { mov espSave, esp and esp, 0xFFFFFFF8 // выравниваю указатель на верхушку стэка до кратности к восьми } X64CodeStart; _asm { push 0xB X64PopR10 push pQueryData pop edx push 1000 X64PopR8 push 0 X64PopR9 mov eax, 0x36 syscall } X64CodeEnd; _asm { mov esp, espSave } return (void*)pQueryData; } int main() { Test(); } В этом коде с помощью смены селектора в регистре cs и переключившись в x64 - я просто пытаюсь вызвать в ручную ZwQuerySystemInfomation(syscall 0x36 с запросом об SystemModuleInformation), используя непосредственно x64-битную ntdll, без необходимости использования юзермодного wow64.dll для трансляции вызовов системных функций на x32. И так, проблема заключается в том, что на 57 строчке (mov esp, espSave), при попытке восстановить регистр esp программа падает с указанием на то, что в espSave у меня nullptr(так говорит отладчик студии 2022), но я этому не верю, так как на 33 строчке кода(mov espSave, esp) я успешно сохранил его. Чтобы убедится в том, что с ESP всё хорошо и стэк у меня не сбит я полез в отладчик разбираться: Как видно в espSave никакого nullptr не лежит, Но эксепшен "Первая попытка исключения на 00DA17DF (C0000005, EXCEPTION_ACCESS_VIOLATION)!" также, как и в Visual Studio - прилетает, но без подробностей. Ради интереса я ещё потрассировал в x64-битном коде с помощью WinDbg и к моему удивлению, именно при ПОЭТАПНОЙ ТРАССИРОВКЕ(p) никаких исключений не валится(игнорирование исключений я не включал!), но когда я решаюсь просто пролететь(g) в этом же месте получаю эксепшен с точно такой же проблемой, как в x32dbg: Как видно на изображении, опять же - никаких nullptr в espSave НЕТ! Что же это за противоречия-то такие. И перед вызовом ret far в rsp и после его вызова в esp - также лежат корректные значения верхушки стека! Итак, дальше я ради интереса попробовал просто в холостую проверить, как будут работать макросы без вызова ZwQuerySystemInfomation: Код (Text): void Test() { uintptr_t espSave{ 0 }; _asm { mov espSave, esp and esp, 0xFFFFFFF8 } X64CodeStart; X64CodeEnd; _asm { mov esp, espSave } } Результат - положительный, никаких исключений нет. Значит на основании этого могу сделать вывод, что ZwQuerySystemInfomation, где-то там шаманит в ядре так, что я получаю эксепшены такого рода. Но почему тогда происходят такие аномалии, почему в отладчике, если поэтапно трассировать по коду с потенциальным исключением - никаких исключений НЕТ, что за магия И почему, если я даже и получаю эксепшен - в отладчике никаких признаков его возникновения не наблюдается - неужели баги ( Я знаю, что на этом форуме очень много высококвалифицированных людей. Воспроизведите и помогите пожалуйста разложить всё по полочкам и разобраться, что за аномалии происходят в отладчиках, почему такие противоречия. Я полагаю, что возможно ZwQuerySystemInfomation в ядре меняет какие-то флаги для доступа к сегменту x32. Но у меня недостаточно опыта, чтобы быть полностью в этом уверенным.
Несмотря на горбатость код работает. Аксесс виолейшн у тебя скорей всего возникает из-за: Код (Text): push pQueryData просто отладчик с ума сходит от этих фокусов. Покажи полный листинг функции test().
Думаю нет, потому что MVSC скомпилил вполне нормальный асм для push pQueryData(push qword ptr [rbp - 10h]), да и приведением результирующего указателя от VirtualAlloc к INT64 и последующим его присваиванием к INT64 pQueryData - я строго компилятору заявил, чтобы он мне спроектировал стэк для функции Test так, чтобы последующий push qword хорошо расположился. MVSC оптимизировать деструктивно, тоже не обязан, так как я проследил, чтобы в Debug оптимизации были выключены. Да вроде ничего такого ему MVSC ненакомпилил, разве что переключение сегментов его опьяняет... Мне бы хотелось верить, что "это просто отладчик сума сходит", но проблема в том, что и без отладчиков приложение успешно падает, аналогично и в Release сборке, то есть я просто напросто не могу выполнить этот код без съедания исключения(отладчиком). Я предоставил полный листинг проблемы, которую пытаюсь решить. Без решения проблемы даже с таким(минимальным) листингом, дальше двигаться я не могу. P.S: И также, по поводу претензий к push pQueryData: после изменения(для проверки) в Test: Код (Text): push pQueryData pop edx На Код (Text): push 0 pop edx[code] Это никак не повлияло на характер исключения.
Давай бинарник свой. И вообще почитай это, нужно перезагружать регистр SS. Вот тут готовый код на вб6 правда, но тут будут работать правильно и колбеки из 64 битного режима для функций типа SendMessage.
Хорошо, обязательно прочту чуть позже про SS. И можете также объяснить, зачем его перезагружать, разве это не должен делать процессор при вызове retf? А также, как игнорирование обновления SS(в ручную) может поломать стэк, ведь в отладчике вполне приемлемые указатели в ESP и EBP даже без манипуляций с SS. Бинарник тут: --- Сообщение объединено, 24 июл 2023 --- Извиняюсь 'code' - случайно забыл убрать
Ну ок, если ты такой молодец и все настолько предусмотрел, что даже почти все работает, то я за тебя спокоен. Какого смысла у меня буквально копия того, что у тебя на картинке, работает, я не знаю, пусть это будет называться чудо неебесное.
Лучше бы вы были за меня беспокойны, как и я за себя, ибо такого рода чудеса мне не нужны - нужна стабильность , а не чудеса.
Да, у тебя дело в SS скорее всего т.к. характер бага тот же самый. Насчёт причин перезагрузки SS к сожалению ничего сказать не могу, т.к. не силен в "железе". Знаю есть implicit stack switching при переключении из 64 битного режима в Compatibility режим, там есть какие-то нюансы с регистром SS. Достаточно просто глянуть дизасм wow64cpu там сделано именно так. --- Сообщение объединено, 24 июл 2023 --- Кстати насколько я понял это проблема существует только на процессорах AMD.
Спасибо тебе большое. Сохранение и восстановление(после syscall) SS - помогло !!! Вчера пол дня возился с этим эксепшеном... не знаю чёб без WASM делал, в окно бы вышел наверно. Да кстати и у меня как раз проц от AMD
On AMD there is a processor bug where the stack segment descriptor is corrupted after a mode switch from 64-bit to 32-bit https://github.com/DynamoRIO/dynamorio/pull/4485
Такое поведение присуще вроде всем процессорам (как Intel, так и AMD), поскольку зависит от алгоритма работы самих инструкций syscall/sysret. Если посмотреть их описание в доках Intel'a том(2) "Instruction Set Reference", можно обнаружить там следующий момент:
Ну вот восстановить ESP я и пытаюсь, только сломанный селектор в SS не давал мне этого сделать. Чуть позже поотлаживаюсь в ядре - посмотрю, что там в MSR IA32_STAR в [63:48] битах валяется перед вызовом sysret.
808Problem, я к тому, что было сказано: ..а значит делаем вывод, что вход в х64 и выход из него фурычат (и проц здесь ни причём), а глюк именно в syscall. Можно попробовать бэкапить RSP до syscall, а не ESP после входа в х32.
наверно, проблема в том, что в дикой среде имеет место быть многопоточность, а при пошаговом прогоне в отладчике получается один поток
Хм, не думаю, что для того, чтобы нормально отработал сускалчик нужна целая пачка потоков для его величества
прикол ещё в том, что у современного проца даже одиночный поток не совсем классический в силу 3ое (out-of-order execution). хрень позволяет ускорять цикл, но увеличивает риски нарушения логики потока
Небольшой ликбез по WinDbg на эту тему.. Для начала, командой "dg" (Display GDT) проверим дескрипторы(CS), на которые указывают селекторы 23/33h. (кстати WinDbg имеет локальный HtmlHelp, для данного случая формат запроса будет: ".hh dg"). Командой "r" можно получить адрес и размер GDT в памяти. Для дампа всей таблицы вводим "dg 00 80", где значение 80h = номер последнего селектора: Код (Text): 0: kd> r @gdtr, @gdtl gdtr=fffff800`03fff000 gdtl=007f ;//<--- адрес и размер таблицы GDT 0: kd> dg 23 33 P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 0023 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb 0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb ;//-- Описание флагов ;//--------------------- Privilege : 3 = User Size : 23 = Big (32-бит), 33 = NoBig (в Long игнорируется) Granularity: 23 = Page, 33 = Byte Present : P = OK Long seg : 23 = No, 33 = Yes Здесь картина валидная - едем дальше.. Вспомним, что говорит Intel об инструкции syscall: Значит на этапе загрузки системы (после создания GDT), "фиксированные значения" всё-же копируются из GDT. Правда я так и не нашёл инфы, где хранятся эти значения - ни в одном из MSR их нет! Проблема в том, что никакими средствами (в тч.и в WinDbg) мы не можем прочитать кэши дескрипторов из 8-байтной/скрытой части сегментных регистров. Остаётся только верить на-слово и надеяться, что при syscall в них лежат валидные значения. Если кто знает, как получить эти кэши - буду благодарен. Прочитать оригиналы из GDT можно по номерам селекторов, и той-же команды "dg". В логе ниже видно, что секция-кода в ядре х64 (PL=0) как и ожидается Long, а вот сегмент стека 32-битный с флагом "Nl". Код (Text): 0: kd> r cs,ss cs=0010 ss=0018 0: kd> dg 10 18 P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 0010 00000000`00000000 00000000`00000000 Code RE Ac 0 Nb By P Lo 0000029b 0018 00000000`00000000 00000000`ffffffff Data RW Ac 0 Bg Pg P Nl 00000c93 Что касается регистров MSR при вызове SYSCALL, то мой WinDbg почему-то игнорирует команду чтения "rdmsr", выдавая на любой запрос "No such msr". Может потому, что в качестве локального LKD я использую "LiveKD" из пакета Sysinternal - хз. Однако прочитать текущее значение любого из регистров MSR можно и другими средствами, например утилитой "Read & Write Utility". Вот скрин, который отображает указанные MSR в состоянии простоя Win x64: Код (Text): IA32_EFER (0xC0000080) — Конфиг расширенных возможностей = 00000000`00000d01 бит[0] — SYSCALL/SYSRET enable, бит[8] — Long mode enable (LME), бит[10] — Long mode active (LMA), бит[11] — No Execute Bit enable (NXE). IA32_STAR (0xC0000081) — Селекторы сегментов Ring(0) и Ring(3) = 00230010`00000000 биты[63:48] — селектор(CS) юзера, биты[47:32] — селектор(CS) ядра, мл. 32 бита — SYSCALL EIP в режиме wow64. IA32_LSTAR (0xC0000082) — RIP ядра для SYSCALL в режиме x64 = fffff800`02cc5180 IA32_CSTAR (0xC0000083) — RIP ядра для SYSCALL в режиме wow64 = fffff800`02cc4ec0 IA32_FMASK (0xC0000084) — маска для рег RFLAGS при SYSCALL x64 = 00000000`00004700
Воу, фига - очень удобно кстати! Да, скорее всего. В в нелокальной ядерной отладке с rdmsr всё хорошо. Спасибо большое за ликбез, очень полезно!