Статья ASM — Как уйти из под отладки

Тема противодействия отладке давно заезжена, и в сети можно встретить огромное кол-во классических (миссионерских) вариантов, от обычной проверки флагов функцией IsDebuggerPresent(), до более изощрённых финтов, типа срыв-стека приложения, или расширения его секций. Но разрабы таких инструментов как: Ghidra, x64Dbg, OllyDbg, IDA, Immunity и других, тоже почитывают те-же материалы, что и мы с вами. Движимые инстинктом выживания и удержанием своих позиций на рынке, они стараются учитывать эти нюансы в своих продуктах, в результате чего добрый десяток известных алгоритмов анти-дебага палится ещё на взлёте, различными их плагинами.

Однако в работе отладчиков есть и механизмы, от которых разрабы не могут избавиться в принципе, т.к. они являются фундаментальными для данного рода программ – это точки-останова, или Breakpoint (кому как нравится). По сей день, ни один из отладчиков так и не научился противостоять сносу бряков, на что мы и сделаем ставку в этой статье. Общая идея такова, что если правильно выбрать "время и место", то факт восстановления ВР в прежнее состояние, позволит нашей программе выйти из-под контроля отладки, выполнив свой код на одном дыхании так, как-будто его запустили под реальным процессором. Авансом скажу, что это простой, элегантный и чертовски полезный баг, затрагивающий буквально все заточенные под Win32/64 отладчики.

Оглавление:

1. Принцип работы отладчиков;
2. Основная идея;
3. Практика. Сброс точки-останова INT-3;
4. Заключение.
---------------------------------------------------


1. Принцип работы отладчиков

Практика – дело хорошее, но при внештатных ситуациях, пробелы в теории могут запросто выбить нас из седла. Поэтому для начала рассмотрим, какие высокие принципы заложены в работу отладчиков. По большому счёту, свой юзермодный дебаггер сможет написать даже программист средней руки, поскольку в системах класса Win всё уже предусмотрено для этого, в нёдрах библиотек Kernel32, Dbgeng и Dbghelp.dll. Эта группа функций известна под общим названием DebugAPI, оптом и в розницу предлагающие в наше распоряжение услуги следующего характера:


1. Загpузка или подключение к клиенту для его отладки;
Первое, что должен сделать отладчик, это функцией CreateProcess() создать отлаживаемый процесс. Причём в аргументе этой функции нужно обязательно указать флаг DEBUG_PROCESS, чтобы сообщить системе, что родительский процесс планирует войти в "цикл-отладки" для управления дочерним процессом. Все современные отладчики могут подключаться и к уже запущенным процессам из диспетчера-задач. В этом случае отладчик зовёт из либы Kernel32.dll функцию DebugActiveProcess().

Поскольку отладчик подключается к клиенту посредством CreateProcess(), то оба они будут исполняться в разных адресных пространствах. То-есть будем иметь два самостоятельных процесса, связанных невидимой нитью отладки. Благодаря этому, устойчивость Win к процессам дебага весьма высока, и не зависит от поведения клиента. Даже если отлаживаемый процесс начнёт беспорядочно записывать мусор в память, эта запись всё-равно не сможет привести к сбою отладчика, т.к. в данном случае процесс стреляет в ногу себе, а не родителю.


2. Отладчик принимает от клиента уведомления об отладочных событиях;
После того-как клиент принят на борт, отладчик запоминает байт с текущего его указателя EIP и меняет этот байт на точку-останова INT-3 (опкод 0xCC). Теперь отладчик входит в свой цикл, путём вызова функции WaitForDebugEvent() для приёма отладочных уведомлений от клиента. С этого момента, через системный "порт-отладки" клиент периодически будет посылать отладчику сообщения, о происходящих в его тушке событиях Event. Вот прототип функции ожидания:

C-подобный:
BOOL WaitForDebugEvent
     lpDebugEvent       ;// адpес стpуктуpы DEBUG_EVENT
     dwMilliseconds     ;// сколько ждать события (-1 = вечно, INFINITE)

Тут мы указываем только время ожидания (обычно -1), а стpуктуpу DEBUG_EVENT по требованию клиента заполнит система. Она сбрасывает в неё технические детали отладочного события:

C-подобный:
struct DEBUG_EVENT
  dwDebugEventCode  dd 0          ;// код события
  dwProcessId       dd 0          ;// Pid,
  dwThreadId        dd 0          ;//  ..и Tid клиента.
  u                 DEBUGSTRUCT   ;// расширенные сведения о событии.
