Tls mapping - emulation

Тема в разделе "WASM.WIN32", создана пользователем galenkane, 12 мар 2026.

  1. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    453
    Анализ механизма ручного TLS вPE-загрузчике
    Вступление

    Разберём интересную реализацию ручного управления TLS (Thread Local Storage) в самодельном PE-загрузчике, который маппит образ прямо поверх собственной памяти без участия Windows Loader.
    Проблема, которую решает этот код
    Когда Windows грузит PE-файл штатно через LoadLibrary, загрузчик (ntdll!LdrpHandleTls) автоматически:
    • выделяет TLS-слот через внутренний bitmap в PEB
    • копирует шаблон данных (StartAddressOfRawData..EndAddressOfRawData) в TLS-блок каждого потока
    • вызывает TLS-коллбэки при DLL_PROCESS_ATTACH / DLL_THREAD_ATTACH
    Здесь же образ маппится вручную, минуя LDR — значит всё это надо воспроизвести самостоятельно.
    Структура состояния: PE_MANUAL_TLS_STATE
    Код (C++):
    1. typedef struct _PE_MANUAL_TLS_STATE {
    2.     kPointers Pointers;        // кэшированные указатели на WinAPI
    3.     PVOID       ImageBase;    // база загруженного образа
    4.     DWORD       TlsIndex;     // выделенный слот в PEB.TlsBitmap
    5.     DWORD_PTR   TlsIndexAddress;  // куда писать индекс (IMAGE_TLS_DIRECTORY.AddressOfIndex)
    6.     DWORD_PTR   RawDataStart;     // шаблон инициализации TLS
    7.     SIZE_T      RawDataSize;
    8.     SIZE_T      ZeroFillSize;     // нулевое заполнение после шаблона
    9.     SIZE_T      TemplateSize;     // RawDataSize + ZeroFillSize
    10.     BOOL        Initialized;
    11. } PE_MANUAL_TLS_STATE;
    Важный момент — g_ManualTlsState намеренно вынесен за пределы секции .manPe, чтобы не быть затёртым при маппинге образа поверх себя.
    Шаг 1 — Выделение TLS-слота: allocate_static_tls_index()
    Код (C++):
    1. PPEB peb = GET_PEB();
    2. for (int slot = (PE_STATIC_TLS_SLOT_LIMIT - 1); slot >= 0; --slot) {
    3.     DWORD mask = (1u << (slot % 32));
    4.     if ((peb->TlsBitmapBits[slot / 32] & mask) == 0) {
    5.         peb->TlsBitmapBits[slot / 32] |= mask;
    6.         return (DWORD)slot;
    7.     }
    8. }
    Вместо вызова TlsAlloc() (который честно пошёл бы в ntdll!RtlpAllocateOpaqueOsResourceId) код напрямую сканирует PEB.TlsBitmapBits и выставляет бит занятости вручную. Поиск идёт с конца (слот 63 → 0), чтобы не конфликтовать со слотами, которые Windows уже выделила низким номерам. Лимит PE_STATIC_TLS_SLOT_LIMIT = 64 — потому что статический TLS занимает только первые 64 слота (дальше идут динамические через FLS).
    Шаг 2 — Инициализация состояния: initialize_manual_tls_state()
    Код (C++):
    1. *(PDWORD)tlsIndexAddress = tlsIndex;
    Это ключевая строка: в IMAGE_TLS_DIRECTORY.AddressOfIndex записывается выделенный индекс — именно так скомпилированный код образа потом читает __declspec(thread) переменные через FS/GS:[TlsIndex * sizeof(PTR)].
    Дополнительно проверяется геометрия:
    • AddressOfIndex должен лежать внутри маппированного образа
    • StartAddressOfRawData / EndAddressOfRawData — тоже
    • rawDataEnd < rawDataStart → отказ (санитарная проверка)
    • overflow при rawDataSize + zeroFillSize → отказ
    Шаг 3 — TLS-вектор потока: ensure_tls_vector_slot()
    Это самая сложная часть. На каждом потоке TEB.ThreadLocalStoragePointer указывает на массив PVOID[], где по индексу TlsIndex лежит указатель на TLS-блок этого потока.
    TEB.ThreadLocalStoragePointer → [ ptr0 | ptr1 | ... | ptrN ]
    ↑ [TlsIndex] → TLS блок потока
    Проблема: Если вектор ещё не существует, или слишком короткий — надо выделить новый и скопировать старые записи.
    На x64 вектор выделяется через HeapAlloc с заголовком PE_NATIVE_TLS_VECTOR_HEADER (EntryCount + Reserved + ReservedPointer) — это имитирует формат, который Windows создаёт сама в ntdll!LdrpInitializeTls. Без этого заголовка LdrpFreeTls при завершении потока прочитает мусор и упадёт.
    На x86 используется VirtualAlloc (нет такого заголовка, другая схема).
    При копировании старого вектора критичная защита:
    Код (C++):
    1. // Копируем только биты, которые реально заняты в PEB.TlsBitmapBits
    2. if (peb2 && (peb2->TlsBitmapBits[bit / 32] & (1u << (bit % 32))))
    3.     newVector[bit] = currentVector[bit];
    4. // иначе — NULL, чтобы LdrpFreeTls не трогал мусор
    Шаг 4 — Установка TLS для потока: install_tls_for_current_thread()
    Код (C++):
    1. tlsBlock = kPointers->VirtualAlloc(NULL, allocationSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
    2. memzero(tlsBlock, allocationSize);
    3. if (tlsState->RawDataSize != 0)
    4.     memcopy(tlsBlock, (PVOID)tlsState->RawDataStart, tlsState->RawDataSize);
    5.     // ZeroFill уже нулевой после VirtualAlloc
    6. tlsVector[tlsState->TlsIndex] = tlsBlock;
    На x64 специально VirtualAlloc (не HeapAlloc) — чтобы Windows не отслеживала эту память и не пыталась её освободить при завершении потока своими механизмами.
    Шаг 5 — Перехват CreateThread: патч IAT
    Поскольку новые потоки создаются кодом уже загруженного образа, для них тоже нужен TLS. Решение — перехват через IAT:
    Код (C++):
    1. if (strcmp(procName, "CreateThread") == 0) {
    2.     g_OriginalCreateThread = (PVOID)(*funcAddress);
    3.     *funcAddress = (DWORD_PTR)wrapped_CreateThread;
    4. }
    wrapped_CreateThread / wrapped_beginthreadex оборачивают реальный старт:
    1. Выделяют PE_MANUAL_TLS_THREAD_CTX с оригинальным StartRoutine + Parameter
    2. Запускают вместо него manual_tls_thread_start
    3. Тот вызывает emulate_tls(..., DLL_THREAD_ATTACH) → устанавливает TLS для нового потока
    4. Запускает оригинальный StartRoutine
    5. После завершения — emulate_tls(..., DLL_THREAD_DETACH) → освобождает TLS-блок
    Шаг 6 — TLS-коллбэки: emulate_tls()
    После установки TLS-блока вызываются коллбэки:
    Код (C++):
    1. PIMAGE_TLS_CALLBACK* pCallbacks = (PIMAGE_TLS_CALLBACK*)pTls->AddressOfCallBacks;
    2. for (int i = 0; i < TLS_CALLBACK_MAX_ATTEMPTS; i++) {
    3.     pCallback = pCallbacks;
    4.     if (!pCallback) break;
    5.     pCallback(ImageData, reason, NULL);
    6. }
    Лимит 100 итераций — защита от битого образа с незавершённым массивом коллбэков. Перед чтением массива проверяется VirtualQuery — если страница недоступна, делается VirtualProtect(PAGE_READONLY).
    Общая схема потока выполнения
    manPe()
    └─ emulate_emulate_importtls(DLL_PROCESS_ATTACH)
    ├─ initialize_manual_tls_state() ← выделяет слот, пишет TlsIndex в образ
    ├─ install_tls_for_current_thread() ← выделяет блок, копирует шаблон
    └─ вызывает TLS-коллбэки
    enualte_import()
    └─ patch_thread_import() ← подменяет CreateThread/_beginthreadex в IAT
    новый поток (через CreateThread)
    └─ wrapped_CreateThread()
    └─ manual_tls_thread_start()
    ├─ emulate_tls(DLL_THREAD_ATTACH) ← TLS для нового потока
    ├─ оригинальный StartRoutine()
    └─ emulate_tls(DLL_THREAD_DETACH) ← освобождение
    Ключевые тонкости
    МоментПочему важно
    g_ManualTlsState вне .manPeСекция перезаписывается при маппинге — состояние погибло бы
    Поиск слота с конца (63→0)Не конфликтует со слотами CRT/Windows
    Заголовок вектора на x64LdrpFreeTls читает EntryCount перед освобождением
    VirtualAlloc для TLS-блокаHeapAlloc — Windows сама освободит при завершении потока, дважды
    Копирование только занятых битовМёртвые записи → crash в LdrpFreeTls
    Патч IAT, не сплайсЧище, не ломает CFG, работает в .manPe контексте
    Реализация полностью обходит ntdll!LdrpHandleTls и воспроизводит его логику вручную — достаточно корректно, чтобы работал статический __declspec(thread) и TLS-коллбэки в произвольных PE-образах без участия системного загрузчика.
     

    Вложения:

    Research нравится это.
  2. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    454
    Интересный способ краш тестирования чтобы не допускать утечек/повреждения данных если я понял. Лайк.
     
  3. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    418
    Research, а компилировать пробовали? Компилируется? А то может это ИИ нагенерировал, а за ним нужен глаз, да глаз. :)
     
  4. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    454
    Скомпелировал через ИИ.
     
  5. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    418
    Шутите? Или я отстал от жизни? :)
     
    CaptainObvious нравится это.
  6. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    2.153
    Судя по описанию это кусок загрузчика. Когда зачем-то пихают код в длл, а потом думают что с этим теперь делать. Есть видимо в этом какое-то мрачное садистское удовольствие: без понимания элементарных базовых вещей решать задачу ну хоть как-нибудь, пусть через жопу и по возможности даже не своей головой. Собственной проделанной работы ноль, вменяемых применений загрузке пе модуля в своем процессе суррогатным загрузчиком тоже не существует, пустое бесцельное дрочево.
     
  7. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    454
    Форумяне. Кто нибудь знает как невозбранно сделать пример такой dll на с?
     
    Последнее редактирование: 13 мар 2026
  8. Ahimov

    Ahimov Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    593

    Вложения:

    • ecv.pdf
      Размер файла:
      4,4 МБ
      Просмотров:
      94
  9. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    454
    Длинный текст. Такое ощущение что автор хоярит его для тренировки рук.
    Для интеллектуальных снобов: Пример будет куда ценнее теории. Я не о хелло-ворде задал вопрос.
    Твой линк по сути - балласт, информационный шум.
     
  10. Ahimov

    Ahimov Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    593
    Research

    Это ответы обученного бота по весьма обширной теме: создать анклав. В пару строк и без понятия не сделать, rtfm :preved:

    Вопросы справа кратко, типо:
    Начни с создания базы - секвенсора(визора).)
     
    Последнее редактирование: 13 мар 2026
  11. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    453
    Выходит что эмулится тлс вручную
    --- Сообщение объединено, 13 мар 2026 ---
    ну береш и пишешь с логами :black_eye:
    --- Сообщение объединено, 13 мар 2026 ---
    теоретично можешь архив допилиить если есть sgx железка
     

    Вложения:

  12. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    454
    galenkane, Ahimov, спасибо.

    Может быть слишком грубое упрощение: хочется иметь возможность передать на вход
    одну строку, на выходе получить другую, и чтобы это в целом не поддавалось анализу.

    Даже если злоумышленник дампит память, чтобы он видел зашифрованные данные.
     
    Последнее редактирование: 13 мар 2026
  13. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    453
    пак чО мин ты шо на приколе)) sgx под жылезо посмотри скок процов

    там ограничка похлеще сталинских времен
     
    Ahimov нравится это.
  14. Ahimov

    Ahimov Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    593
    Research

    Тогда нужны не анклавы, а виртуализация(интерпретаторы), vmp..

    - тело вирты можно прочитать, а что оно делает не понять ;)
     
  15. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    453
    делаю просто чтобы серв возвращал данные в потоке как ллмки свои ответы щас отдают ток в зашифрованном виде + виртой накрыть там никто такое не станет реверсить




    память то прочесть смогут а вот реверсануть врятли
     
  16. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    453
    ишка норм советы дает- если поле боя ето памяти то можно подпортить данныые из рандом места в памяти чтоб прог падала при таймчеке
     
  17. Ahimov

    Ahimov Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    593
    Попробовал посмотреть лоадер, весь диз в чат не влазит, но суть не в этом. В принципе можно разобрать за вечер ВЕСЬ загрузчик!
     

    Вложения:

    • native.pdf
      Размер файла:
      1,7 МБ
      Просмотров:
      85
  18. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    453
    upload_2026-3-24_16-32-26.png

    Есть более простые пути, так и гипервизор заресерчит.

    В ранней инициализации загрузку системного драйвера запускает I/O manager через IopLoad..., после чего управление переходит в MmLoadSystemImageEx, который координирует создание секции образа и передаёт работу в низкоуровневые Mi*-функции.
    Дальше Memory Manager в MiCreateImageFileMap/MiReadImageHeaders читает заголовки файла, а в MiVerifyImageHeader проверяет, что это корректный PE-образ: сигнатуры MZ и PE, тип optional header, число секций и ключевые поля образа.
    После успешной проверки ядро строит control area, мапит образ в системное адресное пространство через MiMapSystemImage и заносит метаданные модуля в loader bookkeeping структуры вроде _KLDR_DATA_TABLE_ENTRY.
     
  19. Ahimov

    Ahimov Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    593
    galenkane,

    Ну и что ты понял из этого выхлопа ?

    Полная чепуха, без связи. Бредогенератор.

    Какие боты в виндебаг, вы умом двинулись ?
     
  20. galenkane

    galenkane Active Member

    Публикаций:
    1
    Регистрация:
    13 янв 2017
    Сообщения:
    453
    можно в промт твой стиль общения заложить