Про DEBUG и anti-DEBUG.

Тема в разделе "FASM", создана пользователем Коцит, 26 май 2017.

  1. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    Когда-то собирал материал по отладчикам реального режима в надежде понять 'как это работает', и остался черновик, который я чуть-причесал и решил выложить сюда. Уточню, что всё ниже-изложенное представляет из-себя исключительно моё мнение по данному вопросу. Приходилось разбираться в тонкостях самому.., поэтому некоторые моменты могут быть ошибочны. Тему создал в разделе фасма, т.к. планирую приводить примеры именно на этом диалекте ассемблера. Сабж расчитан на юзеров первого звена, и только\начинающих познавать мир асма - любителей. Постараюсь без соли..
    -----------------------------

    ВВОДНАЯ ЧАСТЬ

    Первые отладчики появились более 30-ти лет назад. За это время поколение их разрослось, но прогресса в алгоритмах не наблюдается. Виной тому архитектура самого ЦП, где отладочные средства сведены к минимуму - это бит(TF) в регистре флагов, да-пара прерываний BIOS: 01h (останов при TF=1), и 03h (программный брэйк-поинт).

    Позже, у 80386+ появилось 8 специальных\отладочных регистров DR0-DR7, которые значительно расширили возможности отладки. Теперь, в добавок к программным.., можно ставить ещё и аппаратные брэйки, число которых ограничено четырмя. Нужно отдать должное разработчикам дебагеров, которые умудрились создать продукты находясь в условиях, с такими\жёсткими квотами.

    В отладчиках реального режима имеется столько дыр, что можно рассматривать их как одну\сплошную дыру, прикрытую крупной сеткой полезных алгоритмов. Они пытаются хоть как-то скрыть от исследуемой программы своё присутствие перехватывая прерывания, обращения к 'опасным' (на их взгляд) инструкциям, подсовывая левый флаг(TF) - но всё напрасно. Как оказалось, заштопать все лазейки не возможно, ..как и невозможно описать их в одной статье.
    --------------------------

    Эволюция разделила все отладчики на 4 вида: отладчики режимов реального\защищённого, и отладчики эмуляторы\унпакеры. В свою очередь отладчики защищённого режима делятся ещё на 2 (под)вида - прикладного уровня (OllyDBG), и уровня ядра (S-Ice\Syser).

    Отладчики реального режима:
    • SoftIce v.2+ (Nu-Mega DOS),
    • AVPUtil (by E.Kaspersky),
    • Code View (Microsoft),
    • Turbo Debugger (Borland),
    • AFDPro (by H.Puttkamer),
    • GRDB (LADsoft / text-mode),
    • DEBUG (Microsoft / text-mode)
    • GameTools, Periscope, WatcomDebugger, Quaid Analyzer, etc.
    Отладчики защищённого режима:
    • SoftIce v.4+ (Nu-Mega core/Win),
    • Syser (by LoyTheCjw core/Win),
    • OllyDbg (by Oleh Yuschuk)
    • DeGlucker (VAGSoft), etc.
    Эмулирующие отладчики:
    • Soft Debugger (SDB),
    • EDB (by Serge Pachkovsky),
    • SD-2 (by Dmitry Groshev), etc.
    EMU \ авто-распаковщики:
    • cup386 (Cyberware)
    • UNP (by B.Castricum)
    • Intruder (by Creat0r)
    • SnapShot (Dale Co.)
    • AutoHack (BCP group)
    • TRON, TSUP, etc.
    Как видим - список приличный, и у каждого свои преимущества и недостатки. Если один разработчик закрывает дыру(x), то в другом продукте она остаётся открыта. Поэтому свои защитные механизмы разумно тестить под разными отладчиками, или-же вставлять в код сразу несколько вариантов защиты. Авось какой-нить вариант да-сработает.
     
    hjujet, yashechka, TermoSINteZ и 4 другим нравится это.
  2. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    КАК РАБОТАЕТ ОТЛАДЧИК

    ..:: #1. Инстинкт самосохранения у отладчиков реального режима отсутствует напрочь. Первое, что они делают при запуске, это делят с клиентом стек. Вернее стек они не делят, а делают его общим, что приводит к гормональным сбоям с плачевными последствиями. Общий стек позволяет исследуемой программе захватить ресурсы самого отладчика, и ещё не ясно, кто-кого 'поведёт' по трассе.

    Например, если начать код с инструкции NEG_SP, то при дефолтном значении указатель(SP) = FFFEh, он обратится в 0002h, на что проц тут-же отреагирует исключением(#SS) - ошибка стека. Если это прерывание не перехватывается отладчиком, то он просто зависнет, или вылетит даже не успев попрощаться:
    Код (ASM):
    1. start:  neg  sp
    2.         nop
    3.         neg  sp
    4. ;...
    5.  
    На этом фоне выделяются такие дебагеры как: TD, AVPutil, CodeView, ..а вот разработчики AFD, Debug, GRDB оказались продумАноми с нетрадиционной ориентацией. Кто-то выделяет себе отдельную область для стека, а кто-то перехватывает стековые инструкции. Но то-что эти отладчики уверенно продолжают трассировку даже при сброшеным в зеро указателем(SP) - остаётся фактом.

    Список исключений реального режима:

    -----------------------------------
    INT 00h (#DE) - Ловушка деления на ноль.
    INT 01h (#DB) - Ловушка отладочного прерывания при TF=1.
    INT 02 - NMI-прерывание (не маскируемое).
    INT 03h (#BP) - Ловушка точки останова.
    INT 04h (#OF) - Ловушка переполнения регистра.
    INT 05h (#ВС) - Переполнение при BOUND (выход операнда за границы).
    INT 06h (#UD) - Недопустимая инструкция (ud2).
    INT 07h (#NM) - Нет сопра (вызов FPU, когда в CR0 бит(2)=1).
    INT 0Ch (#SS) - Исчерпание стека.
    INT 0Dh (#GP) - Общая ошибка защиты.


    ..:: #2. На сл.этапе, отладчик готовится к маскировке.
    Разработчики быстро поняли, что находиться безоружным в агрессивной среде, по меньшей мере глупо. Чтобы выжить, нужны механизмы отличные от тяжёлой артилерии, т.к. её применение противоречит самой логике отладки. Если при малейшем подозрении сразу-же прибивать клиента, кого тогда отлаживать? Необходимо позволить отлаживаемой программе вести себя вольготно, и в тоже-время как-то ограничивать её свободу.

    Компромисс был найден в виде затенения трассировочного флага(TF). Этот флаг не отображает в своём окне ни один отладчик, дабы не спровоцировать его сброс. Так-же нельзя получить его и прямой операцией чтения регистра(FLAGS), типа PUSHF\POP. Бит(TF) всегда в нём будет сброшен. Как получить реальное значение регистра флагов, будет рассказано ниже.

    ..:: #3. На заключительной стадии настройки среды, отладчики перехватывают прерывания: 1/3/21h (а некоторые ещё кучу), и взводят флаг(TF) в регистре FLAGS. С этого момента проц переходит в пошаговый режим выполнения инструкций, дёргая на каждом Step'e перехваченный INT-1h.

    ЦП не приступит к выполнению очередной инструкции, пока не дождётся от обработчика INT-1h команды IRET, после которой выполнит ещё одну.., которая так-же сгенерит INT-1h. Круг - замыкается. Внутри обработчика инта(1), дебагеру ни что не мешает спокойно 'рисовать' на экране состояние регистров\памяти, и по окончании - вставить (до IRET) функцию ожидания клавиши(F2\F8) для следующего стэпа.

    Алгоритм работы процессора с установленным флагом(TF) поддерживается на аппаратном уровне. Это просто вызов INT-1h, стандартный обработчик которого представляет из себя чуть-навороченную заглушку (типа: зашёл-вышел), поэтому в реальных условиях от первого прерывания прогам не-холодно-не-жарко.

    При вызове любого прерывания, ЦП сначала помещает в стек флаги, CS:IP (как адрес возврата), и если это трейс в отладчике без входа в INT\CALL, то дебагер сбрасывает ещё и флаг(TF), чтобы обработчик прерывания сделал свою работу на одном дыхании, без остановок и тормозов. При трассировке со-входом в INT, флаг(TF) остаётся взведённым, предоставляя нам возможность зайти внутрь обработчика, и ознакомиться с его содержимым.

    Посмотрим на таблицу векторов прерываний, и на стандартный код ловушки INT-01h:
    Код (Text):
    1. GRDB version 1.7 Copyright (c) LADsoft
    2. History enabled
    3.             |<-------------- Векторы прерываний---------------->|
    4. ->d  0:0    |   -0-           -1-           -2-           -3-   |
    5. 0000:0000   68 10 A7 00 - 8B 01 70 00 - 16 00 91 03 - 8B 01 70 00
    6. 0000:0010   8B 01 70 00 - B9 06 0C 02 - 40 07 0C 02 - FF 03 0C 02
    7. 0000:0020   46 07 0C 02 - 0A 04 0C 02 - 3A 00 91 03 - 54 00 91 03
    8. 0000:0030   6E 00 91 03 - 88 00 91 03 - A2 00 91 03 - FF 03 0C 02
    9. ->
    10. ->u  0070:018B
    11. 0070:018B 1E            PUSH    DS
    12. 0070:018C 50            PUSH    AX
    13. 0070:018D B84000        MOV     AX,0040
    14. 0070:0190 8ED8          MOV     DS,AX
    15. 0070:0192 F70614030024  TEST    WORD PTR [0314],2400
    16. 0070:0198 754F          JNZ     01E9
    17. ;.........
    18. 0070:01E9 58            POP     AX
    19. 0070:01EA 1F            POP     DS
    20. 0070:01EB CF            IRET
    21.  
    Здесь видно, что обработчики прерываний 1 и 3 лежат по одинаковым адресам 0070:018Bh, который я U'нассемблировал. Но на этот-же адрес указывает и вектор(4) по адресу(10h), который редко перехватывается отладчиками, и может послужить флагом его присутствия. Достаточно сравнить оффсеты векторов 1 и 4, как отладчик всплывёт наружу:
    Код (ASM):
    1. start:  push  ds 0         ;
    2.         pop   si           ; подготовка
    3.         mov   di,4         ;  ..к подмене вектора(1),
    4.         mov   cx,di        ;     ..на вектор(0)
    5.  
    6.         push  0  0         ; DS:SI = 0000:0000
    7.         pop   ds es        ; ES:DI = 0000:0004  (СХ=4 для REP)
    8.  
    9.         mov   ax,[4]       ; сравниваем адреса обработчиков
    10.         cmp   ax,[10h]     ;    ..int'1 и int'4
    11.         je    @okey        ; нет отладчика!
    12.         rep   movsb        ; иначе: ставим на int'1 вектор(0)
    13. @okey:  pop   ds           ; и пусть отладчик стэпит дальше..
    14.         nop                ;
    15.         ;.....
    16.  
    Если мы под отладчиком, то после этих манипуляций ЦП будет генерить на каждом шаге уже не INT'1, а исключение(#DB) предполагая, что очередная инструкция делит на нуль, вне зависимости от её опкода. TurboDebugger (и не только он) не перехватывает вектор(4), поэтому глотает эту наживку и давится. Хоть инт(4) и перехвачен, то всё-равно его обработчик будет лежать в другой области памяти, т.к. должен обрабатывать ситуацию своеобразно, а не как отладочное прерывание(1).

    После всех телодвижений отладчик готов принять клиента на борт, загрузив его в начало сегмента и настроив указатель(IP) согласно адресу из PSP. Дальнейшие его действия поверхностно описаны выше, хотя на самом-деле там всё довольно сложней.
     
    hjujet, rococo795 и Mikl___ нравится это.
  3. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    ОТЛАДОЧНЫЕ РЕГИСТРЫ ПРОЦЕССОРОВ 386+

    У процессоров до 80386 весь механизм отладки держался на единственном бите(TF) регистра FLAGS. Если он взведён, то проц входит в режим шаговой трассировки, генерируя на каждой инструкции INT-01h. Это прерывание перехватывает дебагер, показывая текущее состояние регистров, чего за-глаза хватало исследователям кода.

    Но с появлением процессора 80386 всё изменилось. Взору программистов предстали новые регистры, в том числе и 8 отладочных (DR7-DR0), 2 из которых (DR5-DR4) забрал ЦП для своих нужд. В отличии от предыдущих процессоров с 'Программными точками останова' (коих может быть сколько угодно), теперь ЦП поддерживает и 4 аппаратных брэйка.

    Программный Breakpoint представляет собой 1-байтный код CCh.
    Если поместить этот байт перед какой-нибудь инструкцией,
    то дойдя до неё ЦП сгенерит исключение(#BP), которое дёрнет INT-3.
    Отлаживаемой программе достаточно подсчитать свою контрольную сумму,
    чтобы выяснить, вставил-ли отладчик байт(ССh) как бряк,
    обнаружив тем-самым процесс отладки.

    При установке 'аппаратных точек', всё-что требуется от программиста - это лишь определить ситуацию, на которую должен отреагировать проц прерыванием(3). Всего существует 4 различных условия - отсюда и 4 точки:

    • прерывание при выполнении команды (Exec),
    • прерывание при модификации ячейки памяти (Write),
    • прерывание при модификации или чтении ячейки (Read/Write),
    • прерывание при обращении к портам ввода-вывода (I/O).
    Условия для них задаются через отладочные регистры (DR7-DR0), доступ к которым возможен только из реального режима работы ЦП (или нулевого кольца виндозы), и исключительно инструкцией (MOV). Никакие другие инструкции - не поддерживаются.

    Самым информативным является "Регистр управления отладкой" (DR7). Регистр(DR6) - это регистр статуса. Он заполняется процессором при каждом срабатывании брэк\поинта. Взведённые биты (DR6) информируют о том, какая именно точка сработала, и по какой причине.

    Четыре регистра (DR0-DR3) хранят физические адреса установленных точек останова. Регистры доступны для чтения и записи, что позволяет легко съэмулировать любой порт, устанавить бряк на адрес памяти, или на выполнение инструкции. Поистине прорыв!

    Посмотрим на битовую карту отладочных регистров(DR7-DR0):

    Код (Text):
    1. DR7: Управление отладкой.
    2. -------------------------
    3.  
    4.      биты(31–30): поле LEN для точки останова(3). Размер точки в байтах.
    5.                   00=1, 01=2, 10=0 (eхec), 11=4.
    6.  
    7.      биты(29–28): поле R/W для точки останова(3). Тип точки.
    8.                   00=EXEC, 01=R, 10=порт (если #DE в CR4=1), 11=R/W
    9.  
    10.      биты(27–26): поле LEN для точки(2)
    11.      биты(25–24): поле R/W для точки(2)
    12.      биты(23–22): поле LEN для точки(1)
    13.      биты(21–20): поле R/W для точки(1)
    14.      биты(19–18): поле LEN для точки(0)
    15.      биты(17–16): поле R/W для точки(0)
    16.  
    17.        бит(13)GD: запрет на обращение к отладочным регистрам (исключение #DB).
    18.  
    19.         бит(9)GE: глобальный BreakPoint
    20.         бит(8)LE: локальный BreakPoint
    21.  
    22.         бит(7)G3: точка(3) включена (глобальная)
    23.         бит(6)L3: точка(3) включена (локальная)
    24.         бит(5)G2: точка(2)
    25.         бит(4)L2: точка(2)
    26.         бит(3)G1: точка(1)
    27.         бит(2)L1: точка(1)
    28.         бит(1)G0: точка(0)
    29.         бит(0)L0: точка(0)
    30.  
    31. DR6: Регистр состояния отладки.
    32. -------------------------------
    33.        бит(14)BS: причина прерывания - флаг(ТF) из регистра FLAGS.
    34.        бит(13)BD: причина прерывания - сл.команда обращается к DR0-7.
    35.        
    36.         бит(3)B3: сработала точка(3)
    37.         бит(2)B2: сработала точка(2)
    38.         бит(1)B1: сработала точка(1)
    39.         бит(0)B0: сработала точка(0)
    40.  
    41. DR5–DR4: Используются самим ЦП и программисту не доступны
    42. ---------------------------------------------------------
    43. DR3–DR0: Регистры адреса.   | Физические адреса 4-х точек останова.
    44.     CR4: Новые возможности. | Бит(3)DE - запрет на аппартные прерывания к портам.
    45.  
    В реальном режиме, определения типа 'локальная\глобальная точка' смысла лишены. Глобальная введена для защищённого режима Win. Если мы под досом, то можно выставлять любой из битов LE/GE (или-же оба сразу), чтобы ЦП понял наши намерения обозначить бряк.

    Попробуем вручную выставить BreakPoint(3). Ставить будем на попытку чтения\записи слова-памяти по физ.адресу(200h). Нужно сказать, что обращение должно быть именно к слову, а не к двойному слову. Как говорится - размер имеет значение:

    Код (Text):
    1. 32-битный\отладочный регистр DR7:
    2. ---------------------------------
    3.  
    4. BP3  BP2  BP1  BP0            3 2  1 0  <-- точки останова (2-бита на точку)
    5.   |    |    |    |             | |  | |
    6. 0111 0000 0000 0000 0010 0001 1100 0000
    7. ^^^^                  |     | ^^
    8. | |                  |     |  +--- (G\L3) Включить точку(3) (в R-mode любой из битов).
    9. | |                  |     +------ (LE)   Локальный бряк.
    10. | |                  +------------ (GD)   Закрываем доступ к DR0-DR7.
    11. | |
    12. | +------ (R/W) Тип точки останова: 11 = Memory R/W.
    13. +-------- (LEN) Размер инструкции : 01 = 2 байта.
    14.  
    15. PS\\: адрес точки нужно указать в регистре(DR3).
    16. ;-----------------------------------------------------------
    17.    mov   eax,01110000000000000010000111000000b   ;0x700021C0
    18.    mov   ebx,200h
    19.    mov   dr7,eax         ; задаём условие
    20.    mov   dr3,ebx         ; указываем физ.адрес   ;0x00000200
    21.  
    Кстати, вопреки мнению, что регистр(DR7) является 'Регистром управления' может послужить тот факт, что под отладчиком, ЦП взводит бит(LE) регистра(DR7) автоматически, в независимости от того, установлен какой-нить бряк или нет. То есть он может играть роль 'Регистра статуса'. Это касается и самого 'Регистра статуса' DR6, который так-же выставляет под отладчиком свой бит(BS), паля взведённый флаг(TF) в регистре флагов.
     
    hjujet, rococo795 и Mikl___ нравится это.
  4. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    ОБЗОР ВОЗМОЖНОСТЕЙ ОТЛАДЧИКОВ

    Первые отладчики появились задолго до рождения процессора 80386 с отладочными регистрами DR0-DR7, поэтому физически не в силах отследить обращения к ним. На скамейку запасных отправляются: Debug, TurboDebugger, AFDPro, AVPUtil и вся их братия. Этому есть простое объяснение: все\они 16-битные, а отладочные регистры 32-бит.

    Но картина не такая-уж и мрачная. Разработчики, которые поддерживали свои продукты 'на плаву' (SoftICE, TurboDebugger, CodeView), в последующих версиях добавляли всё новый функционал, в результате чего работа с отладочными регистрами становится всё-же возможной, но и то с большой натяжкой. Алгоритмы реализованы криво.., видно что левой рукой, через правое ухо. Посмотрим, как подопытные отладчики дизассемблируют чтение отладочных регистров:
    Код (ASM):
    1. ;fasm-code
    2. ;-------------------
    3. org 100h
    4.     mov   eax,dr7
    5.     nop
    6.     mov   ebx,dr6
    7.     ret
    8.  
    Дизассемблеры IDA, HIEW и HDasm справляются с такой задачей нормально, но 16-битные дебагеры дымят не понимая, куда следует впихнуть первый байт, считая его лишней инструкцией:

    Код (Text):
    1.  = CodeView =
    2. мелкософт здесь рулит и отображает REG32:
    3. ------------------------------------------
    4. 0A2D:0100  0F21F8        MOV    EAX,DR7
    5. 0A2D:0103  90            NOP
    6. 0A2D:0104  0F21F3        MOV    EBX,DR6
    7. 0A2D:0107  C3            RET
    8.  
    9. = TurboDebugger =
    10. всё ОК, только состояние в REG16, а не 32:
    11. -------------------------------------------
    12. 00000000:  0F21F8        mov    eax,dr7
    13. 00000003:  90            nop
    14. 00000004:  0F21F3        mov    ebx,dr6
    15. 00000007:  C3            retn
    16.  
    17. = AVPUtil =
    18. дешифрует не правильно. состояние в REG16:
    19. -------------------------------------------
    20. 17F4:0100  0F21F8        MOV    EDI,DR0
    21. 17F4:0103  90            NOP
    22. 17F4:0104  0F21F3        MOV    ESI,DR3
    23. 17F4:0107  C3            RET
    24.  
    25. = AFDPro =
    26. в упор не видит 32-битные регистры:
    27. -------------------------------------------
    28. 1E09:0100  0F            DB     0F
    29. 1E09:0101  21F8          AND    AX,DI
    30. 1E09:0103  90            NOP
    31. 1E09:0104  0F            DB     0F
    32. 1E09:0105  21F3          AND    BX,SI
    33. 1E09:0107  C3            RET
    34.  
    Очевидно, что для экспериментов с отладочными регистрами понадобится инструмент с более современным ядром, который при виде 32-бит не впадал-бы в ступор, и имел мало-мальски 'прямые руки'. На эту роль подошёл-бы SoftICE, но он очень привередлив на платформах выше Win2000, поэтому я остановил свой выбор на консольном дизассемблере\отладчике GRDB, который можно скачать с сайта разработчика, или вместе с исходниками от сюда: http://exmortis.narod.ru
    Код (Text):
    1. GRDB version 1.7 Copyright (c) LADsoft
    2. History enabled
    3.  
    4. eax:00000000  ebx:00000000  ecx:00000000  edx:00000000  esi:00000000  edi:00000000
    5. ebp:00000000  esp:000BFFEE  eip:00000100  eflags:000B0202  NV UP EI PL NZ NA PO NC
    6. ds: 0E80      es:0E80       fs:0E80       gs:0E80       ss:0E80       cs:0E80
    7.  
    8. ->?o       ; опции --------------------
    9. WR - wide registers            enabled
    10. FR - flat real commands        disabled
    11. 32 - 32 bit disassembly        enabled
    12. ZR - divide by zero trap       enabled
    13. BK - ctrl-break trap           enabled
    14. NV - native video              enabled
    15. FI - flat real autoinit        enabled
    16. F0 - flat real 0 default       disabled
    17. SO - signed immediates         disabled
    18. HI - command history           enabled
    19. MD - msdos I/O                 disabled
    20. Logging disabled
    21.  
    Тулза конечно не шедевр, но главное удовлетворяет потребностям, которые в пределах этого топика весьма скромны. С помощью GRDB можно считать состояние любого из 32-битных регистров, изменить пару бит и запихнуть изменённый регистр обратно. Нужно сказать, что данный отладчик по отношению к своим братьям ещё и более 'честный'. Ничего не маскирует и показывает всё как-есть, не вводя исследователя в заблуждение. Приведу простой пример..

    Известный факт, что все отладчики взводят трассировочный бит(TF) в регистре флагов, что переводит ЦП в шаговый режим. Чтобы тестируемая программа не обнаружила процесс отладки, дебаггер маскирует этот факт, представляя в своём окне бит(TF) как сброшеный. Не помогает даже прямое чтение FLAGS:
    Код (Text):
    1. AX 3202  CX 00FF  DS:SI 17F4:0100  CS:IP 17F4:0102  BP 0000  ODITSZAPC |  Stack
    2. BX 0000  DX 17F4  ES:DI 17F4:FFFE  SS:SP 17F4:FFFE  FL 3202  001000000 |-08 17F4
    3. -----------------------------------------------------------------------|-06 0102
    4. 17F4:0100| 9C                   PUSHF                                  |-04 17F4
    5. 17F4:0101| 58                   POP     AX                             |-02 3202
    6. -----------------------------------------------------------------------|>00 0000
    7.  
    Здесь видно, что флаг(T) сброшен в нуль, а взведён только 'INT' разрешая нам пользоваться прерываниями. В регистре(AX) лежат флаги со-значением 3202h. Посмотрим, в каком состоянии находится процессор с такими флагами:
    Код (Text):
    1. Регистр FLAGS
    2. -----------------------
    3. биты 1.5.13.15 - резерв
    4.  
    5. 0011 0010 0000 0010  = 3202h
    6. | | |||| || |  | |
    7. | | |||| || |  | +--- бит(0)  = CF (Cary)         перенос
    8. | | |||| || |  +----- бит(2)  = PF (Parity)       чётность
    9. | | |||| || +-------- бит(4)  = AF (Auxiliary)    всп.перенос
    10. | | |||| |+---------- бит(6)  = ZF (Zero)         нуль
    11. | | |||| +----------- бит(7)  = SF (Sign)         знак
    12. | | |||+------------- бит(8)  = TF (Trap)         трассировка   <---<----//
    13. | | ||+-------------- бит(9)  = IF (Interrupt)    прерывания
    14. | | |+--------------- бит(10) = DF (Direction)    направление
    15. | | +---------------- бит(11) = OF (Overflow)     переполнение
    16. | +------------------ бит(12) = IOPL (2-бита)     I/O Privilege Level
    17. +-------------------- бит(14) = NT (Nested Task)  вложенность задачи
    18.  
    У-гу.. Бит(8) сброшенный, ..хотя мы знаем, что это не так.
    Нужно сказать, что все\подопытные отладчики ведут себя в данном случае одинаково. Один плюс ушёл в копилку разработчиков, которые хоть как-то попытались замаскировать своё присутствие. Но если вспомнить глюк всех отладчиков с тасованием регистра(SS), то оказывается что 'рано пить Боржоми'. Их маскировка напоминает страуса с зарытой головой, и вот почему..

    Под отладчиком, проц генерит INT-1 после каждой инструкции. Но при стековых манипуляциях с регистром(SS), INT-1 вызывается уже не после одной, а после 2-х инструкций, что позволяет нам вытащить реальные флаги контрабандным путём:
    Код (Text):
    1. AX 3302  CX 00FF  DS:SI 17F4:0100  CS:IP 17F4:0105  BP 0000  ODITSZAPC |  Stack
    2. BX 0000  DX 17F4  ES:DI 17F4:FFFE  SS:SP 17F4:FFFE  FL 3202  001000000 |-08 17F4
    3. -----------------------------------------------------------------------|-06 0105
    4. 17F4:0101| 16                   PUSH    SS                             |-04 17F4
    5. 17F4:0102| 17                   POP     SS                             |-02 3202
    6. 17F4:0103| 9C                   PUSHF                                  |>00 0000
    7. 17F4:0104| 58                   POP     AX                             |+02 20CD
    8.  
    Стоило только снять со-стека регистр(SS), как отладчик пропустил между ног следующую инструкцию(PUSHF). Теперь он не смог отследить обращение к флаговому регистру, и выдал в регистр(АХ) действительное его значение(3302h), хотя в окне и стеке продолжает красоваться 3202h.

    AH=33h - число не чётное (мл.бит=1), значит в АХ трассировный бит(TF) на самом деле взведён. Минусуем с копилки жуликов. Теперь наш код может легко обнаружить факт отладки, и уйти из-под неё. Эту особенность шагового режима называют "Потерей трассировочного прерывания".

    Процесс обработки прерываний отладчиками выглядит так..
    Сначала ЦП сохраняет в стеке регистр флагов, и CS:IP как адрес возврата в программу.
    Затем отладчик сбрасывает флаг(TF), что предотвращает по-шаговое выполнение самого обработчика прерывания. IRET (на выходе из обработчика) извлекает со-стека прежние состояния флагов и CS:IP, снова переводя ЦП в режим по-шаговой трассировки. Это означает, что бесполезно искать реальный флаг(TF) внутри обработчика прерывания, т.к. он будет там сброшен. Нужно поймать его снаружи подручными средствами.

    Нужно отдать должное разработчикам 'AFDPro', которые предусмотрели финт с регистром(SS). Он продолжает трассировку, в результате чего выдаёт подложный бит(TF). Зато 'GRDB' вообще его не контролирует. Имеется лишь вялая попытка маскировки в окне отладчика, но при обычном чтении FLAGS, GRDB сразу-же выдаёт чистое значение - 3302h. Глюк(SS) по-прежнему не обрабатывается. Видимо компания 'LADsoft' придерживается мнения: "Не хотите чтобы вас отлаживали? Да никто и не настаивает".

    Продолжение следует...
     
    rococo795 и Mikl___ нравится это.
  5. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    ПРАКТИЧЕСКАЯ ЧАСТЬ. ANTI-DEBUG

    Достоинства процесса отладки трудно переоценить, когда мы пытаемся выловить блох в коде своего защитного механизма. Но ключевое слово здесь "МЫ", т.к. это-же может проделать и злоумышленник, чтобы нейтрализовать нашу защиту. Вот тут-то и приходится бороться с отладчиками, дабы спрятать ключ от потайной двери. Рассмотрим несколько способов противодействия отладке..

    #0. Первое, что приходит на ум - это отправить клаву к праотцам.
    Достаточно просто отключить.., и сразу-же включить её обратно. Ни один из дебагеров не перенесёт такой изврат и наглухо зависнет на INC_AL. (правда эмулирующие отладчики могут заблокировать обращения к портам клавиатуры, но дебаг под ними скорей исключение, чем правило):
    Код (ASM):
    1. ; вкл/откл  клавиатуры
    2. bye_Keyb:
    3.     mov   cx,2
    4.     mov   al,0ADh      ; выключить
    5. @1: out   64h,al
    6.     inc   al           ; включить
    7.     dec   cx
    8.     jnz   @1
    9.  
    #1. На сл.месте знаменитый трюк с инверсией регистра(SP).
    Большинство отладчиков видят это действо в своём страшном сне. Любой дебагер (да и не только он) сильно привязан к стеку, поэтому установка (SP) ниже плинтуса воспринимается им как форс-мажорное обстоятельство, на которое каждый из отладчиков реагирует по-своему. Кто-то перехватывает эту инструкцию, а кто-то забивает на неё, полагаясь на совесть кодера.

    Только не понятно, почему разработчики не учитывали эту особенность? Если стек для тебя так критичен, почему-бы на старте не отхватить от него кусок по-больше: [SUB_SP,40h]. Тогда (при инверсии) в заначке останется как-минимум 64-байт, с которыми можно выехать из сложившийся ситуации на подсосе. Посмотрим, на стартовые значения(SP) тестируемых отладчиков:
    Код (ASM):
    1. ; инверсия указателя SP
    2. bye_Debug:
    3.      neg  sp    ; если SP был fffeh, то станет 0002h
    4.      nop        ; если SP был ffeeh, то станет 0012h
    5.      neg  sp
    6. ;....
    7. ;//-----------------------------------------------------
    8.    TD      :  SP = fffeh - не жилец
    9.    AVPutil :  SP = fffeh - вылетает со-свистом
    10.    CodeView:  SP = fffeh - в нокауте
    11.  
    12.    AFDPro  :  SP = fffeh - ОК! имеет свой стек.
    13.    GRDB    :  SP = ffeeh - ОК! перехватывает SP.
    14.  
    Такой-же эффект можно получить, если при выровненном стеке снять с него слово.
    При указателе (SP=FFFEh) он примет значение нуль:
    Код (ASM):
    1. start:            ; SP = FFFEh
    2.     pop  ax       ; SP = 0000h
    3. ;...
    4.  

    #2. На очередной позиции прочно обосновался финт с проверкой флага(TF).

    Этот вариант никогда не даёт осечек. Ясно, что отладчик начнёт тут мухлевать, подсовывая нам левый флаг, поэтому вспомним про ошибку с потерей трассировочной инструкции через регистр(SS). Способов реализации самих проверок можно насчитать сотни, и вот только некоторые из них:
    Код (ASM):
    1. ;// Вариант(#0). Типичная проверка регистра
    2. ;------------------------------------------
    3.        xor   ax,ax          ; АХ = 0
    4.        push  ss             ; насилуем регистр(SS)
    5.        pop   ss             ;
    6.        pushf                ; (!)отладчик пропустит эту инструкцию
    7.        pop   bx             ; реальный FLAGS в ВХ
    8.        test  bh,1           ; проверить бит(TF)
    9.        jz    @okey          ; если он сброшен... ----->----+
    10.        jmp   ax             ; иначе: INT-20h (на выход)    |
    11. @okey: nop                  ; <--------------<-------------+
    12. ;.....
    13.      
    14. ;// Вариант(#1). Манипулируем с TF прямо в стеке
    15. ;// Вместо push\pop_SS можно предварять pushf префиксами сегментов.
    16. ;// ES: = 26h, SS: = 36h, CS: = 2Eh, DS: = 3Eh
    17. ;-------------------------------------------------------
    18.        mov   bx,sp             ; BХ = дно стека
    19.        sub   bx,2              ;    ..(корректируем прицел)
    20.        db    26h               ; сбиваем трассировку сл.инструкции (префикс ES:)
    21.        pushf                   ; флаги в стек
    22.        and   word[bx],100h     ; оставляем в стеке только бит(TF)
    23.        jz    @okey             ; если он сброшен.. ---------->---------+
    24.        in    ax,40h            ; иначе: берём рандом в AX              |
    25.        shrd  [bx],ax,16        ; задвигаем его в стек вместо флагов,   |
    26.        ret                     ;    ..и уходим по рандому в космос.    |
    27. @okey: add   sp,2              ; выравниваем стек от PUSHF <-----<-----+
    28. ;.....
    29.  
    30. ;// Вариант(#2). Тест отладочного регистра(DR6)
    31. ;// всё-что от нас требуется, это взвести биты(10-9-8) в DR7,
    32. ;// ..а потом проверить бит(14) в DR6.
    33. ;// ЦП взводит его при активном флаге трассировки(TF)
    34. ;---------------------------------------------------------------
    35.        mov   eax,700h        ; заряжаем DR7 битами(10-9-8)
    36.        mov   dr7,eax         ;
    37.        db    90h             ; NOP
    38.        mov   eax,dr6         ; читаем регистр статуса(DR6)
    39.        db    90h             ;
    40.        and   ax,4000h        ; выделяем в нём трассировочный бит(14)
    41.        jz    @okey           ; OK! если сброшен ----------------+
    42.        div   al              ; иначе: ошибка деления на нуль!   |
    43. @okey: nop                   ; <----------------<---------------+
    44. ;.....
    45.  
    #3. Время выполнения инструкций так-же оказывает отладчикам медвежью услугу.
    При трассировке, после каждого шага в дело вступает обработчик int'1, который выполняется какое-то время. За этот промежуток времени таймер системы успеет несколько раз тикнуть. Стандартный интервал системных тиков составляет 18.2 раза в секунду, чего вполне хватает для вычисления разницы между исполнением соседних инструкций. Вот пример реализации подобной проверки:
    Код (ASM):
    1.        xor   cx,cx              ; СХ = 0 (счётчик для LOOP)
    2.        mov   es,cx              ; сегмент BIOS
    3.        mov   ax,[es:046Ch]      ; читаем системные тики
    4.        nop                      ;
    5.        cmp   ax,[es:046Ch]      ; время выполнения NOP
    6.        je    @okey              ; нет тормозов.. -------->------+
    7. @00:   push  ax                 ; иначе: переполняем стек       |
    8.        loop  @00                ;                               |
    9. @okey: nop                      ; <--------------<--------------+
    10. ;.....
    11.  
    В реальных условиях однобайтная инструкция NOP выполнится гораздо быстрее, чем таймер успеет тикнуть 1 раз, поэтому такая проверка под отладчиком имеет место быть. Необычный здесь и обработчик события 'иначе', который тупо пихает в стек 65535 слов, в результате чего все дебагеры предпочитают сделать себе хара-кири, чем решать этот вопрос на программном уровне.

    #4. Ну и почётное место на пьедестале занимает перехват векторов(1\3).

    Этим трюком мы забираем у отладчика все\его ресурсы, и он полностью теряет потенцию к дальнейшему трейсу. Получив таким образом управление, можно заставить дебагер трассировать самого себя, пере\направить CS:IP на системную область, и т.д. и т.п. Тут всё зависит от фантазии, ..которую я планирую включить в следующий пост..

    Продолжение следует..
     
    rococo795 и Mikl___ нравится это.
  6. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.087
    Товарищ, а gdb проверял?
     
  7. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    UbIvItS, к сожалению с GDB дело не имел.
     
  8. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.087
    В любом случае Спасибо за Интересную Инфу :)
     
    Коцит нравится это.
  9. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    Хотелось бы чего то актуального и современного, а не досы :scratch_one-s_head:
     
  10. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    Indy_, как-говорится - чем богаты..
     
    _edge нравится это.
  11. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    ..значит перехват прерываний отладчика.
    Как уже говорилось, весь алгоритм работы дебагеров зашит в перехваченых интах 1 и 3. Если первый - это стэп, то третий - это бряк, перехват которого редко оправдывает ожидания. Кодо\копатель может вообще не использовать точки останова, тогда наш обработчик(3) останется не у дел. Другое дело трейс-прерывание(1), без участия которого не обходится ни один стэп.

    Доступ к вектору(1) эмулируют все\известные отладчики, и это логично. Никто в здравом уме не позволит срубить сук, на котором сидит. Но что интересно, дебагеры отлавливают перехват инта(1) только стандартными методами, типа fn.(25h) сервиса DOS. Попробуем подменить его и посмотрим, что из этого выйдет:

    Код (ASM):
    1. start:  db    2 dup(90h)      ; два NOP'a на старте
    2.         mov   ax,2501h        ; забираем вектор(1) у отладчика
    3.         mov   dx,@byeDebug    ; ставим на него свой обработчик
    4.         int   21h             ;
    5.         jmp   begin           ; к началу полезного кода.
    6.  
    7. @byeDebug:                    ; новый обработчик(1)
    8.         xor   ax,ax           ;
    9.         nop                   ;
    10.         iret                  ; выход из обработчика!
    11.  
    12. begin:  nop                   ;
    13. ;....
    14.  
    Только 'TurboDebugger' аплодирует стоя такому приёму, и жертвует собой в пользу науки. Но все\остальные отладчики спокойно продолжают делать свою работу, полностью игнорируя событие, на которое мы так расчитывали. Если (после перехвата) заглянуть в таблицу векторов прерываний, то можно обнаружить, что ни один тушканчик там не пострадал, и что вектор(1) ничуть не изменился.

    Делаем вывод, что для перехвата INT-1h нужны альтернативные методы, которыми природа нас щедро одарила. Достаточно произвести запись в сегмент(0), плюс номер инта умноженый на 4, как вектор можно считать перехваченным. Формат векторов - Offset:Segment, т.е. сперва указывается смещение обработчика, а потом его сегмент. Так-как никто кроме отладчиков не пользуется прерыванием(1), то сохранять старый его вектор не обязательно. Если юзеру сильно приспичит, то пусть он ребутнёт машину, и BIOS создаст девственную таблицу векторов IVT.

    Примеры ниже демонстрируют варианты перехвата вектора(1) в обход сторожевых псов отладчика. Подменять весь вектор не обязательно. Вполне хватит сменить что-то одно: или сегмент, или смещение вектора. Отладчики 'AFDPro и AVPutil' органически не переваривают присутствия в коде сегментных регистров FS и GS, поэтому резонно задействовать именно эти регистры:

    Код (ASM):
    1. ;//  Вариант(#1). Прямая запись 'stack-2-vector'.
    2. ;//----------------------------------------------
    3. start:  pushd 0                ; в стеке 4 байта нулей
    4.         pop   fs               ; FS = 0000h (сегмент таблицы прерываний)
    5.         pop   word[fs:6]       ; забиваем нулями сегмент обработчика(1)
    6.         nop                    ;
    7. ;....
    8.  
    9. ;//  Вариант(#2). Перехват прерываний через регистры.
    10. ;//  Ставим на вектор(01h), вектор(19h).
    11. ;//  Обработчик INT-19h под чистым досом перезагружает машину
    12. ;------------------------------------------------------------
    13. start:  xor   ax,ax            ; АХ = 0
    14.         mov   gs,ax            ; сегмент таблицы векторов прерываний
    15.         lfs   si,[gs:19h*4]    ; FS:SI = адрес обработчика(19h)
    16.         mov   word[gs:4],si    ; копируем в инт(1) смещение(19h)
    17.         mov   word[gs:6],fs    ; копируем в инт(1) сегмент(19h)
    18.         nop
    19. ;[....]
    20.  
    21. ;// Вариант(#3). Вешаем на инт(1) свой обработчик 'EXIT'.
    22. ;//------------------------------------------------------
    23. start:  push  ds 0             ;
    24.         pop   ds               ;
    25.         mov   word[4],@exit    ; оффсет вектора(1) указывает на наш 'EXIT'
    26.         mov   word[6],cs       ; сегмент тоже нашей программы
    27.         pop   ds               ;
    28.         nop                    ;
    29.  
    30. ;[..........]
    31. ;...    здесь вся программа...
    32. ;[..........]
    33.  
    34. @exit:  mov   ax,4C00h         ; завершить программу
    35.         int   21h              ; выход в DOS !!!
    36.  
    Нужно сказать, что в большинстве случаях перехватывать вектор(1) даже не обязательно (пусть он останется таким, как 'мать-дзебуг' родила), а модифицировать уже сами инструкции внутри перехваченного отладчиком обработчика(1). В этом случае до трассировки дело вообще не дойдёт, и любопытный юзверь будет только недоумевать, почему отладчик крэшится. Посмотрим на код ниже, который расскажет больше:

    Код (ASM):
    1. start:  push  ds 0              ;
    2.         pop   ds                ; DS - сегмент IVT
    3.         lds   si,[4]            ; DS:SI = адрес обработчика(1)
    4.         mov   byte[si],0Fh      ; вставим первой инструкцией ошибку 'ud2'
    5.         pop   ds                ; дело сделали. вернёмся на-родину..
    6.         nop                     ;
    7. ;[....]
    8.  
    Здесь мы получаем в DS:SI сегментный адрес, где находится (в памяти дебагера) обработчик прерывания(01h). Именно на этот адрес отладчик передаёт управление на каждом стэпе, для вывода на экран содержимого регистров\памяти. Эта 'святыня' никак не охраняется, что позволяет нам вертеть дебагами как вздумается.

    Что делает инструкция: MOV_BYTE[SI],0Fh ???
    Она записывает в первый байт обработчика исключение #UD2, опкодом которой является байт(0Fh). Для наглядности я записал только 1 байт, хотя это не табу. Рядом по смыслу лежат следующие опкоды:

    Код (ASM):
    1.         mov   byte[si],00Fh     ; ud2   - недопустимый опкод
    2.         mov   byte[si],0CCh     ; int3  - точка останова (бряк)
    3.         mov   byte[si],0CFh     ; iret  - выход из прерывания
    4.         mov   byte[si],0F0h     ; lock  - захват шины
    5.         mov   byte[si],0F4h     ; halt  - останов процессора
    6.  
    Ничто не мешает нам вставить в начало обработчика хоть целую цепочку инструкций, но толку от этого нуль, т.к. дальше первой.. трейс в отладчике всё-равно не пройдёт, и наткнувшись на неё отладчик рухнет замертво, подняв в небеса гору пыли.

    Продолжение следует..
     
  12. _edge

    _edge Well-Known Member

    Публикаций:
    1
    Регистрация:
    29 окт 2004
    Сообщения:
    631
    Адрес:
    Russia
    Спасибо за статью :) А то, понимаешь, обмажутся своим дотнетом..

    Целая пачка антиотладочных приемов была в Night $pirit Universal Polymorphic Device, http://vxheaven.org/vx.php?id=es19

    Есть еще вариант - прямой перезаписью вектора int 21h (любое важное прерывание) таблицы прерываний, установить на него свой обработчик, который например что-то расшифровывает (и только; нет возврата управлению оригинальному обработчику), и наполнить такими наномитами свой код

    mov ax,0xbaba
    int 0x21

    mov ax,0xfacc
    int 0x21

    Еще вариант - в bios data area, туда, куда обработчик int 08 пишет обновляемое каждый тик таймера значение, "сохронить" что-то важное, скажем промежуточное значение какого-то вычисления. Перед этим можно (хотя учитывая что таймер медленный, можно и без запрета прерываний обойтись) запретить прерывания, после завершения работы с этой "переменной" - разрешить.

    Распаковка в видеопамять - еще со времен Спектрума антиотладочный трюк.

    В реальном режиме таблицу прерываний можно сдвинуть через LIDT/SIDT 386+, а исконную затереть своими данными; интересно как отладчики, расчитывающие на расположение таблицы интов на 0:0, к этому отнесутся?

    В ветхозаветном Antidebugging Tricks by Inbar Raz приводился алгоритм Running Line от Сергея Пачковского, пример мощной антиотладки.
     
    Коцит нравится это.
  13. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    _edge, помню ветхозавет.. Прикольный материал был.
    А насчёт LIDT/SIDT - надо попробовать. Хорошая идея!
     
  14. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    ИГРЫ СО СТЕКОМ.

    Стек - это золотое дно, и если его пошурудить, то наружу всплывёт много интересного. Уделим немного внимания механизму его работы и посмотрим, что из этого можно будет выгадать.

    Вполне реально создать программу без использования стековой памяти. Например, запоминать промежуточные данные не в стеке, а в регистрах\переменных, читать клаву с портов, и выводить данные сразу в видеобуфер. Но если мы задействуем в программе хоть одно прерывание, то ЦП без нашего согласия поместит в стек как-минимум регистр флагов и адрес возврата (CS:IP). Если при этом в стеке не будет свободного места (или он будет не определён) - крах приложения неизбежен!

    Стек растёт снизу-вверх и определяется состоянием регистровой пары SS:SP. Если регистр(SS) указывает на сегмент стека, то регистр(SP) является указателем на его дно. Кстати, в разной литературе его представляют по-разному: одни называют SP указателем на макушку, а другие - указателем на дно. В принципе оба определения одинаковы, просто с какой стороны рассматривать рост адресов: если снизу-вверх, то SP - макушка; если сверху-вниз, то SP - указывает на дно. Так-как отладчики отображают рост адресов сверху-вниз, то мы будем считать SP указателем на дно стека:
    Код (Text):
    1.  
    2.   CS:IP = 17F6:0100    BP = 0000
    3.   SS:SP = 17F6:FFFE    FL = 7202
    4.   -------------------------------------------------------------
    5.   CS:0100 |  43 4F 4D 4D  4F 4E 50 52  4F 47 52 41  4D 46 49 4C <<- Полезный
    6.   CS:0110 |  45 53 3D 43  3A 5C 50 52  4F 47 52 41  7E 31 5C 43       код.
    7.   CS:0120 |  4F 4D 4D 4F  4E 7E 31 00  43 4F 4D 50  55 54 45 52
    8.   CS:0130 |  4E 41 4D 45  3D 53 41 4D  4C 41 42 00  44 45 56 4D
    9. ;.~.~.~.~.~.~
    10.   SS:FFC0 |  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00 <<- Стековая
    11.   SS:FFD0 |  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00      память.
    12.   SS:FFE0 |  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    13.   SS:FFF0 |  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    14.                                                           |
    15.                              SP = FFFEh (дно стека) ------+
    16.  
    Указатель(SP) можно перемещать резервируя\освобождая таким образом стековую память. Равнодушен проц и к регистру(SS), который так-же можно выставлять на любую область памяти. В глазах 'политики безопасности' (которая в дос отсутствует) это конечно рискованный трюк, хотя отладчики почему-то так не считают, ратуя нам полную свободу действий. Посмотрим на схему ниже:

    Код (Text):
    1.  
    2.   ;//---  PUSH  1234h
    3.   ----------------------------------------------------------------
    4.   SS:FFE0 |  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    5.   SS:FFF0 |  00 00 00 00  00 00 00 00  00 00 00 00  34 12 00 00
    6.                                                     |
    7.                                         SP=FFFСh ---+
    8.   ;//---  POP   AX
    9.   ----------------------------------------------------------------
    10.   SS:FFE0 |  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    11.   SS:FFF0 |  00 00 00 00  00 00 00 00  00 00 00 00  34 12 00 00
    12.                                                           |
    13.                                               SP=FFFEh ---+
    14.  
    Инструкция PUSH кидает на дно стека значение 1234h (по-соглашению Intel в обратном порядке), и сдвигает указатель на 2 влево. Но обратим внимание на действие команды POP. Она вынимает значение из стека, и просто сдвигает указатель(SP) вправо, оставляя сами данные болтаться там мусором. Следующий PUSH затерёт их..

    Аналогичная картина наблюдается и при вызове прерываний INT лишь с тем отличием, что в стек помещается уже 6-байтная конструкция вида: флаги\CS\IP, а указатель(SP) смещается на 6 влево. После этого, CS:IP переустанавливается новыми значениями из таблицы векторов IVT, и проц приступает к выполнению обработчика прерывания. IRET (которым заканчиваются все обработчики) опять растасовывает по своим регистрам прежние значения из стека, и выравнивает его, сдвинув SP на 6 вправо. Напомню, что данные при этом остаются на том-же месте, а значит мы можем использовать их повторно. Не плохая идея..

    Код (Text):
    1.  
    2.   ;//---  INT 21h (стек при вызове)
    3.   ----------------------------------------------------------------
    4.   SS:FFE0 |  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    5.   SS:FFF0 |  00 00 00 00  00 00 00 00  27 01 F6 17  02 32 00 00
    6.                                        |   | |   |  ^^^^^------ флаги
    7.                            SP=FFF8h ---+   | ^^^^^------------- CS:
    8.                                        ^^^^^------------------- IP
    9.   ;//---  IRET  (стек на выходе)
    10.   ----------------------------------------------------------------
    11.   SS:FFE0 |  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    12.   SS:FFF0 |  00 00 00 00  00 00 00 00  27 01 F6 17  02 32 00 00
    13.                                                           |
    14.                                               SP=FFFEh ---+
    15.  
    Если дать возможность отладчику сделать 1 стэп, то он дёрнет INT-1, а тот в свою очередь сохранит в стеке адрес возврата. В это время мы смещаем указатель(SP) на 6 влево, и запоминаем его в промежуточном регистре(BP). AVPutil хорошо отображает стек:

    Код (Text):
    1.  
    2.   CS:IP 17F6:0100  BP 0000  ODITSZAPC |    Stack
    3.   SS:SP 17F6:FFFE  FL 7202  001000000 |  -08 0000 _
    4.   ------------------------------------|  -06 0000  \  Содержимое стека
    5.   JMP     Short 0102                  |  -04 0000   > при загрузке клиента
    6.   NOP                                 |  -02 0000 _/  CS:IP = 17F6:0100
    7.   SUB     SP,0006                     |-> 00 0000
    8.   MOV     BP,SP                       |  +02 20CD
    9.   PUSH    DS                          |  +04 9FFF
    10.   PUSH    0000                        |  +06 9A00
    11.   POP     DS                          |  +08 FEF0
    12.   LES     BX,DWord ptr [0004]         |  +0A F01D
    13.  
    14. ;.................
    15.  
    16.   CS:IP 17F6:0102  BP 0000  ODITSZAPC |    Stack
    17.   SS:SP 17F6:FFFE  FL 3202  001000000 |  -08 17F6 _
    18.   ------------------------------------|  -06 0102  \  Фрейм INT-01h
    19.   NOP                                 |  -04 17F6   > после первого стэпа
    20.   SUB     SP,0006                     |  -02 3202 _/  CS:IP = 17F6:0102
    21.   MOV     BP,SP                       |-> 00 0000
    22.   PUSH    DS                          |  +02 20CD
    23.   PUSH    0000                        |  +04 9FFF
    24.   POP     DS                          |  +06 9A00
    25.   LES     BX,DWord ptr [0004]         |  +08 FEF0
    26.   MOV     Word ptr CS:[012A],BX       |  +0A F01D
    27.  
    Теперь, если перехватить отладочное прерывание(1) и дописать в его начало MOV_SP,BP, то IRET на каждом шаге будет возвращать отладчик в прошлое, заставляя его маршировать на одном месте. Реализовать это достаточно просто, и эффект на выходе получаем прикольный:

    Код (ASM):
    1.  
    2. start:  nop                    ; получили адрес возврата
    3.         sub   sp,6             ; запоминаем его справа от SP
    4.         mov   bp,sp            ; BP = указатель на предыдущий фрейм
    5.  
    6.         push  ds 0             ;
    7.         pop   ds               ;
    8.         les   bx,[4]           ; ES:BX = адрес обработчика(1)
    9.         mov   [cs:bug+0],bx    ; копируем его в переменную
    10.         mov   [cs:bug+2],es    ; ^^^
    11.         push  cs @back         ;
    12.         pop   eax              ; EAX = адрес нового обработчика
    13.         mov   dword[4],eax     ; перехватываем прерывание(1)
    14.         pop   ds               ;
    15.         jmp   @begin           ; уходим на начало полезного кода..
    16. ;-----------------------------------------------------------------
    17. ;// Новый обработчик INT-1
    18. ;// Если мы здесь, значит SP сместился ещё на 6 влево от BP
    19. @back:  mov   sp,bp            ; возвращаем SP в прошлое
    20.         db    0EAh             ; JMP FAR
    21. bug     dw    0,0              ;    ..на обработчик отладчика
    22. ;-----------------------------------------------------------------
    23.  
    24. @begin: nop
    25. ;[......]
    26.  
    С этого момента участь отладчика предрешена! Адрес возврата в стеке замёрзнет на одной позиции, и последствия не заставят себя ждать. На работу программы в обычном режиме это не окажет никакого влияния, т.к. перехватывается тут только отладочное прерывание(1). Без отладчика, джумп в хвосте тупо прыгнет на метку '@begin:' и программа продолжит работу без каких-либо эксцезов.
     
    hjujet, Mikl___ и rococo795 нравится это.
  15. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    Неплохие возможности предоставлет нам и 'Префикс программного сегмента' - PSP.
    В нём зарыто много интересных полей, на которые можно направлять(SP) и передавать управление через RET_FAR. Отладчик начнёт трассировать не нашу программу, а например сам-себя, или вообще command.com. Наиболее интересные поля PSP описаны ниже:

    Код (Text):
    1.  
    2.              0  1  2  3   4  5  6  7   8  9  A  B   C  D  E  F
    3.             --------------------------------------------------
    4. 17F6:0000 | CD 20 FF 9F  00 9A F0 FE  1D F0 6D BD  6B 05 4B 01
    5. 17F6:0010 | 12 04 51 BE  6B 05 6B 05  01 01 01 00  02 FF FF FF
    6. 17F6:0020 | FF FF FF FF  FF FF FF FF  FF FF FF FF  B0 17 FE FF
    7. 17F6:0030 | F6 17 14 00  18 00 F6 17  FF FF FF FF  00 00 00 00
    8. 17F6:0040 | 05 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    9. 17F6:0050 | CD 21 CB 00  00 00 00 00  00 00 00 00  00 20 20 20
    10. 17F6:0060 | 20 20 20 20  20 20 20 20  00 00 00 00  00 20 20 20
    11. 17F6:0070 | 20 20 20 20  20 20 20 20  00 00 00 00  00 00 00 00
    12. 17F6:0080 | 00 0D 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    13. ;......
    14.  
    15.    Addr Size Name
    16.    -----------------------------
    17.    00h     2       Инструкция INT 20h
    18.    02h     2       Сегмент первого\свободного байта за пределами программы = 9FFF:
    19.    05h     5       CALL_FAR на диспетчер DOS    =  CALL  F01D:FEF0
    20.    0Ah     4       Адрес входа в Command.com    =  056B:BD6D
    21.    0Eh     4       Адрес обработчика Int-23h    =  0412:014B
    22.    12h     4       Адрес обработчика Int-24h    =  056B:BE51
    23.    16h     2       Сегмент PSP родителя         =  056B:
    24.    2Ch     2       Сегмент блока окружения      =  17B0:
    25.    2Eh     4       Адрес стека программы SS:SP  =  17F6:FFFE
    26.  
    При чтении данных из PSP нужно иметь в виду, что поля её заполняются правильными значениями только после вызова сервиса DOS. В противном случае адреса будут указывать в космос, и наш план потерпит фиаско. Для этого можно использовать любую функцию DOS с приставкой(GET), которую мы тупо проигнорируем - например AH=30h 'Get_DOS_Ver'.

    Если у вас дося версии(5), то вообще шик.. и можно сразу отправить отладчик дебажить 'Диспетчер DOS', ниточка к которому торчит в PSP по смещению(5) от его начала:

    Код (ASM):
    1.  
    2. ;//-- Вариант(#1). Экскурсия в досовский код
    3. ;-------------------------------------------
    4. start:  push  ss         ;
    5.         pop   ss         ;
    6.         pushf            ;
    7.         pop   bx         ;
    8.         test  bh,1       ; проверяем бит(TF)
    9.         je    @okey      ;
    10.  
    11.         mov   ah,30h     ; заполняем PSP 'правильными' значениями
    12.         int   21h        ; АХ = 5, если версия дос 5.00
    13.         jmp   ax         ; CALL_FAR на диспетчер DOS
    14.  
    15. @okey:  nop              ; нет отладчика..
    16. ;...
    17.  
    18. ;//-- Вариант(#2). Подмена адреса возврата
    19. ;//-- отладчик трэйсит сам-себя.
    20. ;-------------------------------------------
    21. start:  mov   ah,54h     ;
    22.         int   21h        ; правим PSP
    23.  
    24.         mov   bx,12h     ; ВХ = поле в PSP (вектор int24h)
    25.         push  ss         ;
    26.         pop   ss         ;
    27.         pushf            ;
    28.         pop   ax         ;
    29.         test  ah,1       ; проверяем бит(TF)
    30.         je    @okey      ;
    31.  
    32.         mov   sp,bx      ; подменяем адрес возврата
    33.         retf             ; уходим по нему!
    34.        
    35. @okey:  nop              ; нет отладчика..
    36. ;...
    37.  
    Продолжение следует..
     
    hjujet, Мановар, Mikl___ и ещё 1-му нравится это.
  16. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    ЗАЩИТА ДЛЯ ЗАЩИТЫ

    Тему проверки бита(TF) можно продолжать бесконечно, но есть-ли в этом смысл? Реверсеру достаточно забить NOP'ами обработчик 'иначе', как защита разлетается в пух-и-прах. Не завидная перспектива.. От сюда следует, что для создания охранной системы одной сигнализации недостаточно, а нужен ещё и питбуль, который примчится на первый-же свист. В теории всё просто, а как-же на практике защитить свою тушку от обезвоживания?

    NOP идеально подходит для кастрации критических узлов кода, но только в клинических случаях, - когда кодер совсем не ценит свой труд и время. Как правило, чтобы противостоять забою нопом, тушку шифруют или переодически сверяют её контрольную сумму. Если копнуть исходник любого BIOS, то можно найти в нём типичный алгоритм проверки целостности кода. Назовём его (CRC-8):
    Код (ASM):
    1. Microsoft (R) MASM version 5.00
    2. page, 120
    3. TITLE    ROM_CHECKSUM
    4. ;============================================================;
    5. ; DESCRIPTION: This routine checksums ROM modules and writes ;
    6. ; down the complementary result in the last ROM byte.           ;
    7. ;============================================================;
    8. 0000  B9 FFFF             start:     mov      cx,0ffffh         ;number of bytes
    9. 0003  32 C0                          xor      al,al
    10. 0005  BB 0000                        mov      bx,0
    11. 0008  02 07               lp:        add      al,[bx]           ;checksum --> AL
    12. 000A  43                             inc      bx
    13. 000B  E2 FB                          loop     lp
    14. 000D  32 D2                          xor      dl,dl
    15. 000F  2A D0                          sub      dl,al                ;the complementary result
    16. 0011  88 17                          mov      [bx],dl
    17.  
    Но что это меняет? Опять участок кода, который точно-также подвержен NOP-инъекции. В добавок, на выходе нужна ещё и проверка(JZ), которую легко обратить на JNZ всего одним битом. Отложим в сторону кроссворд и подумаем, как можно закрыть доступ к модулю "CheckSum".


    Так что-же творит отладчик внутри перехваченного INT-1?
    Откроем любую прогу в TD, сделаем 1 шаг, перейдём табом в окно его дампа, и комбинацией [CTRL+G] введём [0:0], чтобы запелинговать обработчик INT-1h. Здесь видно, что отладчиком перехвачены инты(0,1,3) в сегменте 199Eh, а интересующий нас обработчик(1) лежит по смещению [199E:01CEh]:
    Код (Text):
    1. [/COLOR][/COLOR]
    2. [COLOR=#0000ff][COLOR=#000000]           |<---0-------------1---------------------------3--->|
    3.            |                                                   |
    4. fs:0000   54 02 9E 19 - CE 01 9E 19 - 16 00 93 03 - D7 01 9E 19
    5. fs:0010   8B 01 70 00 - B9 06 0E 02 - 40 07 0E 02 - FF 03 0E 02
    6.  

    Ясно.. Значит чтобы попасть в сердце отладчика достаточно установить бряк на этот адрес, как мы окажемся внутри святыни. Если отладчик позволит трэйсить свой-же обработчик, значит его механизм защиты для наших целей не подойдёт, и нужно искать другие пути. Переходим табом в окно кода и через [ALT+F2] ставим точку останова на адрес 199Eh:01CEh. Всё готово.. Теперь дебагер должен ткнуть нас носом прямо в обработчик(1), что и происходит при первом-же стэпе.

    Упс.. Пара CS:IP в окне регистров сразу-же сменилась, и мы наблюдаем два одинаковых входа в обработчики(1,3) с адресами 01CEh\01D7h соответственно. Тут даже одна функция(046Fh) на двоих, просто аргумент в AL разный:
    Код (Text):
    1.  199E:01CE  50             push   ax[/COLOR][/COLOR]
    2. [COLOR=#0000ff][COLOR=#000000]199E:01CF  B002           mov    al,02
    3. 199E:01D1  90             nop
    4. 199E:01D2  0E             push   cs
    5. 199E:01D3  E89902         call   046F
    6. 199E:01D6  CB             retf
    7.  
    8. 199E:01D7  50             push   ax
    9. 199E:01D8  B003           mov    al,03
    10. 199E:01DA  90             nop
    11. 199E:01DB  0E             push   cs
    12. 199E:01DC  E89002         call   046F
    13. 199E:01DF  CB             retf
    14.  
    15. ;// ==  Снимем бряк по [F2], и заглянем внутрь функции(046F) клавишей [F7]:
    16.  
    17.    cs:046F->55             push   bp
    18.    cs:0470  8BEC           mov    bp,sp
    19.    cs:0472  56             push   si
    20.    cs:0473  57             push   di
    21.    cs:0474  50             push   ax
    22.    cs:0475  E421           in     al,21         ; читается маска IRQ
    23.    cs:0477  2EA26F00       mov    cs:[006F],al  ;
    24.    cs:047B  0C1B           or     al,1B         ; ставятся в ней биты 1Bh = 0001 1011b
    25.    cs:047D  E621           out    21,al         ;      ...маскируются IRQ:     4 3 10
    26.    cs:047F  58             pop    ax            ; ------------------------------------
    27.    cs:0480  FB             sti                  ; ^^^ это инты: 08h,09h,0Bh,0Ch
    28.    cs:0481  5F             pop    di
    29.    cs:0482  5E             pop    si
    30.    cs:0483  5D             pop    bp
    31. ;//...........
    32.    cs:049C  B020           mov    al,20         ; посылка EOI в APIC
    33.    cs:049E  E620           out    20,al         ;
    34. ;//...........
    35.  

    Главный облом в том, что все\подопытные дебагеры позволяют трассировать себя, а значит защита от инопланетного вторжения в них не предусмотрена. Может это и к лучшему? Есть повод активировать своё\серое вещество.

     
  17. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    Пересчёт контрольной суммы кода

    Ламерский алгоритм CRC-8 приведён выше, и подразумевает сложение всех байт критического участка (отбрасывая перенос), и вычитания полученной суммы из константы 100h. Следующий код демонстрирует пример подсчёта контрольной суммы. Саму функцию "CheckSum" я вклеил в обработчик INT-1h и закинул в хвост сегмента, чтобы она не маячила в отладчике. Для опытных крэкеров это конечно булочка-с-маслом, но если учесть что ломкой страдают не только проф\пригодные взломщики, но и огромная армия пионеров, то шансы на спасение тушки всё-же остаются.

    Несколько слов о самой реализации, чтобы программа стала дееспособной..

    1. Критический участок оформлен в виде запроса пассворда, с которого считается 1-байтный хэш в регистр(BL). Хэш исходного пасса нужно расчитать до компиляции, и сравнивать его с регистром(BL). Я задал пароль "Коцит" и получил его хэш в виде байта(03h). Рассеивание никуда-не-годное, но это всего-лишь пример:
    Код (ASM):
    1. ;..
    2. @stop:  mov   ah,9            ;
    3.         mov   dx,ok           ;
    4.         cmp   bl,3            ; pass:?
    5.         je    @okey           ; OK!
    6.         dec   byte[$+5]       ; меняем int21h на int20h
    7. @okey:  int   21h             ;
    8. ;..
    9.  
    2. Контрольная сумма расчитывается только с критического блока, поэтому её тоже нужно расчитать заранее и сохранить где-нибудь в потоке кода, или в хвосте программы. Я выбрал последнее.. Для расчёта валидной CRC(8) можно задействовать тестовую функцию, которую потом изъять из кода. Эта функция обязательно должа находиться ПОСЛЕ блока с которого собираемся считать сумму, иначе (после её кастрации) все адреса поменяются, что приведёт к искажению CRC.

    3. В модуле "CheckSumm" (внутри обработчика) нет ничего нового, кроме фиктивного байта(0EAh), который представляет из-себя опкод инструкции [JMP_FAR]. Если вставить этот байт перед какой-нибудь инструкцией, а потом перепрыгнуть его обычным джумпом, то в дизассемблерном листинге получится каша, т.к. дизассемблер посчитает его реальной инструкцией и добавит последующие байты как сегмент:смещение к JMP_FAR. Вот отрывок этого участка, и как он дизассемблируется отладчиком:

    Код (ASM):
    1. ;// участок кода
    2. ;----------------------------------------------
    3.         and   ah,0        ;
    4.         jmp   @crc        ;
    5.         db    0EAh        ; jmp far
    6. @crc:   lodsb             ;
    7.         add   ah,al       ; считаем сумму..
    8.         loop  @crc        ;
    9.         shr   ax,8        ;
    10.         neg   al          ; 100h - AL = CRC(8)
    11.  
    12. ;// и его дизассемблерный листинг
    13. ;----------------------------------------------
    14. cs:0100->80E400         and    ah,00
    15. cs:0103  EB01           jmp    0106
    16. cs:0105  EAAC00C4E2     jmp    E2C4:00AC
    17. cs:010A  FB             sti
    18. cs:010B  C1E808         shr    ax,08
    19. cs:010E  F6D8           neg    al
    20.  
    После NEG_AL идёт сравнение контрольной суммы, с зашитой в программу константой. Разница должна быть нуль! Если внутри контролируемого участка прибить нопом хоть-одного тушканчика (или обратить проверку на JNZ), то CRC блока сразу-же изменится, а обработчик(1) отреагирует на это подменой адреса возврата. Последствия не заставят себя ждать..

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

    Код (ASM):
    1. ;fasm-code..
    2. ;------------
    3. org     100h
    4. jmp     start
    5.  
    6. mes0    db     'TurboDebuger MustDie!',13,10
    7.         db     'Type pass: $'
    8. ok      db     ' <---OK!$'
    9.  
    10. ;============= ТОЧКА ВХОДА =====================
    11. ;// перехватываем прерывание(1)
    12.                               ; На старте - в стеке нуль
    13. start:  pop    es             ; снимаем его в ES
    14.         push   es             ;    ^^..(баланс)
    15.         pushd  [es:4]         ; в стек вектор(1)
    16.         popd   [cs:old]       ; запомнить его в своём теле
    17.         push   cs vec1        ;
    18.         popd   [es:4]         ; ставим свой обработчик!
    19.         push   $+4            ;
    20.         ret                   ; Begin...
    21.  
    22. ;=======8<=============8<==============8<=======
    23. ;// Критический участка кода -------------------
    24. ;// с которой снимается CRC(8) -----------------
    25. begin:  mov   dx,mes0         ;
    26.         mov   ah,9            ;
    27.         int   21h             ; Type Password! (Коцит)
    28.         sub   bl,bl           ; хэш пароля
    29. @in:    xor   ah,ah           ; key
    30.         int   16h             ;
    31.         cmp   al,13           ; Enter?
    32.         je    @stop           ;
    33.         add   bl,al           ; считаем хэш..
    34.         mov   al,1            ;
    35.         int   29h             ; выводим смайлик
    36.         jmp   @in             ;
    37.  
    38. @stop:  mov   ah,9            ;
    39.         mov   dx,ok           ;
    40.         cmp   bl,3            ; pass:?
    41.         je    @okey           ; OK!
    42.         dec   byte[$+5]       ; int21h -1
    43. @okey:  int   21h             ;
    44. endCRC:                       ;
    45. ;=======8<=============8<==============8<=======
    46. ;// Вспомогательная функция расчёта CRC(8) -----
    47. ;// Закомментировать в обработчике "JNZ NEXT",--
    48. ;// и полученное значение(AH) вписать в самый хвост,
    49. ;// по адресу "check". -------------------------
    50. ;        mov   cx,endCRC
    51. ;        mov   si,begin
    52. ;        sub   cx,si
    53. ;        xor   ah,ah
    54. ;@lp:    lodsb
    55. ;        add   ah,al
    56. ;        loop  @lp
    57. ;        neg   ah       ;<-----; CRC(8)
    58.  
    59. ;============ Конец программы ==================
    60. ;-----------------------------------------------
    61. @exit:  xor   ax,ax           ;
    62.         int   16h             ;
    63.         mov   ax,4C00h        ; Game Ower!
    64.         int   21h             ;
    65.  
    66.  
    67. ;// Резервируем область, чтобы обработчик не мозолил глаза..
    68. space   rb    0FFFh
    69.  
    70. ;------------ ОБРАБОТЧИК INT-1 -----------------
    71. ; Считает CRC-8 критического участка кода,------
    72. ; и передаёт управление по цепочке обработчиков.
    73. vec1:   pusha                     ;
    74.         pushf                     ;
    75.         mov   cx,endCRC           ; CX = хвост блока
    76.         mov   si,begin            ; SI = голова
    77.         sub   cx,si               ; CX = длина блока
    78.         and   ah,0                ; AH = 0
    79.         jmp   @crc                ;
    80.         db    0EAh      ;<--------; (!)собьём листинг дизасма
    81. @crc:   lodsb                     ;
    82.         add   ah,al               ; ..считаем сумму..
    83.         loop  @crc                ;
    84.  
    85.         shr   ax,8                ; AL = контрольная сумма
    86.         neg   al                  ; 100h - AL = CRC(8)
    87.         sub   al,[check]          ;        ^^ должно быть нуль!
    88.         jnz   next      ;<--------; закоментировать при тесте
    89.         popf                      ;
    90.         popa                      ;
    91. next:   db    0EAh                ; передаём управление по цепочке
    92. old:    dd    0                   ;  ..на вектор отладчика
    93. check   db    90h       ;<--------; валидная контрольная сумма!
    94.                                   ;  ..у меня вышла 90h.
    95.  
    На этом повествование можно было и закончить, но хотелось-бы сказать, что для сокрытия следов своей деятельности лучше проверять CRC не сразу, а откладывать проверку до лучших времён. Например, пересчитав контрольную сумму сохранить её в надёжном месте и в момент, когда реверсер совсем этого не ожидает - проверить её. Ясно, что хранить CRC в переменных или в стеке не самая лучшая идея, но кроме них есть ещё и порты!

    Если записать значение в неиспользуемый порт, то оно будет храниться там, пока юзверь не ребутнёт машину. Только нужно выбирать порт, который доступен на чтение и запись, иначе ничего не выйдет. Как-нельзя лучше подходят на эту роль порты LPT с номерами 378h\278h. Это порты данных, которые как-раз доступны на R\W. Присутствие самого девайса при этом совсем не обязательно, - лишь-бы бивис поддерживал порт. Вот модернезированный вариант пересчёта контрольной суммы, с сохранением результата в порту(378h):

    Код (ASM):
    1. portB   dw    378h                ; где-нибудь..
    2. ;//------------------------
    3. ;[.....]
    4.         mov   dx,[portB]          ; номер порта
    5.         sub   ah,ah               ;
    6. @crc:   lodsb                     ;
    7.         add   ah,al               ; ..считаем сумму..
    8.         loop  @crc                ;
    9.         neg   ah                  ;
    10.         xchg  al,ah               ; AL = CRC(8)
    11.         out   dx,al               ; посылка до востребования!
    12. ;[.....]
    13.  
    14. ;// проверка в любое время
    15. ;//------------------------
    16.         mov   dx,[portB]          ;
    17.         imul  ax,0                ; AX=0
    18.         push  @okey               ;
    19.         mov   bp,sp               ; BP = адрес перехода
    20.         in    al,dx               ; берём CRC
    21.         xor   al,[check]          ; фэйс-контроль (впускают только лысых)
    22.         add   [bp],ax             ; прибавим АХ к адресу перехода
    23.         retn                      ; если CRC=0, то ничего не изменится
    24. @okey:  nop                       ; иначе: в космос..
    25.  
    Как говорилось в начале сабжа - невозможно раскрыть эту тему до конца в пределах одного топика, поэтому ставлю здесь жирную точку, и желаю Всем удачи!

    PS//::::::
    ------------------
    1. Материалы взяты из свободных источников.
    2. Все эксперементы проводились лично и только на своей машине.
    3. Программный код ни одного из отладчиков модификации не подлежал, а значит можно спать спокойно.

    The end..
     
    TermoSINteZ и Mikl___ нравится это.
  18. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    4. Во время проведения эксперимента ни один из отладчиков не пострадал :)
     
    Коцит нравится это.
  19. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    Коцит,

    Это же дос. Эти методы принципиально не портабельны на нт. Или под иную ось, которая использует IA архитектуру.

    И асм у вас кривой:

    1. cs:[old] должно быть, так как такова архитектура и адресация - селектор:смещение. Смещение задаётся в квадратных скобках, таким образом адрес отличается от константы, загружаемой в регистры.

    2. Такой инструкции не существует.
     
  20. Коцит

    Коцит Active Member

    Публикаций:
    0
    Регистрация:
    31 янв 2017
    Сообщения:
    130
    Уважаемый, это синтаксис фасма (поэтому и топик тут), а у него сегментный регистр указывается в скобках, т.к. по сути является частью адреса