ends
;//------------------
DebugEventCode:
   CREATE_PROCESS_DEBUG_EVENT  ;// клиент создаёт пpоцесс.
   EXIT_PROCESS_DEBUG_EVENT    ;// соответственно закрывает его.
   CREATE_THEAD_DEBUG_EVENT    ;// аналогично для потоков..
   EXIT_THREAD_DEBUG_EVENT     ;// ^^^
   LOAD_DLL_DEBUG_EVENT        ;//   ..и внешних модулей.
   UNLOAD_DLL_DEBUG_EVENT      ;//       ^^^
   EXCEPTION_DEBUG_EVENT       ;// ВАЖНО!!! клиент сгенерил исключение Step/BreakPoint/etc..
   OUTPUT_DEBUG_STRING_EVENT   ;// клиент хочет послать строку.
   RIP_EVENT                   ;// системная ошибка с посмертной надписью!

3. Изменить принадлежащую клиенту память;
На данном этапе клиент заморожен и у отладчика появляется возможность вывести на экран любые сведения о нём, типа состояние регистров через GetThreadContext(), дизассемблерный листинг кода и прочую информацию. Клиент будет спать до тех пор, пока отладчик не снимет ранее установленный бряк, сдвинув его на следующую инструкцию. Здесь-то код нашего приложения (аля клиент) и должен будет взять бразды правления в свои руки, самостоятельно восстановив исходный байт, вместо бряка INT-3. В результате, процессор не обнаружив никаких точек-останова начнёт выполнять все последующие инструкции, пока не встретит терминальную функцию ExitProcess(). При иных (благоприятных для отладчика) обстоятельствах он продолжит свой алгоритм.

4. Даёт возможность клиенту сделать очередной шаг;
Завершив обработку некоторого события, отладчик вызывает ContinueDebugEvent(), тем-самым нарушив крепкий сон клиента, и цикл повторится вновь.

Нужно отметить, что перечисленные функции отладочного API могут быть вызваны только тем потоком, который при помощи CreateProcess() создавал процесс с флагом отладки DEBUG. Эта информация хранится в дескрипторе отладчика, куда её заносит системный протекторат. Таким образом, скелет типичного дебаггера может выглядеть примерно так:


C-подобный:
;// Выбираем процесс для отладки
start:  nop
        invoke  CreateProcess,...,DEBUG_PROCESS,...

;// Цикл отладки! Ставим время на макс и ждём сообщения..
;// Если пришла sms'ка "конец программы", то выходим из цикла.
@trace: nop
        invoke  WaitForDebugEvent,..-1,..
        cmp     DebugEventCode,[EXIT_PROCESS_DEBUG_EVENT]
        je      @StopTrace

;//---- здесь обрабатываем событие отладки -------------
        invoke  GetThreadContext,..
       cinvoke  printf,...

;// Даём добро клиенту на продолжение, и уходим на начало цикла
        invoke  ContinueDebugEvent,...
        jmp     @trace

@StopTrace:
        nop


2. Основная идея

В предыдущих 4-х пунктах была рассмотрена последовательность действий отладчика, когда он уже подцепил к себе клиента. Но в контексте данной темы нас будет больше интересовать то, что происходит за кулисами апартаментов отладчика (на подготовительном этапе загрузки), когда система только планирует его контекст.

Сначала процесс клиента загружается системой в выделенную ему память. Отладчик отслеживает этот момент и взяв из PE-заголовка клиента точку-входа в него (EntryPoint), устанавливает по этому адресу свою/программную точку-остановка INT-3. После этого, отладчик опять возвращает руль загрузчику образов Win, который посредством функции KiUserApcDispatcher() создает первичный поток клиента, и начинает подгружать в его память статически прилинкованные к нему библиотеки DLL. Эти шаги регистрируются в логе отладчика x64Dbg, а вот Оля здесь филонит, пытаясь скрыть от нас реальное положение дел:


x32Dbg_BP.png


