Проблемы с RET FAR

Тема в разделе "WASM.BEGINNERS", создана пользователем 808Problem, 24 июл 2023.

  1. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Доброго времени суток. Я экспериментирую с переключением режима процессора(x86/x64) с помощью ret far и смены сегмента в регистре cs (0x33/0x23)
    Прикладываю минимальный код для воспроизведения проблемы(компилятор MSVC, Debug - x86, C++):
    Код (Text):
    1. #include <windows.h>
    2.  
    3. using namespace std;
    4.  
    5. #define ASM(opcode) _asm _emit opcode
    6.  
    7. #define X64PopR10 ASM(0x41) ASM(0x5A)
    8. #define X64PopR8 ASM(0x41) ASM(0x58)
    9. #define X64PopR9 ASM(0x41) ASM(0x59)
    10. #define syscall ASM(0xF) ASM(0x5)
    11.  
    12. #define X64CodeStart                                                                    \
    13. {                                                                                       \
    14.     ASM(0x6A) ASM(0x33)                                /* push 0x33 (X64 Segment)    */ \
    15.     ASM(0xE8) ASM(0x00) ASM(0x00) ASM(0x00) ASM(0x00)  /* call $+5                   */ \
    16.     ASM(0x83) ASM(0x04) ASM(0x24) ASM(0x05)            /* add dword ptr [esp], 0x5   */ \
    17.     ASM(0xCB)                                          /* ret far                    */ \
    18. }
    19.  
    20. #define X64CodeEnd                                                                      \
    21. {                                                                                       \
    22.     ASM(0xE8) ASM(0x00) ASM(0x00) ASM(0x00) ASM(0x00)  /* call $+5                   */ \
    23.     ASM(0xC7) ASM(0x44) ASM(0x24) ASM(0x04) ASM(0x23) ASM(0x00) ASM(0x00) ASM(0x00) /* mov dword ptr [rsp + 4], 0x23 (x32 Segment)*/ \
    24.     ASM(0x48) ASM(0x83) ASM(0x04) ASM(0x24) ASM(0x0E)  /* add qword ptr [rsp], 0xE   */ \
    25.     ASM(0xCB)                                          /* ret far                    */ \
    26. }
    27.  
    28. void* Test() {
    29.     INT64 pQueryData = (INT64)VirtualAlloc(nullptr, 1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    30.     uintptr_t espSave{ 0 };
    31.  
    32.     _asm {
    33.         mov espSave, esp
    34.         and esp, 0xFFFFFFF8 // выравниваю указатель на верхушку стэка до кратности к восьми
    35.     }
    36.  
    37.     X64CodeStart;
    38.     _asm {
    39.         push 0xB
    40.         X64PopR10
    41.  
    42.         push pQueryData
    43.         pop edx
    44.  
    45.         push 1000
    46.         X64PopR8
    47.  
    48.         push 0
    49.         X64PopR9
    50.  
    51.         mov eax, 0x36
    52.         syscall
    53.     }
    54.     X64CodeEnd;
    55.  
    56.     _asm {
    57.         mov esp, espSave
    58.     }
    59.  
    60.     return (void*)pQueryData;
    61. }
    62.  
    63. int main()
    64. {
    65.     Test();
    66. }
    В этом коде с помощью смены селектора в регистре cs и переключившись в x64 - я просто пытаюсь вызвать в ручную ZwQuerySystemInfomation(syscall 0x36 с запросом об SystemModuleInformation), используя непосредственно x64-битную ntdll, без необходимости использования юзермодного wow64.dll для трансляции вызовов системных функций на x32.

    И так, проблема заключается в том, что на 57 строчке (mov esp, espSave), при попытке восстановить регистр esp программа падает с указанием на то, что в espSave у меня nullptr(так говорит отладчик студии 2022), но я этому не верю, так как на 33 строчке кода(mov espSave, esp) я успешно сохранил его.

    Чтобы убедится в том, что с ESP всё хорошо и стэк у меня не сбит я полез в отладчик разбираться: x32dbg.png

    Как видно в espSave никакого nullptr не лежит, Но эксепшен "Первая попытка исключения на 00DA17DF (C0000005, EXCEPTION_ACCESS_VIOLATION)!" также, как и в Visual Studio - прилетает, но без подробностей.

    Ради интереса я ещё потрассировал в x64-битном коде с помощью WinDbg и к моему удивлению, именно при ПОЭТАПНОЙ ТРАССИРОВКЕ(p) никаких исключений не валится(игнорирование исключений я не включал!), но когда я решаюсь просто пролететь(g) в этом же месте получаю эксепшен с точно такой же проблемой, как в x32dbg: ShareX_nqg9s9yEOP.png
    Как видно на изображении, опять же - никаких nullptr в espSave НЕТ! Что же это за противоречия-то такие. И перед вызовом ret far в rsp и после его вызова в esp - также лежат корректные значения верхушки стека!

    Итак, дальше я ради интереса попробовал просто в холостую проверить, как будут работать макросы без вызова ZwQuerySystemInfomation:

    Код (Text):
    1. void Test() {
    2.     uintptr_t espSave{ 0 };
    3.  
    4.     _asm {
    5.         mov espSave, esp
    6.         and esp, 0xFFFFFFF8
    7.     }
    8.  
    9.     X64CodeStart;
    10.     X64CodeEnd;
    11.  
    12.     _asm {
    13.         mov esp, espSave
    14.     }
    15. }
    Результат - положительный, никаких исключений нет. Значит на основании этого могу сделать вывод, что ZwQuerySystemInfomation, где-то там шаманит в ядре так, что я получаю эксепшены такого рода. Но почему тогда происходят такие аномалии, почему в отладчике, если поэтапно трассировать по коду с потенциальным исключением - никаких исключений НЕТ, что за магия :) И почему, если я даже и получаю эксепшен - в отладчике никаких признаков его возникновения не наблюдается - неужели баги (
    Я знаю, что на этом форуме очень много высококвалифицированных людей. Воспроизведите и помогите пожалуйста разложить всё по полочкам и разобраться, что за аномалии происходят в отладчиках, почему такие противоречия.

    Я полагаю, что возможно ZwQuerySystemInfomation в ядре меняет какие-то флаги для доступа к сегменту x32. Но у меня недостаточно опыта, чтобы быть полностью в этом уверенным.
     
    Последнее редактирование: 24 июл 2023
  2. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    1.995
    Несмотря на горбатость код работает. Аксесс виолейшн у тебя скорей всего возникает из-за:
    Код (Text):
    1.         push pQueryData
    просто отладчик с ума сходит от этих фокусов. Покажи полный листинг функции test().
    изображение_2023-07-24_132142074.png
     
    Последнее редактирование: 24 июл 2023
  3. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Думаю нет, потому что MVSC скомпилил вполне нормальный асм для push pQueryData(push qword ptr [rbp - 10h]), да и приведением результирующего указателя от VirtualAlloc к INT64 и последующим его присваиванием к INT64 pQueryData - я строго компилятору заявил, чтобы он мне спроектировал стэк для функции Test так, чтобы последующий push qword хорошо расположился.
    ShareX_1qAk2MpfRz.png
    MVSC оптимизировать деструктивно, тоже не обязан, так как я проследил, чтобы в Debug оптимизации были выключены.
    Да вроде ничего такого ему MVSC ненакомпилил, разве что переключение сегментов его опьяняет... Мне бы хотелось верить, что "это просто отладчик сума сходит", но проблема в том, что и без отладчиков приложение успешно падает, аналогично и в Release сборке, то есть я просто напросто не могу выполнить этот код без съедания исключения(отладчиком).

    Я предоставил полный листинг проблемы, которую пытаюсь решить. Без решения проблемы даже с таким(минимальным) листингом, дальше двигаться я не могу.

    P.S: И также, по поводу претензий к push pQueryData: после изменения(для проверки) в Test:
    Код (Text):
    1. push pQueryData
    2. pop edx
    На
    Код (Text):
    1. push 0
    2. pop edx[code]
    3.  
    Это никак не повлияло на характер исключения.
     
    Последнее редактирование: 24 июл 2023
  4. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Давай бинарник свой. И вообще почитай это, нужно перезагружать регистр SS. Вот тут готовый код на вб6 правда, но тут будут работать правильно и колбеки из 64 битного режима для функций типа SendMessage.
     
  5. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Хорошо, обязательно прочту чуть позже про SS. И можете также объяснить, зачем его перезагружать, разве это не должен делать процессор при вызове retf? А также, как игнорирование обновления SS(в ручную) может поломать стэк, ведь в отладчике вполне приемлемые указатели в ESP и EBP даже без манипуляций с SS.
    Бинарник тут:
    --- Сообщение объединено, 24 июл 2023 ---
    Извиняюсь 'code' - случайно забыл убрать :dntknw:
     

    Вложения:

    • cplustest.rar
      Размер файла:
      9,9 КБ
      Просмотров:
      126
  6. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    1.995
    Ну ок, если ты такой молодец и все настолько предусмотрел, что даже почти все работает, то я за тебя спокоен. Какого смысла у меня буквально копия того, что у тебя на картинке, работает, я не знаю, пусть это будет называться чудо неебесное.
     
  7. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Лучше бы вы были за меня беспокойны, как и я за себя, ибо такого рода чудеса мне не нужны - нужна стабильность , а не чудеса.
     
  8. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Да, у тебя дело в SS скорее всего т.к. характер бага тот же самый. Насчёт причин перезагрузки SS к сожалению ничего сказать не могу, т.к. не силен в "железе". Знаю есть implicit stack switching при переключении из 64 битного режима в Compatibility режим, там есть какие-то нюансы с регистром SS. Достаточно просто глянуть дизасм wow64cpu там сделано именно так.
    --- Сообщение объединено, 24 июл 2023 ---
    Кстати насколько я понял это проблема существует только на процессорах AMD.
     
    808Problem нравится это.
  9. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Спасибо тебе большое. Сохранение и восстановление(после syscall) SS - помогло !!! Вчера пол дня возился с этим эксепшеном... не знаю чёб без WASM делал, в окно бы вышел наверно.

    Да кстати и у меня как раз проц от AMD :)
     
  10. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    450
  11. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    189
    Такое поведение присуще вроде всем процессорам (как Intel, так и AMD), поскольку зависит от алгоритма работы самих инструкций syscall/sysret. Если посмотреть их описание в доках Intel'a том(2) "Instruction Set Reference", можно обнаружить там следующий момент:

     
  12. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Ну вот восстановить ESP я и пытаюсь, только сломанный селектор в SS не давал мне этого сделать. Чуть позже поотлаживаюсь в ядре - посмотрю, что там в MSR IA32_STAR в [63:48] битах валяется перед вызовом sysret.
     
  13. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    189
    808Problem,
    я к тому, что было сказано:
    ..а значит делаем вывод, что вход в х64 и выход из него фурычат (и проц здесь ни причём), а глюк именно в syscall. Можно попробовать бэкапить RSP до syscall, а не ESP после входа в х32.
     
    Mikl___ нравится это.
  14. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Не-а, бэкап до syscall не дал положительных результатов :dntknw:
     
  15. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.243
    наверно, проблема в том, что в дикой среде имеет место быть многопоточность, а при пошаговом прогоне в отладчике получается один поток :)
     
  16. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Хм, не думаю, что для того, чтобы нормально отработал сускалчик нужна целая пачка потоков для его величества :)
     
  17. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.243
    прикол ещё в том, что у современного проца даже одиночный поток не совсем классический в силу 3ое (out-of-order execution). хрень позволяет ускорять цикл, но увеличивает риски нарушения логики потока :)
     
  18. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    189
    Небольшой ликбез по WinDbg на эту тему..
    Для начала, командой "dg" (Display GDT) проверим дескрипторы(CS), на которые указывают селекторы 23/33h. (кстати WinDbg имеет локальный HtmlHelp, для данного случая формат запроса будет: ".hh dg"). Командой "r" можно получить адрес и размер GDT в памяти. Для дампа всей таблицы вводим "dg 00 80", где значение 80h = номер последнего селектора:

    Код (Text):
    1. 0: kd> r @gdtr, @gdtl
    2. gdtr=fffff800`03fff000  gdtl=007f   ;//<--- адрес и размер таблицы GDT
    3.  
    4. 0: kd> dg 23 33
    5.                                                         P Si Gr Pr Lo
    6. Sel         Base               Limit           Type     l ze an es ng  Flags
    7. ----  -----------------  -----------------  ----------  - -- -- -- --  --------
    8. 0023  00000000`00000000  00000000`ffffffff  Code RE Ac  3 Bg Pg P  Nl  00000cfb
    9. 0033  00000000`00000000  00000000`00000000  Code RE Ac  3 Nb By P  Lo  000002fb
    10.  
    11. ;//-- Описание флагов
    12. ;//---------------------
    13. Privilege  :  3  = User
    14. Size       :  23 = Big (32-бит),  33 = NoBig (в Long игнорируется)
    15. Granularity:  23 = Page, 33 = Byte
    16. Present    :  P  = OK
    17. Long seg   :  23 = No, 33 = Yes
    Здесь картина валидная - едем дальше..
    Вспомним, что говорит Intel об инструкции syscall:
    Значит на этапе загрузки системы (после создания GDT), "фиксированные значения" всё-же копируются из GDT. Правда я так и не нашёл инфы, где хранятся эти значения - ни в одном из MSR их нет! Проблема в том, что никакими средствами (в тч.и в WinDbg) мы не можем прочитать кэши дескрипторов из 8-байтной/скрытой части сегментных регистров. Остаётся только верить на-слово и надеяться, что при syscall в них лежат валидные значения. Если кто знает, как получить эти кэши - буду благодарен.

    Прочитать оригиналы из GDT можно по номерам селекторов, и той-же команды "dg".
    В логе ниже видно, что секция-кода в ядре х64 (PL=0) как и ожидается Long, а вот сегмент стека 32-битный с флагом "Nl".
    Код (Text):
    1. 0: kd> r cs,ss
    2. cs=0010  ss=0018
    3.  
    4. 0: kd> dg 10 18
    5.                                                        P Si Gr Pr Lo
    6. Sel         Base              Limit           Type     l ze an es ng  Flags
    7. ----  ----------------- -----------------  ----------  - -- -- -- --  --------
    8. 0010  00000000`00000000 00000000`00000000  Code RE Ac  0 Nb By P  Lo  0000029b
    9. 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:

    Intel_MSR.png

    Код (Text):
    1. IA32_EFER  (0xC0000080) — Конфиг расширенных возможностей       = 00000000`00000d01
    2.                           бит[0]  — SYSCALL/SYSRET enable,
    3.                           бит[8]  — Long mode enable (LME),
    4.                           бит[10] — Long mode active (LMA),
    5.                           бит[11] — No Execute Bit enable (NXE).
    6. IA32_STAR  (0xC0000081) — Селекторы сегментов Ring(0) и Ring(3) = 00230010`00000000
    7.                           биты[63:48] — селектор(CS) юзера,
    8.                           биты[47:32] — селектор(CS) ядра,
    9.                           мл. 32 бита — SYSCALL EIP в режиме wow64.
    10. IA32_LSTAR (0xC0000082) — RIP ядра для SYSCALL в режиме x64     = fffff800`02cc5180
    11. IA32_CSTAR (0xC0000083) — RIP ядра для SYSCALL в режиме wow64   = fffff800`02cc4ec0
    12. IA32_FMASK (0xC0000084) — маска для рег RFLAGS при SYSCALL x64  = 00000000`00004700
     
    Mikl___ и 808Problem нравится это.
  19. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Воу, фига - очень удобно кстати!

    Да, скорее всего. В в нелокальной ядерной отладке с rdmsr всё хорошо.

    Спасибо большое за ликбез, очень полезно!
     
    Marylin нравится это.
  20. 808Problem

    808Problem Member

    Публикаций:
    0
    Регистрация:
    1 мар 2023
    Сообщения:
    31
    Привет. Скажи пожалуйста, какой шрифт ты поставил в отладчике? Меня зацепило, такой же хАчу :)