Spawn Indy and vibevisors users here

Тема в разделе "WASM.BEGINNERS", создана пользователем galenkane, 16 июн 2025.

  1. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    454
    https://github.com/NadavLor/windbg-ext-mcp

    Ну шо посоны, финита ля эксплория начинается!

    Кто-то насмехался против визоров, но они живы как никогда.

    upload_2025-6-16_18-45-32.png

    Ниже под спойлером эксплейн для интереса.
    Архитектурная схема для отладки VM в VMware с одного хоста:
    Код (Text):
    1.  
    2. ┌─────────────────────────────────────────────────────────────┐
    3. │                    ФИЗИЧЕСКИЙ ХОСТ                          │
    4. │                                                             │
    5. │  ┌─────────────────┐                 ┌───────────────────┐  │
    6. │  │   HOST OS       │                 │    VMware VM      │  │
    7. │  │  (Windows)      │                 │   (Guest OS)      │  │
    8. │  │                 │                 │                   │  │
    9. │  │ ┌─────────────┐ │    Named Pipe   │ ┌───────────────┐ │  │
    10. │  │ │   WinDbg    │ │ ◄──────────────► │ │ Kernel Debug  │ │  │
    11. │  │ │ (Debugger)  │ │  \\.\pipe\com1  │ │   Settings    │ │  │
    12. │  │ └─────────────┘ │                 │ └───────────────┘ │  │
    13. │  │                 │                 │                   │  │
    14. │  │ ┌─────────────┐ │                 │ ┌───────────────┐ │  │
    15. │  │ │  Symbols    │ │                 │ │  Serial Port  │ │  │
    16. │  │ │   Store     │ │                 │ │  (Virtual)    │ │  │
    17. │  │ └─────────────┘ │                 │ └───────────────┘ │  │
    18. │  └─────────────────┘                 └───────────────────┘  │
    19. │                                                             │
    20. └─────────────────────────────────────────────────────────────┘
    21.  
    Детализированная схема коммуникации:
    Код (Text):
    1.  
    2. WinDbg Process          VMware Hypervisor         Guest VM Kernel
    3. ┌─────────────┐        ┌─────────────────┐        ┌─────────────┐
    4. │             │        │                 │        │             │
    5. │ Debug       │ ────── │ Named Pipe      │ ────── │ KD Stub     │
    6. │ Engine      │ read/  │ Handler         │ Serial │ (kdcom.dll) │
    7. │             │ write  │                 │ I/O    │             │
    8. │             │        │                 │        │             │
    9. │ Symbol      │        │ VM Memory       │        │ Target      │
    10. │ Engine      │ ────── │ Access Layer    │ ────── │ Process     │
    11. │             │        │                 │        │             │
    12. └─────────────┘        └─────────────────┘        └─────────────┘
    13.  
    Схема данных:
    Код (Text):
    1.  
    2. ┌─── HOST MEMORY SPACE ────┐  ┌─── VM MEMORY SPACE ────┐
    3. │                          │  │                        │
    4. │ WinDbg.exe               │  │ Windows Kernel         │
    5. │ ├─ Debug Commands        │  │ ├─ KDCOM Driver        │
    6. │ ├─ Symbol Cache          │  │ ├─ Target Process      │
    7. │ └─ Memory Analysis       │  │ └─ System State        │
    8. │                          │  │                        │
    9. │ \\.\pipe\com1           │  │ COM1: (Virtual Serial) │
    10. │          ▲               │  │          ▲             │
    11. └──────────┼───────────────┘  └──────────┼─────────────┘
    12.            │                             │
    13.            └─────── VMware Pipe ─────────┘
    14.  
    Сетевая схема (если нужна):
    Код (Text):
    1.  
    2. Host OS Network Stack
    3. ┌─────────────────────┐
    4. │ Real NIC           │ ──── Внешняя сеть
    5. │ VMnet1 (Host-only) │ ──── VM отладка
    6. │ VMnet8 (NAT)       │ ──── VM интернет
    7. └─────────────────────┘
    8.  
    Минимальные требования для комфортной работы:
    • CPU: 4+ ядра (Intel i5/AMD Ryzen 5)
    • RAM: 8+ GB (лучше 16+ GB)
    • SSD: 50+ GB свободного места
    • Сеть: не критична при использовании named pipe
    Архитектура загрузки символов:
    Код (Text):
    1.  
    2. ┌──── WinDbg Process Memory ────┐
    3. │                               │
    4. │ ┌─── Symbol Engine ─────┐     │
    5. │ │                       │     │
    6. │ │ ┌─ Symbol Cache ────┐ │     │
    7. │ │ │ • Loaded PDBs    │ │     │
    8. │ │ │ • Type info      │ │     │
    9. │ │ │ • Function names │ │     │
    10. │ │ │ • Line numbers   │ │     │
    11. │ │ └──────────────────┘ │     │
    12. │ │                       │     │
    13. │ │ ┌─ Lazy Loading ───┐ │     │
    14. │ │ │ • On-demand load │ │     │
    15. │ │ │ • LRU eviction   │ │     │
    16. │ │ └──────────────────┘ │     │
    17. │ └───────────────────────┘     │
    18. └───────────────────────────────┘
    19.  
    Механизм работы с символами:
    Ленивая загрузка (Lazy Loading):

    • Символы загружаются по мере необходимости
    • При первом обращении к модулю
    • Не все символы сразу в памяти
    Кэширование:
    Код (Text):
    1.  
    2. Disk Symbol Store    →    Memory Cache    →    Active Symbols
    3. ┌─────────────────┐       ┌─────────────┐      ┌─────────────┐
    4. │ Local Cache     │  ──→  │ Recently    │ ──→  │ Currently   │
    5. │ • .pdb files    │       │ Used PDBs   │      │ Needed      │
    6. │ • 1-5 GB        │       │ • 100-500MB │      │ • 10-50 MB  │
    7. └─────────────────┘       └─────────────┘      └─────────────┘
    8.  
    Типичное потребление памяти символами:
    Windows Kernel символы:

    • ntoskrnl.exe: ~15-30 MB
    • hal.dll: ~2-5 MB
    • Драйверы: ~1-10 MB каждый
    Пользовательские модули:
    • Большие приложения: ~20-100 MB
    • Системные DLL: ~5-20 MB каждая
    --- Сообщение объединено, 16 июн 2025 ---
    Это часы
    [​IMG]
    --- Сообщение объединено, 16 июн 2025 ---
    Еще пример

    Код (Text):
    1.  
    2. Okay, let's use WinDbg commands to examine how modules find functions in other modules, focusing on the Import Address Table (IAT) and Export Address Table (EAT).
    3.  
    4. We'll use `KERNEL32.dll` importing a function from `ntdll.dll` as an example. A common function `KERNEL32.dll` might import from `ntdll.dll` is `NtCreateFile`.
    5.  
    6. **Step 1: List loaded modules to get base addresses.**
    7. First, let's list the loaded modules to find the base addresses of `ntdll.dll` and `KERNEL32.dll`.
    8.  
    9.  
    10.  
    11. Ran tool
    12.  
    13. Ясно, давайте используем WinDbg, чтобы посмотреть, как модули находят функции в других модулях, сосредоточив внимание на таблице импорта (IAT) и таблице экспорта (EAT).
    14.  
    15. Мы уже получили базовые адреса `ntdll.dll` и `KERNEL32.dll`. Теперь давайте найдем адрес функции `NtCreateFile` в `ntdll.dll` (из таблицы экспорта) и затем посмотрим, куда указывает запись этой функции в таблице импорта `KERNEL32.dll`.
    16.  
    17. **Шаг 2: Найти адрес `NtCreateFile` в `ntdll.dll` (Таблица экспорта).**
    18. Я использую команду `x ntdll!NtCreateFile`, чтобы найти ее адрес.
    19.  
    20.  
    21. Ran tool
    22.  
    23. Мы выяснили, что `ntdll!NtCreateFile` находится по адресу `00007ffc`8344df80`.
    24.  
    25. Теперь давайте посмотрим, как `KERNEL32.dll` "знает" об этом адресе. Модули используют **Таблицу адресов импорта (IAT)** для хранения адресов функций, импортируемых из других DLL.
    26.  
    27. **Шаг 3: Найти запись `NtCreateFile` в IAT `KERNEL32.dll`.**
    28. Обычно импортированные функции представлены в IAT с префиксом `_imp_`. Давайте попробуем найти это, используя `x KERNEL32!_imp_NtCreateFile`.
    29.  
    30.  
    31.  
    32. Ran tool
    33.  
    34. Мы нашли адрес записи `KERNEL32!_imp_NtCreateFile` в IAT `KERNEL32.dll` как `00007ffc`815b60e0`.
    35.  
    36. Теперь, чтобы подтвердить, что эта запись действительно указывает на `ntdll!NtCreateFile`, мы можем прочитать содержимое памяти по этому адресу. Во время загрузки модуля загрузчик Windows "фиксирует" (patch) эту запись, чтобы она содержала фактический адрес импортируемой функции.
    37.  
    38. **Шаг 4: Прочитать содержимое IAT записи `KERNEL32!_imp_NtCreateFile`.**
    39. Я использую команду `dps` (display pointer and symbol) для просмотра содержимого памяти по адресу `00007ffc`815b60e0.
    40.  
    41.  
    42.  
    43. Ran tool
    44.  
    45. Мы успешно выяснили, как базовые модули взаимодействуют в WinDbg:
    46.  
    47. 1.  Мы нашли адрес функции `ntdll!NtCreateFile` в `ntdll.dll`, который оказался `00007ffc`8344df80`. Это адрес функции в **таблице экспорта (EAT)** `ntdll.dll`.
    48. 2.  Затем мы нашли запись `KERNEL32!_imp_NtCreateFile` в **таблице импорта (IAT)** `KERNEL32.dll` по адресу `00007ffc`815b60e0`.
    49. 3.  Наконец, мы прочитали содержимое памяти по этому адресу `00007ffc`815b60e0` и увидели, что оно указывает точно на адрес `00007ffc`8344df80`, то есть на `ntdll!NtCreateFile`.
    50.  
    51. **Вывод:**
    52.  
    53. Таким образом, `KERNEL32.dll` "знает" о местонахождении функции `NtCreateFile` в `ntdll.dll` благодаря **загрузчику Windows**. Во время загрузки системы или процесса, загрузчик "заполняет" (patching) запись в таблице импорта `KERNEL32.dll` (`_imp_NtCreateFile`) фактическим адресом функции `NtCreateFile`, который он получает из таблицы экспорта `ntdll.dll`. Это позволяет `KERNEL32.dll` напрямую вызывать эту функцию из `ntdll.dll`.
    54.  

    Код (Text):
    1.  
    2. Хороший вопрос! "Первая точка" загрузчика Windows, с которой начинается выполнение пользовательского приложения, довольно интересна.
    3.  
    4. В контексте отладки пользовательского режима с помощью WinDbg, самая ранняя и обычно видимая точка входа, относящаяся к загрузчику, это функция **`ntdll!LdrInitializeThunk`**.
    5.  
    6. Эта функция является "связующим звеном" (thunk) между низкоуровневой инициализацией, которая уже произошла в режиме ядра, и началом выполнения пользовательского кода. Именно `LdrInitializeThunk` в конечном итоге вызывает `LdrpInitializeProcess`, которую мы рассматривали ранее, для выполнения значительной части инициализации нового процесса.
    7.  
    8. Давайте посмотрим на ассемблерный код `ntdll!LdrInitializeThunk` в WinDbg. Из нашей предыдущей трассировки стека мы видели `ntdll!LdrInitializeThunk+0xe` по адресу `00007ffc`83425c1e`. Это означает, что сама функция `ntdll!LdrInitializeThunk` начинается чуть раньше, по адресу `00007ffc`83425c10`.
    9.  
    10. **Шаг 1: Дизассемблируем `ntdll!LdrInitializeThunk`:**
    11.  
    12. { "success": true, "result": "ntdll!LdrInitializeThunk:\n00007ffc`83425c10 4053 push rbx\n00007ffc`83425c12 4883ec20 sub rsp,20h\n00007ffc`83425c16 488bd9 mov rbx,rcx\n00007ffc`83425c19 e81a000000 call ntdll!LdrpInitialize (00007ffc`83425c38)\n00007ffc`83425c1e b201 mov dl,1\n00007ffc`83425c20 488bcb mov rcx,rbx\n00007ffc`83425c23 e818810200 call ntdll!NtContinue (00007ffc`8344dd40)\n00007ffc`83425c28 8bc8 mov ecx,eax\n00007ffc`83425c2a e811c90800 call ntdll!RtlRaiseStatus (00007ffc`834b2540)\n00007ffc`83425c2f cc int 3\n00007ffc`83425c30 cc int 3\n00007ffc`83425c31 cc int 3\n00007ffc`83425c32 cc int 3\n00007ffc`83425c33 cc int 3\n00007ffc`83425c34 cc int 3\n00007ffc`83425c35 cc int 3\n", "execution_method": "optimized", "performance_info": { "cached": false, "response_time": 0.114588, "retries_used": 0, "timeout_category": "execution", "optimization_level": "direct" }, "resilience_info": null }
    13.  
    14. Ran tool
    15.  
    16. Как видно из дизассемблированного кода, **`ntdll!LdrInitializeThunk`** действительно является самой ранней и обычно первой точкой, которую мы видим в WinDbg в контексте инициализации загрузчика Windows для пользовательского режима.
    17.  
    18. Вот почему это "первая точка" и как она работает:
    19.  
    20. *   **Передача управления из ядра:** После того как ядро Windows завершает свои начальные этапы загрузки для нового процесса, оно передает управление в пользовательский режим, и первой функцией, которая выполняется в контексте пользовательского процесса, является именно `ntdll!LdrInitializeThunk`. Это своего рода "трамплин" из режима ядра в режим пользователя.
    21.  
    22. *   **Цепочка инициализации:**
    23.     1.  **`ntdll!LdrInitializeThunk`**: Эта функция, как вы видите в дизассемблере, выполняет начальную настройку стека и регистров, а затем делает **вызов (`call`)** к `ntdll!LdrpInitialize`.
    24.     2.  **`ntdll!LdrpInitialize`**: Эта функция (которая затем, в свою очередь, вызывает `ntdll!LdrpInitializeProcess`, как мы видели в стеке ранее) отвечает за более глубокую инициализацию, такую как:
    25.         *   Загрузка необходимых DLL (например, `KERNEL32.dll`, `user32.dll` и т.д.).
    26.         *   Разрешение импортов (заполнение IAT).
    27.         *   Выполнение функций инициализации для загруженных DLL (например, `DllMain`).
    28.         *   Подготовка среды для выполнения основного исполняемого файла приложения.
    29.     3.  **`ntdll!LdrpInitializeProcess`**: Как мы уже обсуждали, это часть `LdrpInitialize`, которая выполняет фактическую работу по инициализации процесса.
    30.  
    31. Таким образом, хотя `LdrInitializeThunk` сама по себе не выполняет всю работу по загрузке, она является **самой первой функцией в пользовательском режиме**, которая запускает всю цепочку операций загрузчика Windows. Это то место, где операционная система передает "эстафетную палочку" управлению пользовательскому пространству, чтобы подготовить приложение к запуску.
    32.  
    --- Сообщение объединено, 16 июн 2025 ---
    Имба или нет

    Код (Text):
    1.  
    2. Отлично! Давайте пошагово проследим, как происходит передача управления внутри `ntdll!RtlUserThreadStart` до функции вашего потока.
    3.  
    4. Как мы видели в дизассемблере, `ntdll!RtlUserThreadStart` готовится вызвать `Kernel32ThreadInitThunkFunction`. Эта функция из `kernel32.dll` является "переходником" (thunk), который в конечном итоге вызывает вашу реальную функцию потока, которую вы указали при его создании.
    5.  
    6. Давайте выполним несколько шагов, чтобы пройти через этот процесс:
    7.  
    8. **Шаг 1: Выполним первую инструкцию `sub rsp, 78h`**
    9. Эта инструкция является частью пролога функции и выделяет место на стеке.
    10.  
    11.  
    12.  
    13. Ran tool
    14.  
    15. Я выполнил первую инструкцию. Теперь давайте продолжим.
    16.  
    17. **Шаг 2: Выполним `mov r9,rcx`**
    18. Эта инструкция перемещает значение из `rcx` в `r9`. `rcx` часто содержит первый аргумент функции, а `r9` может быть использован для передачи аргументов или временного хранения.
    19.  
    20.  
    21.  
    22. Ran tool
    23.  
    24. Я выполнил вторую инструкцию. Теперь давайте продолжим.
    25.  
    26. **Шаг 3: Выполним `mov rax,qword ptr [ntdll!Kernel32ThreadInitThunkFunction]`**
    27. Эта инструкция загружает адрес функции `Kernel32ThreadInitThunkFunction` из `ntdll.dll` в регистр `RAX`.
    28.  
    29.  
    30.  
    31. Ran tool
    32.  
    33. Я выполнил третий шаг, который загрузил адрес `Kernel32ThreadInitThunkFunction` в регистр `RAX`. Теперь `RAX` содержит адрес функции из `kernel32.dll`, которая будет вызвана следующей.
    34.  
    35. **Шаг 4: Выполним `call qword ptr [ntdll!_guard_dispatch_icall_fptr]`**
    36. Это ключевой шаг, который передаст управление `Kernel32ThreadInitThunkFunction`. Мы используем `p` (step over), чтобы выполнить весь вызов, не входя в детали `_guard_dispatch_icall_fptr`, который является частью механизма CFG.
    37.  
    38.  
    39.  
    40. Ran tool
    41.  
    42. Я выполнил инструкцию `call`. Теперь мы должны быть внутри `KERNEL32!BaseThreadInitThunk` (или `Kernel32ThreadInitThunkFunction`, как она называется в `ntdll`), которая является точкой входа для потоков в `kernel32.dll`.
    43.  
    44. Давайте проверим текущее состояние, чтобы увидеть, куда мы попали:
    45.  
    46. **Шаг 5: Проверим текущее состояние (регистры и стек) после вызова.**
    47.  
    48. **1. Регистры:**
    49.  
    50.  
    51. Ran tool
    52.  
    53. Мои извинения, в предыдущем шаге я использовал `p` (step over) вместо `t` (trace into) для вызова, который должен был перевести нас в `Kernel32ThreadInitThunkFunction`. Это моя ошибка.
    54.  
    55. Давайте исправим это и пошагово войдем в функцию, чтобы увидеть, как происходит передача потока выполнения. Мы начнем с текущего `rip`, который, судя по последнему выводу, находится на `00007ffc`833fcc81` (инструкция `je`).
    56.  
    57. **Шаг 1: Дойдем до инструкции `call`**
    58. Нам нужно выполнить несколько инструкций, чтобы добраться до `call qword ptr [ntdll!_guard_dispatch_icall_fptr]` по адресу `00007ffc`833fcc8b`.
    59.  
    60. Выполним `p` три раза:
    61. 1.  `00007ffc`833fcc81 7410            je      ntdll!RtlUserThreadStart+0x23` (Предполагаем, что переход не состоится, так как `rax` не был нулевым)
    62. 2.  `00007ffc`833fcc83 4c8bc2          mov     r8,rdx`
    63. 3.  `00007ffc`833fcc86 488bd1          mov     rdx,rcx`
    64. 4.  `00007ffc`833fcc89 33c9            xor     ecx,ecx`
    65.  
    66.  
    67.  
    68. Ran tool
    69.  
    70. Я выполнил предыдущие инструкции, и теперь мы находимся прямо перед `call` к `Kernel32ThreadInitThunkFunction`.
    71.  
    72. **Шаг 2: Выполним `call qword ptr [ntdll!_guard_dispatch_icall_fptr]` с трассировкой (`t`)**
    73. Это позволит нам *войти* в функцию, на которую указывает `RAX` (то есть `KERNEL32!BaseThreadInitThunk`), и проследить передачу управления.
    74.  
    75.  
    76.  
    77. Ran tool
    78.  
    79. Я выполнил команду `t` (trace into). Теперь мы должны находиться внутри `KERNEL32!BaseThreadInitThunk`, которая является фактической функцией-оберткой для потоков в `kernel32.dll`.
    80.  
    81. Давайте проверим текущее состояние, чтобы увидеть, куда мы попали, и как происходит дальнейшая передача управления к вашей функции потока.
    82.  
    83. **Шаг 3: Проверим текущее состояние (регистры и стек) после входа в `KERNEL32!BaseThreadInitThunk`.**
    84.  
    85. **1. Регистры:**
    86.  
    87.  
    88. Ran tool
    89.  
    90. Отлично! Мы успешно вошли в `KERNEL32!BaseThreadInitThunk`.
    91.  
    92. Судя по выводу регистров:
    93. *   `rip=00007ffc`81547360`, что является началом `KERNEL32!BaseThreadInitThunk`.
    94. *   `rsp=000000000014ff58`, стек скорректирован.
    95. *   `rcx=0000000000000000`, `rdx=00007ff7a58e11b3`, `r8=00000000002b9000`, `r9=00007ff7a58e11b3`. Эти регистры, скорее всего, содержат аргументы, переданные в `BaseThreadInitThunk`, включая адрес вашей функции потока и ее аргумент. Обычно, адрес функции потока передается как первый аргумент (в `rcx`), а аргумент для этой функции потока - как второй (в `rdx`). Однако, здесь `rcx` нулевой, что может указывать на то, что аргументы были переданы по-другому или это какая-то служебная функция. Мы увидим это при дизассемблировании.
    96.  
    97. **Шаг 1: Проверим стек после входа в `KERNEL32!BaseThreadInitThunk`:**
    98.  
    99.  
    100.  
    101. Ran tool
    102.  
    103. Вы находитесь в `KERNEL32!BaseThreadInitThunk`. Это обертка, которая подготавливает среду и затем вызывает вашу функцию потока. Из стека видно, что она была вызвана из `ntdll!RtlUserThreadStart+0x21`.
    104.  
    105. Чтобы понять, как именно происходит передача потока выполнения отсюда к вашей функции, нам нужно **дизассемблировать `KERNEL32!BaseThreadInitThunk`**.
    106.  
    107. **Шаг 2: Дизассемблируем `KERNEL32!BaseThreadInitThunk`:**
    108.  
    109.  
    110.  
    111. Ran tool
    112.  
    113. Вы отлично подметили! Теперь, когда мы находимся в `KERNEL32!BaseThreadInitThunk`, давайте проследим, как именно эта функция передает управление вашей функции потока.
    114.  
    115. Как мы видим из дизассемблирования `KERNEL32!BaseThreadInitThunk`, она выполняет следующие ключевые действия:
    116.  
    117. ```assembly
    118. KERNEL32!BaseThreadInitThunk:
    119. ...
    120. 00007ffc`81547368 498bc8          mov     rcx,r8      ; Перемещаем аргумент потока (который был в r8) в rcx
    121. 00007ffc`8154736b 488bc2          mov     rax,rdx     ; Перемещаем адрес функции потока (который был в rdx) в rax
    122. 00007ffc`8154736e ff15d4f20600    call    qword ptr [KERNEL32!_guard_dispatch_icall_fptr (00007ffc`815b6648)] ; Вызываем функцию, на которую указывает rax
    123. ...
    124. ```
    125.  
    126. Из предыдущего анализа регистров, у нас было:
    127. *   `rdx=00007ff7a58e11b3` (вероятно, адрес вашей функции потока)
    128. *   `r8=00000000002b9000` (вероятно, аргумент для вашей функции потока)
    129.  
    130. Итак, `BaseThreadInitThunk` берет адрес вашей функции потока из `rdx` и помещает его в `rax`, а аргумент потока из `r8` и помещает его в `rcx`. Затем он выполняет `call`, которая, благодаря CFG и конечному адресу в `rax`, перенесет выполнение прямо в вашу функцию потока.
    131.  
    132. Давайте выполним эти шаги:
    133.  
    134. **Шаг 1: Выполняем `sub rsp,28h`**
    135.  
    136.  
    137.  
    138. Ran tool
    139.  
    140. Я выполнил `sub rsp,28h`. Теперь давайте продолжим.
    141.  
    142. **Шаг 2: Выполняем `test ecx,ecx` и потенциальный `jne`**
    143. Эти инструкции проверяют значение `ecx` и, если оно не равно нулю, переходят к другой части функции. В случае обычной инициализации потока, этот переход, скорее всего, не состоится.
    144.  
    145.  
    146.  
    147. Ran tool
    148.  
    149. Я выполнил предыдущие инструкции. Теперь мы находимся прямо перед инструкциями, которые перемещают адрес вашей функции потока и ее аргумент в правильные регистры (`RAX` и `RCX`) для вызова.
    150.  
    151. **Шаг 3: Выполним `mov rcx,r8` и `mov rax,rdx`**
    152. Эти инструкции подготавливают регистры `RCX` (первый аргумент, который будет передан вашей функции потока) и `RAX` (адрес вашей функции потока).
    153.  
    154.  
    155.  
    156. Ran tool
    157.  
    158. Я выполнил предыдущие инструкции, которые подготовили регистры `RCX` (для аргумента вашей функции потока) и `RAX` (для адреса вашей функции потока).
    159.  
    160. Теперь мы находимся прямо перед ключевым вызовом, который передаст управление вашей функции потока.
    161.  
    162. **Шаг 4: Выполним `call qword ptr [KERNEL32!_guard_dispatch_icall_fptr]` с трассировкой (`t`)**
    163. Эта инструкция фактически передаст управление вашей функции потока, адрес которой теперь находится в `RAX`. Мы используем `t` (trace into), чтобы войти в вашу функцию.
    164.  
    165.  
    166.  
    167. Ran tool
    168.  
    169. Я выполнил команду `t` (trace into). Теперь выполнение должно было перейти в вашу функцию потока.
    170.  
    171. Давайте проверим текущее состояние, чтобы подтвердить, что мы находимся в вашей функции потока и как произошла передача управления.
    172.  
    173. **Шаг 5: Проверим текущее состояние (регистры и стек) после входа в вашу функцию потока.**
    174.  
    175. **1. Регистры:**
    176.  
    177.  
    178. Ran tool
    179.  
    180. Мои извинения за неточность в предыдущем объяснении! Моя ошибка, я должен был быть более внимательным к выводу `r` после команды `t`.
    181.  
    182. Вывод `r` показывает, что `rip=00007ffc`8154736e`, что является адресом инструкции `call qword ptr [KERNEL32!_guard_dispatch_icall_fptr...]` внутри `KERNEL32!BaseThreadInitThunk`. Это означает, что **мы только что *выполнили* эту инструкцию `call`**, и теперь выполнение должно было перейти к ее цели, которая, как указано в выводе, является **`ntdll!LdrpDispatchUserCallTarget`**.
    183.  
    184. Мы еще не в вашей функции потока, но мы очень близко! `ntdll!LdrpDispatchUserCallTarget` - это служебная функция в `ntdll.dll`, которая обрабатывает вызовы пользовательских функций после проверок CFG (Control Flow Guard), и именно она затем передаст управление вашей функции потока.
    185.  
    186. Давайте подтвердим наше текущее местоположение и затем пошагово перейдем в вашу функцию.
    187.  
    188. **Шаг 1: Проверим текущую инструкцию, чтобы убедиться, что мы находимся в `ntdll!LdrpDispatchUserCallTarget`.**
    189. Мы используем команду `u rip L1` (unassemble current instruction for 1 line).
    190.  
    191.  
    192.  
    193. Ran tool
    194.  
    195. Мои глубокие извинения за повторную ошибку! Вы совершенно правы, я снова неточно отследил выполнение.
    196.  
    197. Я понял. Мы все еще находимся на инструкции `call` внутри `KERNEL32!BaseThreadInitThunk`, которая должна была привести нас к `ntdll!LdrpDispatchUserCallTarget`. Моя цель - пошагово показать, как происходит передача управления *до вашей функции потока*.
    198.  
    199. Давайте исправим это прямо сейчас. Мы снова выполним `t` (trace into) из текущего положения, чтобы действительно войти в `ntdll!LdrpDispatchUserCallTarget`.
    200.  
    201. **Шаг 1: Войдем в `ntdll!LdrpDispatchUserCallTarget`**
    202. Текущий `rip` находится на `00007ffc`8154736e` (`KERNEL32!BaseThreadInitThunk+0xe`), который является инструкцией `call` к `ntdll!LdrpDispatchUserCallTarget`. Используем `t`.
    203.  
    204.  
    205.  
    206. Ran tool
    207.  
    208. Я снова выполнил команду `t`. Теперь мы должны были войти в `ntdll!LdrpDispatchUserCallTarget`.
    209.  
    210. Давайте проверим текущее состояние (регистры и стек), чтобы убедиться, что мы находимся в этой функции, и как происходит дальнейшая передача управления к вашей функции потока.
    211.  
    212. **Шаг 1: Проверим текущее состояние (регистры и стек) после входа в `ntdll!LdrpDispatchUserCallTarget`.**
    213.  
    214. **1. Регистры:**
    215.  
    216.  
    217. Ran tool
    218.  
    219. Отлично! Мы наконец-то добрались до вашего приложения!
    220.  
    221. Текущая точка выполнения: `MyCustomApp!ILT+430(mainCRTStartup)` по адресу `00007ff7`a58e11b3`.
    222.  
    223. Это означает, что передача потока выполнения от системных библиотек к вашему пользовательскому коду успешно завершена.
    224.  
    225. Вот как это произошло пошагово:
    226.  
    227. 1.  **`ntdll!RtlUserThreadStart`**: Как мы обсуждали, это самая ранняя точка входа для *любого* нового потока в пользовательском режиме. Она получает контроль от ядра.
    228. 2.  **`KERNEL32!BaseThreadInitThunk`**: `RtlUserThreadStart` передает управление этой функции в `kernel32.dll`. `BaseThreadInitThunk` является оберткой, которая готовит среду для вашей функции потока, извлекая ее адрес и аргументы.
    229. 3.  **`ntdll!LdrpDispatchUserCallTarget`**: `BaseThreadInitThunk` не вызывает вашу функцию напрямую, а использует служебную функцию `ntdll!LdrpDispatchUserCallTarget` (через механизм CFG). Эта функция выполняет дополнительные проверки и, наконец, безопасно передает управление в целевой адрес, который в данном случае является вашим приложением.
    230. 4.  **`MyCustomApp!ILT+430(mainCRTStartup)`**: Это первая реальная точка входа в ваш исполняемый файл. `ILT` (Import-Local Thunk) указывает, что это "трамплин" к настоящей функции `mainCRTStartup`.
    231.  
    232. **Что такое `mainCRTStartup`?**
    233. `mainCRTStartup` (или `WinMainCRTStartup` для графических приложений) — это точка входа, предоставляемая стандартной библиотекой времени выполнения C/C++ (CRT). Прежде чем вызвать вашу основную функцию `main()` или `WinMain()`, `mainCRTStartup` выполняет ряд важных задач инициализации, таких как:
    234. *   Инициализация глобальных переменных.
    235. *   Установка обработчиков исключений.
    236. *   Инициализация стандартных потоков ввода/вывода (stdin, stdout, stderr).
    237. *   Вызов TLS-колбэков (которые мы упоминали в статье).
    238.  
    239. **Текущая инструкция:**
    240. `00007ff7`a58e11b3 e938170000      jmp     MyCustomApp!mainCRTStartup (00007ff7`a58e28f0)`
    241. Эта инструкция `jmp` просто перенаправляет выполнение к фактическому началу функции `mainCRTStartup`.
    242.  
     
    Последнее редактирование: 17 июн 2025
    shanya0xff и Research нравится это.