Здесь виндо, что отладчики устанавливают первый свой бряк до загрузки всех библиотек в память, а значит из процедуры инициализации этих библиотек DllEntryPoint() у нас появляется возможность отправить данный Breakpoint к праотцам, вернув оригинальный байт программы на своё почётное место. В этом случае, после того-как отработает лоадер системных DLL, отладчик уже не остановится на точке-входа в отлаживаемую программу, и в обычном режиме со-скоростью реактивного гепарда, процессор пойдёт топтать весь программный наш код, от его начала и до самого конца (если по-ходу не встретит диалоговых окон или процедур взаимодействия с пользователем). Отладчику остаётся лишь с тоскою наблюдать за мимо пролетающими инструкциями.

Выходит чтобы осуществить наш план, достаточно прицепить к своему EXE какую-нибудь DLL (на рис.выше это myDll.dll), на входе в которую сбросить к чертям системную точку-останова Breakpoint at 00402000 set!, чтобы оригинальные байтики программы смотрелись рядом гармонично, без всяких бряков и прочего мусора. Дело в том, что на этапе загрузки образа в память, системный загрузчик даёт команду "рассчитаться на первый/второй" всем библиотекам, которые перечислены в секции-импорта приложения, и каждая из них должна отрапортовать.

Посмотрим на прототип функции DllEntryPoint(), которая по-факту является точкой-входа в любую библиотеку. На выходе из этой функции, мы обязательно должны возвратить системному лоадеру в регистре EAX значение true=1 если инициализация прошла успешно, или-же false=0 если что-то пошло не так (зависит от нашего настроения). В последнем случае, система выстрелит в юзера модальным окном следующего содержания (см.сишный хидер ntstatus.h), после чего тупо прихлопнет приложение. Например можно внутри DllEntryPoint() искать отладчик и обнаружив таковой, возвратить загрузчику EAX=0:


С142.png


C-подобный:
DllEntryPoint()
  hinstDLL       ;// VA-база DLL в памяти
  fdwReason      ;// одна из 4-х причин вызова точки-входа в DLL
  lpvReserved    ;// статическое(1) или динамическое(0) подключение
;//---------------------
fdwReason:
  DLL_PROCESS_ATTACH = 1 ;// DLL подключается к процессу при его запуске
  DLL_PROCESS_DETACH = 0 ;// DLL удаляется из пространства процесса
  DLL_THREAD_ATTACH  = 2 ;// Процесс создаёт новый поток (на основной не распространяется)
  DLL_THREAD_DETACH  = 3 ;// Поток отработал своё и завершается

Значит имеем три аргумента функции и все их через стек передаёт в нашу библиотеку системный загрузчик образов. Обратите внимание на причину вызова DLL_PROCESS_ATTACH – этот флаг наша либа может получить только в трёх случаях: (1) в момент запуска EXE на исполнение и инициализации DLL, (2) первого обращения к экспортируемым ею функциям, (3) в результате динамического вызова библиотеки посредством LoadLibrary(). Если происходит повторный запуск какой-нибудь функции из DLL, данный флаг не выставляется, т.к. загрузчик берёт инфу уже из информационной структуры РЕВ. Аргумент "lpReserved" указывает на способ загрузки библиотеки – статический (вместе с приложением) или динамический (в произвольный момент, по требованию). Остальные флаги сейчас нам абсолютно не интересны.


3. Практика. Сброс программной точки-останова INT-3

Собрав во-едино всё выше/сказанное можно сделать вывод, что отладчик вмешивается в наш код и вставляет прерывание INT-3 везде, где ему нужно остановиться. При этом оригинальный байт он запоминает, а на его место записывает опкод данного прерывания = 0хСС. При первой загрузки отлаживаемого образа в память, отладчик перезаписывает байт, который в аккурат попадает на "EntryPoint" в программу (см.рис.1). Поскольку приложение пишем мы сами, то можно предварительно вставить туда заранее известный нам опкод.. например 90h соответствующий инструкции NOP (No-Operation, ничего не делать). Мы подсунем эту "утку" отладчику, а позже сами восстановим его.

Значит алго нашей библиотеки DLL должен быть примерно таким:


1. Внутри точки-входа в DLL проверить флаг на ATTACH и если гуд, то приступить к поиску адреса "EntryPoint" в приложение. Для этого, сначала находим базу образа памяти, по смещению 3Ch от которой будет лежать указатель на РЕ-заголовок. Далее, сместившись на РЕ+28h получаем точку-входа в программу.
2. У любого приложения секция-кода всегда должна быть защищена от записи (привет самомодификация), иначе аверы могут поднять шум. Поскольку нам нужно будет перезаписывать байт у точки-входа в код, значит зовём функцию VirtualProtect() и всю защиту от записи как турбиной сдувает. Теперь имеем доступ на R/W, хотя в мерах предосторожности разумно будет по окончании махинаций обратно снять атрибут Write.
3. Пользуясь моментом, быстро восстанавливаем свой NOP, отправляя в топку Breakpoint отладчика. На заключительном этапе, рапортуем системному загрузчику образов EAX=1 давая ему знать, что "в Багдаде всё спокойно" и приложение можно смело стартовать. Вот пример такой библиотеки:

C-подобный:
;//------------- FASM-code ----------------------
;// Библиотека DLL,
;// с фиксированной базой 10000000h (без релоков)
;//----------------------------------------------
format   pe console dll at 0x10000000
include 'win32ax.inc'
entry    DllEntryPoint     ;//<--- определяем точку-входа
;//---------
.data
old    dd  0
buff   db  0

;//---------
.code
proc   DllEntryPoint, hInstance, fdReason, LpReserved
       cmp     [fdReason],DLL_PROCESS_ATTACH   ;// проверка флага на ATTACH
       jne     @f                              ;// пропустить если другой..

;//--- Иначе: получить полное имя EXE-файла
;//--- и найти его базу в памяти (он-же дескриптор Handle)
       pusha
       invoke  GetModuleFileName,0,buff,128
       invoke  GetModuleHandle,buff
       xchg    esi,eax          ;// ESI = база

;//--- Вычисляем точку-входа в EXE
       mov     ebx,[esi+0x3C]   ;//
       add     ebx,esi          ;// указатель на РЕ-заголовок
       mov     ebx,[ebx+0x28]   ;//
       add     ebx,esi          ;// EBX = точка-входа в EXE

;//--- Открываем секции-кода доступ на запись!
;//--- EBX = начало страницы памяти, 4096 её размер,
;//--- PAGE_WRITE = новый атрибут, в "old" сбросится старый атрибут.
       push    ebx
       invoke  VirtualProtect,ebx,4096,PAGE_EXECUTE_READWRITE,old
       pop     ebx

;//--- Внимание!!!
;//--- Перезаписываем "Breakpoint" отладчика,
;//--- восстанавливая свой NOP на место (опкод 90h)
       mov     byte[ebx],0x90

;//--- Дело сделано.
;//--- Восстанавливаем прежние атрибуты секции-кода
       invoke  VirtualProtect,ebx,4096,[old],old
       popa

@@:    mov     eax,1   ;// TRUE = OK (системному лоадеру)
       ret
endp
;//<<---<<---- КОНЕЦ ТОЧКИ-ВХОДА В БИБЛИОТЕКУ --->>--->>----//

;//------------------------
;// DLL должна экспортировать мин одну функцию,
;// иначе она не загрузится в память процесса.
;//------------------------
proc   About
       pusha
      cinvoke  printf,\
               <10,' *bpCrackme* ver.0.1',\
                10,' ------------------------',\
                10,' Full patch....: %s',10,0>,buff
       popa
       ret
endp
;//---------
section '.edata' export data readable
export   'myDll.dll',About,'About'
;//---------
section '.idata' import data readable writeable
library  msvcrt,'msvcrt.dll',kernel32,'kernel32.dll'
import   msvcrt, printf,'printf'
include 'api\kernel32.inc'

Обязательным условием является здесь то, что эту библиотеку нужно сохранить только под именем myDll.dll, поскольку в её секции-экспорта '.edata' указано именно это имя. Ничто не мешает вам задать любое/другое, лишь-бы они совпадали.

Теперь осталось написать саму программу, которая будет статически подгружать в своё пространство эту либу. Чтобы от кода была хоть какая-то практическая польза продемонстрируем, как можно вытащить базы всех загружаемых системных DLL не обращаясь к Win32-API (это было-бы слишком просто), а прямо из структуры окружения "Process-Environment-Block", или просто РЕВ.

Если в двух словах, то на 32-битных системах в сегментном регистре FS лежит указатель на структуру ТЕВ (на х64 это GS), а сместившись внутри неё на 30h найдём адрес структуры РЕВ. В этом клондайке зарыто много интересного, в том числе и адрес ещё одной, не менее интересной структуры под названием "PEB_LDR_DATA". Как видно по корню LDR, информация принадлежит как-раз лоадеру системных библиотек.

Формат структуры такой, что в ней хранятся целых три вложенные структуры, но нам нужен только порядок очерёдности загрузки dll'ок именно в память. Поэтому берём указатель по смещению 14h внутри PEB_LDR_DATA, и натыкаемся на таблицу "InMemoryOrderModuleList". Каждая запись в этой таблице имеет размер 10h, поэтому перемещаясь с таким шагом, получаем полный паспорт системных модулей.

Вот пример EXE-файла, который тянет за собой нашу либу (она делает всю/черновую работу), а что-бы не бездействовать – сам выводит на консоль базовые адреса постоянно присутствующих в каждом файле библиотек:


C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//--------------
.data
buff   db  0
;//--------------
.code
start: nop    ;//<----- байт, куда отладчик поспешит поставить свой "Breakpoint",
       nop                     ;// а наша DLL его по-полной обломает.

       invoke  About           ;// зовём функцию из своей DLL,
                               ;// иначе она не будет загружена в память.

;//--- Поиск и перечисление все баз из структуры РЕВ
;//-------------------------------------------------
       mov     esi,[ fs:30h]   ;// ESI = указатель на PEB
       mov     esi,[esi+0Ch]   ;//<-------- структура PEB_LDR_DATA
       mov     esi,[esi+14h]   ;//<-------- InMemoryOrderModuleList

       mov     eax,[esi+10h]   ;// EAX = своя база

       mov     esi,[esi]       ;// связной список LIST_ENTRY
       mov     ebx,[esi+10h]   ;// EBX = база Ntdll.dll

       mov     esi,[esi]       ;//
       mov     ecx,[esi+10h]   ;// ECX = база Kernel32.dll

       mov     esi,[esi]       ;//
       mov     edx,[esi+10h]   ;// EDX = база KernelBase.dll (Win7+)

       mov     esi,[esi]       ;//
       mov     ebp,[esi+10h]   ;// EBP = база msvcrt.dll

      cinvoke  printf,<10,' ProcBase......: %08X',\
                       10,' Ntdll.dll.....: %08X',\
                       10,' Kernel32.dll..: %08X',\
                       10,' KernelBase.dll: %08X',\
                       10,' Msvcrt.dll....: %08X',0>,eax,ebx,ecx,edx,ebp

       cinvoke  gets,buff
       cinvoke  exit,0
;//--------------------
section '.idata' import data readable writeable
library  msvcrt,'msvcrt.dll',myDll,'myDll.dll'
import   msvcrt, printf,'printf',gets,'gets',exit,'exit'
import   myDll,  About, 'About'   ;//<---------- импорт fn.из своей DLL!!!

Если всё сделано правильно, то жмякнув в окне компилятора fasm клавишу F9, на выходе получаем исправно функционирующий файл. Однако вскормив его отладчику обнаруживаем, что отладчик не только не успел дизассемблировать ни одного тушканчика кода и вывести его листинг в левое окно, у него даже не оказалось времени сформировать стек.

На рис.ниже результат где видно, что код в принципе отработал до конца, однако это вовсе не заслуга отладчика, а скажем мягко – наоборот. Оля так вообще сразу капитулировала, честно отобразив буквально во-всех своих окнах молоко. Как отлаживать такой файл?


Result.png



4. Заключение

Ясно, что этот код не претендует ни на что, т.к. вся его подноготная лежит на поверхности и с нею можно ознакомиться не только в отладчике, но и в любом дизассемблере. Но дизассму тоже можно противостоять (как-правило шифрованием тушки) и это совсем другая история. Целью данного повествования было продемонстрировать именно беспомощность всех современных инструментов отладки к подобного рода сносу их бряков. Если и покрутить в настройках свойствами Breakpoint'ов, это всё-равно не даст желаемых результатов, поскольку неопытный реверсер попадёт в непроходимые джунгли непонятного кода и поспешит скорее на выход. А от бывалых взломщиков и защищаться бесполезно – вскроют одним пальцем.

Как обычно, в скрепке цепляю готовые исполняемые файлы, чтобы у заинтересованных была возможность опробовать технику на практике. Всем удачи, и до скорого!!!
 

Вложения

  • bpCrackme.zip
    1,1 КБ · Просмотры: 434
Последнее редактирование:

pp11

Green Team
16.09.2018
201
82
BIT
1
Очень интересная статья. Расскажите возможно ли как-то избавиться от DLL, в которой хранится техника анти-отладки?
Всегда лучше таскать с собой один exe, чем exe+dll.
 
  • Нравится
Реакции: Marylin

ROP

Red Team
27.08.2019
327
663
BIT
149
Спасибо за эту отличную статью Marylin. (y)
Читать было интересно. Объясняете подробно и не научно + пояснение кода.
До прочтения вашей статьи не задумывался о таком возможном "подарке" от программы для отладчика.

Авансом скажу, что это простой, элегантный и чертовски полезный баг, затрагивающий буквально все заточенные под Win32/64 отладчики.

Надеюсь, я правильно понимаю, что в linux такая история не сработает. Без функций windows'a ведь пишутся отладчики. Если будет свободное время, нужно будет загуглить, как в linux системах с этим дела обстоят.
 
  • Нравится
Реакции: Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
324
1 441
BIT
649
я правильно понимаю, что в linux такая история не сработает. Без функций windows'a ведь пишутся отладчики.
Про линух не знаю,
у него свои рычаги и если понять суть, думаю реализовать подобное можно.

При желание, под виндой можно обойтись вообще без WinAPI, если при компиляции исходника сразу открыть секцию-кода на запись (установить ей флаг Writeable). Например базу и точку-входа в программу взять из РЕВ, а остальные все операции там регистрами.
 
  • Нравится
Реакции: ROP и Mikl___

Marylin

Mod.Assembler
Red Team
05.06.2019
324
1 441
BIT
649
Всегда лучше таскать с собой один exe, чем exe+dll.
вот вариант без DLL, а сбивает бряк отладчика вшитый в exe TLS-callback.
код запрашивает у юзера пасс, который хранится в секции-кода (вместе со-строками). Поскольку код открыт на запись, то в идеале нужно зашифровать строки, и заставить Callback их расшифровывать. Тогда и в дизассме они потеряются.

C-подобный:
;//------------------------
;// TLS-callback.
;// уход из под отладчика.
;//------------------------
format   pe console
include 'win32ax.inc'
entry    start
;//---------
.data
tls     dd  0          ;//...\ Секция TLS
chain   dd  ClearBp,0  ;//.../    ^^^^^^^

frmt    db  '%s',0
buff    db  0
    
;//----- Открываем секцию-кода на запись --vvvv---
section '.code' code executable readable writeable
start:  dd      0x90909090  ;//<------ вагон NOP'ов для отладчика
        jmp     @f

@crypt:    ;//<---------- Начало блока-шифрования ------------
capt    db      ' *bpCrackme* ver.0.2'
        db      10,' -------------------'
        db      10,' Pass: ',0
ok      db      ' OK!',0
wrong   db      ' WRONG! WRONG! WRONG! WRONG!',0
pass    db      'Breakpoint',0    ;// пароль
len     =       ($ - pass)-1      ;// его длина
@endCrypt: ;//<---------- Конец блока-шифрования -------------

;//--- Фейс-контроль ----------------
@@:    cinvoke  printf,capt       ;// запрос пароля
       cinvoke  scanf,frmt,buff   ;//  ..его ввод в буфер.

        mov     esi,buff       ;// юзерский пасс
        mov     edi,pass       ;// оригинал
        mov     ecx,len        ;// длина оригинала
        repe    cmpsb          ;// проверить по длине ECX

        mov     ebx,ok         ;// в дефолте выводим "OK!"
        jcxz    @prn           ;// пасс по всей длине совпал?
        mov     ebx,wrong      ;// иначе: меняем мессагу
@prn:  cinvoke  printf,ebx     ;//

       cinvoke  scanf,frmt,buff
       cinvoke  exit,0

;//****** TLS-callback ****************
proc   ClearBp                ;//
       mov      edi,start     ;// указатель на "EntryPoint"
       mov      al,0x90       ;// опкод инструкции NOP
       stosb                  ;// записать в EDI
       ret                    ;//
endp                          ;//
;//***********************************
;//******* Данные TLS-секции *********
data   9
       dd  tls,tls,tls,chain
end    data
;//***********************************
;//---------
section '.idata' import data readable
library  msvcrt,'msvcrt.dll'
import   msvcrt, printf,'printf',scanf,'scanf',exit,'exit'

Кстати, чтобы заставить работать коллбеки на 64-битных системах, нужно снимать флаг ASLR.
Подробности .
 

Вложения

  • TLS_Breakpoint.zip
    1,1 КБ · Просмотры: 369
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!