• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья ASM для х86. (3.0) Практика

Часть 3. Практика под виндой

Теорию можно продолжать бесконечно, тем-более что тема эта настолько обширна, что закончить её можно только ко ‘второму пришествию’. Без практики она теряет смысл, поэтому пробежавшись по макушкам основ, рассмотрим фундаментальные команды ассемблера на практике.

В качестве компилятора буду использовать FASM, т.к. считаю его синтаксис самым правильным из всех ассемблеров. В отличии от своих собратьев, в нём нет двумысленных команд, да и в использовании он до неприличия прост - компилирует без всяких батников, по одной клавише F9 в своём окне. Весит всего 1 Мб и бесплатно валяется на сайте разработчика, от куда
. Теперь создайте в любой директории папку FASM и просто распакуйте в неё архив – fasm не требует установки в систему.

Ещё нам понадобится отладчик, чтобы воочию лицезреть происходящее внутри программы. Под виндой большой популярностью пользуется OllyDBG, причём советую скачать не вторую, , т.к. она возвращает более детальную информацию. Кроме того, для исследования ядерных структур, позже нужен будет и WinDbg от Microsoft. Этот зверь может отлаживать как пользовательские программы, так системные драйвера, до чего Оля пока не доросла. Ну и по мелочи, это дизассемблеры W32Dasm и HackersDisassembler, хек-редакторы HIEW32 и HxD. Калькулятор винды в инженерном виде, можно вообще закрепить на рабочем столе. Для начала этого хватит за глаза.


3.0. Приложение для тестов

Если с инструментами разобрались, напишем Win32 приложение, которое в дальнейшем будем использовать как ‘бокс’ для вставки в него различных инструкций. Запустим FASMW.EXE и скопи\пастим в окно его редактора такой код:

C-подобный:
format   PE gui
include  'win32ax.inc'
;------
.data
capt     db  'Прога v1.0',0
text     db  'Программируем на FASM под Win32.',13,10
         db  'Для выхода из программы нажмите ОК!',0
;------
.code
start:   invoke  MessageBox,0,text,capt,0
         invoke  ExitProcess,0
;------
.end start
Теперь просто жмём F9 и должна появиться такая форточка..

api_0.png


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

Весь код состоит из четырёх частей. В заголовке строка PE-GUI указывает fasm’у собрать оконное, а не консольное приложение. Инклуд почти всегда будет только один ‘win32ax.inc’ – он универсальный и подходит для большинства случаев. Дальше идёт секция-данных, в которой задаём имя окну, и текст в его тушке. 13,10 означает перевод строки, сама-же строка должна быть в кавычках и заканчиваться нулём. Под виндой все строки нуль-терминальные, в отличии от ДОС, где маркером окончания строки служит символ доллара($).

За секцией-данных сразу начинается секция-кода. Метка start: (с двоеточием в конце) является точкой-входа в программу, что на буржуйский манер звучит как ‘Entry-Point’ или просто ЕР. Если открыть эту программу в отладчике, то первым его Breakpoint будет как раз ЕР.

Для вывода диалогового окна используем Win-API ‘MessageBox’, которой в качестве параметров передаём адреса выводимых строк. Последний её параметр(0) задаёт кол-во буттонов в окне. Попробуйте установить его в 1, получите две кнопки ОК\Отмена.

Следом за первой API идёт вторая ‘ExitProcess’, которая прихлопнет нашу программу и выгрузит её из памяти Win. В самом хвосте кода, расположилась секция-импорта виндовых API-функций - это макрос такой ‘.end метка’. В качестве стартовой метки ЕР можно использовать любое имя (не обязательно start:), главное – потом подставить в макрос .end это-же имя.


3.1. Основные команды ассемблера.

3.1.0. Организация циклов

Циклы в ассемблере можно строить по разному - тут зависит от поставленной задачи.
Допустим нужно посчитать длину строки и мы знаем, что она заканчивается нулём. Тогда обнуляем один из свободных регистров, и на каждом шаге (итерации) увеличиваем этот регистр на 1. Как-только найдём терминальный нуль, выходим из цикла и в регистре-счётчике получаем длину строки. В этом случае нужна проверка и переход назад, если условие ложно. Пример такого цикла представлен ниже:


C-подобный:
format   PE gui
include  'win32ax.inc'
;------
.data
capt     db  'Считаем длину строки',0
text     db  'Программируем на FASM под Win32.',13,10
         db  'Для выхода из программы нажмите ОК!',0
;------
.code
start:   sub     ecx,ecx         ; ECX будет счётчик - обнуляем его.
         mov     esi,text        ; ESI = указатель на начало строки
@cycle:  cmp     byte[esi],0     ; цикл! сравнить байт из ESI с нулём.
         je      @ok             ; переход на метку, если равно (Jump Equal)
         inc     ecx             ; иначе: счётчик +1
         inc     esi             ; сдвигаем указатель ESI на сл.позицию в строке
         jmp     @cycle          ; вернуться в начало цикла!

@ok:     invoke  MessageBox,0,text,capt,0
         invoke  ExitProcess,0
;------
.end start
В комментах есть все пояснения, поэтому повторяться не буду.
Загрузите эту программу в отладчик OllyDbg и потрассируйте её клавишей F8.
По окончании цикла, в ECX получим длину строки равной 45h (69) символов.


cycle.png


Другой вариант цикла – это когда мы знаем длину строки, и нам нужно что-нибудь с ней сделать. В этом случае можно пойти по такому-же алгоритму, только не увеличивать счётчик, а поместив в него длину наоборот уменьшать его, пока не получим в нём нуль. Однако есть и другой вариант.

Для организации циклов, в ассемблере предусмотрена специальная команда LOOP, которая требует в качестве счётчика строго регистр ECX. Как только он обнуляется, LOOP автоматически останавливает цикл. Кроме того, эта инструкция сама и уменьшает ECX на каждой итерации, и нам не нужно двигать его руками. Вообщем как-раз то, что доктор прописал.

Попробуем таким макаром, например, зашифровать всю строку. Метод шифрования сейчас не важен.. пусть будет обычная операция XOR с любым 1-байтным ключом, таким как 7Eh. Вот пример такого алгоритма (будем считать, что длину строки(69) мы уже знаем из предыдущего кода):

C-подобный:
.code
start:   mov     ecx,69          ; ECX = длина строки для LOOP
         mov     esi,text        ; ESI = адрес строки
@crypt:  xor     byte[esi],7Eh   ; цикл! шифруем байт из ESI нашим ключом
         inc     esi             ; сл.байт в строке..
         loop    @crypt          ; повторить ECX-раз!

         invoke  MessageBox,0,text,capt,0
         invoke  ExitProcess,0
.end start

3.1.1. Команды для работы со-строками

Часто приходится иметь дело с большими массивами данных. Это может быть и просто копирование блоков памяти с места-на-место, или например повторить одну операцию несколько раз. Для таких случаев в ассемблере предусмотрены т.н. ‘цепочечные’ команды. Плюсы их в том, что не нужно вручную двигать указатели – они сдвигаются на автомате:
  • LODSB - чтение в AL из ESI;
  • STOSB - запись AL в EDI;
  • MOVSB - копирование из ESI в EDI;
  • CMPSB - сравнивает байт EDI, с байтом ESI (сравнивает строки);
  • SCASB - сравнивает байт AL, с байтом EDI (поиск символа в строке).
Буквально во-всех случаях, в качестве указателя на источник данных используется регистр ESI, а EDI – это всегда приёмник. Окончание(B) в названии команды означает байт, соответственно можно заменить его на: W=2-байта (word), или D=4-байта (dword). В зависимости от этого окончания, указатели авто\увеличиваются на 1, 2 или 4-байта.

Для организации циклов к ним применяется префикс REP. Как и в случае с командой LOOP, счётчиком служит регистр ECX, который при каждом шаге уменьшается на 1. Ниже приведены модификации префикса REP для строковых команд:

Код:
REP   - применяется для MOVSB (копирование строки)
REPE  - применяется для CMPSB (сравнение строк)
REPNE - применяется для SCASB (поиск символа в строке)
В качестве демки напишем программу, которая выведет в окно Environment машины, на которую попала. Среди этих данных будет: имя пользователя, версия Win, пути установки и прочая информация. Эти данные система копирует в самый подвал адресного пространства любого процесса, который запускает на исполнение. Это адрес 0х10000, хотя для самой программы выделяется база 0х400000. Адреса ниже 0х10000 система резервирует для отлова нулевых указателей. Это 64К-байтный сегмент от нуля, куда приложениям доступ закрыт:

envir.png


Как видно из этого скрина, текстовые строки хранятся там в виде ‘Unicode’, т.е. каждый символ кодируется двумя байтами, а не одним как в ASCII. Второй байт всегда нуль. Значит наша задача поставить указатель источника ESI на адрес 0х10000, а приёмник EDI прицелить на буфер в своей программе. Теперь, чтобы отсеять нули, читать будем из ESI по 2-байта, а сохранять в EDI по одному. Тогда получим ASCII-строку, которую можно будет сбоксить в мессагу функцией MessageBox(). Каждая строка текста заканчивается Unicode нулём (выделен синим), а после всего блока данных идёт топкое болото нулей, т.е. маркером окончания блока - будет 4 нуля подряд. Это на словах громоздко, а на деле..

C-подобный:
format   PE gui
include 'win32ax.inc'
;-------
.data
capt    db      'Блок окружения системы',0
buff    db      2048 dup(0)     ; выделяем 2К буфер в секции-данных
;-------
.code
start:  mov     esi,10000h      ; указатель на источник
        mov     edi,buff        ; адрес приёмника
@unicode:
        lodsw                   ; берём 2-байта из ESI в АХ
        cmp     ax,0            ; конец строки?
        jz      @test           ; да - на метку! (Jump Zero)
        stosb                   ; иначе: сохраняем в EDI только байт AL (из всего АХ)
        jmp     @unicode        ; продолжить.. (lodsw сам увеличивает esi на 2)

;// Встретилась пара нулей! Проверка на конец блока данных --------
@test:  cmp     dword[esi],0    ; проверить 4-байта из ESI на нуль
        je      @stop           ; если нарвались на болото нулей - стоп!
        mov     ax,0a0dh        ; иначе: перевод строки 13,10
        stosw                   ; вставить их в буфер!
        jmp     @unicode        ; продолжить..

@stop:  invoke  MessageBox,0,buff,capt,0    ; выводим мессагу на экран
        invoke  ExitProcess,0               ; выход из приложения!
.end start
envir_0.png


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


3.1.2. Команды CALL и RET – передача и возврат управления

Эта парочка должна всегда ходить вместе. Команда CALL кладёт на вершину стека адрес-возврата и вызывает какую-нибудь процедуру, а команда RET должна находится в конце вызванной процедуры, и сняв со стека адрес-возврата, передаёт на него управление. Рассмотрим такой код в отладчике..


C-подобный:
format   PE gui
include 'win32ax.inc'
;-------
.data
capt1   db     'Окно процедуры',0
text1   db     'Это вызов процедуры из приложения!',0
capt2   db     'Выход',0
text2   db     'Программа завершена успешно!',0
;-------
.code
start:  call    Merylin        ;// вызов процедуры!
        nop
        nop
        invoke  MessageBox,0,text2,capt2,0
        invoke  ExitProcess,0
;- Процедура ------
proc    Merylin
        invoke  MessageBox,0,text1,capt1,0
        ret                    ;// возврат управления
endp
.end start
ret.png


Инструкция RET вообще окутана тайнами, к которым прибегают многие асматики. Дело в том, что для процессора регистр EIP играет роль собаки-поводыря - куда он указывает, туда процессор и идёт. Значит чтобы перехватить управление у программы, нужно перехватить регистр EIP. Только он не поддаётся никаким уговорам и ‘причесать’ его не так-уж просто. В него нельзя записать значение, его нельзя поместить в стек, и вообще с ним ничего нельзя делать.

И тут на помощь приходит инструкция RET. Достаточно через PUSH поместить указатель на любую область памяти в стек, и следом вызвать инструкцию RET, как EIP сменит свой нрав на милость и прямиком последует за нами. Это старый приём, который назвали ‘фиктивным вызовом функций’.



3.1.3. Условные переходы

Под занавес главы, вспомним таблицу условных переходов, где литер N втиснутый по-середине, просто обращает условие на противоположное. Литер E в конце, уточняет условие и означает равно (например, JBE - это "ниже или равно", а JNG - "не больше"):
Код:
+----------------+-----------------+-------------------+
|   Символьный   |    Знаковый     |    По-флагам      |
+----------------+-----------------+-------------------+
|   JB - ниже    |   JL - меньше   |   JS - знак(-)    |
|   JE - равно   |   JZ - ноль     |   JC - перенос    |
|   JA - выше    |   JG - больше   |   JP - чётный     |
+----------------+-----------------+-------------------+
 
Последнее редактирование:

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 312
BIT
114
3.1.4. Stack - и его команды

Для комфортной работы, системе необходима некоторая область памяти для производственных нужд. Типичная ситуация, когда нужно временно сохранить значение, а свободных регистров нет. Для таких случаев, ещё в i8086 был выделен сегмент памяти под названием стек, и специальная регистровая пара SS:SPStack-Segment and Pointer. Устройство данного сегмента отличается от остальных тем, что адреса в нём не увеличиваются, а наоборот уменьшаются. То-есть стек растёт снизу-вверх, а не как обычные сегменты сверху-вниз.

В доках Интела такую картину назвали ‘Expand-Down’ – обратное расширение адреса. В предыдущей части я приводил скрин с описанием сегментного дескриптора. В нём есть 4-битное поле ‘Type’, в котором описывается тип и характеристики отдельно взятого сегмента. Географически, эти биты заняли позиции [11-8] в 8-байтном дескрипторе, их описание из третьего тома приводится ниже:


stype.png


На рисунке видно, что если бит(11) установлен в единицу, перед нами сегмент-кода, иначе данных. Стек – это разновидность сегмента данных, только со-взведённым битом(10) Expand. Судя по докам, стек должен быть неисполняемым Execute, однако на практике выходит совсем иначе, и если запихать в него шелл-код, то он прекрасно отработает. К сожалению INTEL во-время спохватилась и кастрировала эту возможность, введя бит NX с технологией DEP (Data-Execution-Protect).

Для работы со-стеком имеются две команды - PUSH кладёт значение на вершину стека, а POP – снимает его от туда. При этом ESP автоматически уменьшается или увеличивается на 4, соответственно. Посмотрим на такой рисунок:


stack_0.png


Устройство стека можно сравнить с боксом для лазерных дисков. Командой PUSH мы кладём в стек первый dword, и он падает на дно стека. Значение ESP при этом авто\уменьшается на 4. Следующий PUSH бутербродом положит свой dword сверху, и ESP опять уменьшится на 4. Теперь, чтобы изъять из стека первое значение, нужно вытащить все сверху-лежащие. Поэтому программист должен следить за стеком в оба глаза, чтобы стек не оказался не выровненным. То-есть сколько положили в стек, столько нужно и снять с него. Если вспомнить свойства инструкции RET, то станет ясно ‘почему’.

Кроме нашей программы, стек активно используют и системные API. В промышленной их реализации всегда присутствует т.н. ‘Пролог’ и ‘Эпилог’. Пролог копирует ESP в EBP, чтобы через него получить доступ к параметрам функции в стеке. Поскольку (при вызове функции) CALL сохраняет ещё и адрес-возврата, то первый параметр получается по смещению EBP+4, второй EBP+8 и т.д.. Эпилог очищает стек от параметров, тем-самым выравнивая его на выходе из функции. В качестве демонстрации, напишем пустую функцию с параметрами, чтобы посмотреть, как преобразит её FASM.

C-подобный:
include 'win32ax.inc'
;-------
.code
start:  stdcall  myFunc,1234h,5678h
        invoke   ExitProcess,0

proc    myFunc a,b
        nop
        nop
        ret
endp
.end start
prolog.png


Как видим, FASM добавил в код отсебятину, т.к. мы планировали внутри функции только две инструкции ‘No-Operation’ NOP. Причём в конце стоит не просто RET, а RET 8, чтобы снять со-стека два аргумента по 4-байт, которые мы туда положили при вызове функции. То-есть компилятор сам оптимизирует код, чтобы избавить нас от рутины.

Кроме PUSH есть ещё и инструкция PUSHA, что означает Push-All-Registers. Она без операндов и помещает в стек сразу 8 регистров РОН в следующем порядке: EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI. Снять их скопом в таком-же порядке позволяет инструкция POPA (хоть и звучит неприлично). Код ниже показывает, как можно вывести на экран значения всех РОН при помощи инструкции PUSHA. Для преобразования числа в строку, задействуем API wsprintf():

C-подобный:
format   PE gui
include 'win32ax.inc'
;-------
.data
frmt    db  'EDI...:  %08X',13,10               ; спецификаторы для wsprintf()
        db  'ESI...:  %08X',13,10               ;
        db  'EBP...:  %08X',13,10               ;
        db  'ESP...:  %08X  - ординал',13,10    ;
        db  'EBX...:  %08X',13,10               ;
        db  'EDX...:  %08X',13,10               ;
        db  'ECX...:  %08X',13,10               ;
        db  'EAX...:  %08X',0                   ;

capt    db  'RegDump v1.0',0
text    db  'Состояние регистров процессора',13,10
        db  '------------------------------',13,10
buff    db  512 dup(?)                          ; буфер для преобразованных строк
;-------
.code
start:  pusha                                   ; запомнить все регистры в стеке
        invoke  wsprintf, buff, frmt            ; куда и в каком формате скинуть результат
        add     esp,8                           ; за wsprintf() нужно очищать стек
        popa                                    ; восстановить все регистры
        invoke  MessageBox,0,text,capt,0        ; вывод результата на экран!
        invoke  ExitProcess,0                   ;
.end start
regdump.png


Программный стек – это кладезь информации, которую оставляют после себя различные функции. Дело в том, что инструкция POP не затирает значения в стеке, а просто сдвигает указатель ESP. Это приводит к тому, что вся информация остаётся на месте и если прокрутить стек назад, то можно найти в нём много интересного.

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

CKAP

Green Team
10.06.2019
68
39
BIT
0
> Если при компиляции получили ошибку, значит у вас система 64-битная >__<

Так это, ВМ машину поднять для практики.. или лучше все же физическая. Или просто include 'win64a.inc' 0__________О
Я так понимаю Вы\Ты матан будете подавать отталкиваясь от 32 битной.. короче как лучше то? О великий...
 
Последнее редактирование:

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 312
BIT
114
Или просто include 'win64a.inc'
Простой сменой инклуда тут не обойтись, т.к. у функций х32 параметры передаются через стек, а у х64 через регистры – т.е. они не совместимы. Поэтому да.. лучше установить ВМ. Просто у меня машина 32-битная, соответственно и примеры все буду приводить на ней.
 
  • Нравится
Реакции: Mikl___ и CKAP

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 312
BIT
114
3.1.5. Регистр EFLAGS - и команды управления им

EFLAGS - это информационный регистр процессора размером 32-бита. Такая разрядность позволяет характеризовать 32 его состояния. Каждый бит назвали флагом ‘Flag’, которые меняют своё состояние в результате выполнения арифметических и логических команд, а также команд ротации и сдвигов.

Некоторые флаги программист может менять вручную, что приводит к изменению состояния процессора. Например, можно заставить проц не реагировать на внешние прерывания, а можно задать ему обратный шаг для адреса. Регистр флагов можно сохранить в стек командой PUSHF, и после некоторых действий восстановить прежнее состояние процессора, командой POPF. Во всех случаях, флаги сохраняют своё значение до тех пор, пока другая команда не изменит их.

Из 32-х флагов используются не все - некоторые отправлены на полку до лучших времён. Наиболее информативна 16-битная младшая часть регистра под названием FLAGS (без приставки ‘E’), которая пришла в защищённый режим из ДОСа. Но и в этом случае только 9 из 16-ти заняты. Вот их нумерация во-флаговом регистре:

C-подобный:
*********************************************************************
Номер бита:    15  14  13  12  11  10  9  8  7  6  5  4  3  2  1  0
Флаг:           *   *   *   *   O   D  I  T  S  Z  *  A  *  P  *  C
*********************************************************************
Флаги процессора – важная часть программирования, поэтому каждый отладчик отображает их в своём окне, и Оля не исключение.

flags.png


1. CF (Carry Flag) - флаг переноса. Содержит значение переносов (0/1) из старшего разряда операнда, при арифметических и логических операциях. Наиболее часто используемый флаг. Командами для его проверки являются JC или JNC. Например, если мы прибавили к регистру некоторое значение и сумма превысила его ёмкость 32-бит (старший разряд ушёл в 33-ий бит), то процессор взведёт флаг CF.

2. PF (Parity Flag) - флаг чётности. Проверяет только младший байт регистра. Нечётное число бит приводит к установке этого флага в 0, а чётное - в 1. Не следует путать флаг чётности с битом контроля на чётность – это разные вещи. Флаг проверяют команды перехода JP или JNP.

3. AF (Auxiliary Carry Flag) - дополнительный флаг переноса. Устанавливается в 1, если арифметическая операция приводит к переносу четвёртого справа бита (бит 3) в регистровой однобайтовой команде. Данный флаг имеет отношение к арифметическим операциям над символами кода ASCII, и к упакованным числам BCD. Используется редко. Инструкция перехода JA не имеет отношения к этому флагу.

4. ZF (Zero Flag) - флаг нуля. Часто используемый флаг. Флаг проверяют команды перехода JE/JZ или JNE/JNZ. Нулевой результат при проверках и арифметических операциях приводит к установке в 1 этого флага (т.е. 0 обозначает ‘нет’, а 1 "да").

5. SF (SIgn Flag) - знаковый флаг. Процессор выставляет этот флаг в 1, если проверяемое число отрицательное (взведён старший бит). Флаг проверяют команды условного перехода JG и JL.

6. TF (Trap/Trace Flag) - флаг пошагового выполнения. Если этот флаг установлен в 1, то процессор переходит в режим пошагового выполнения каждой инструкции. Все отладчики взводят этот флаг, для трассировки своих клиентов.

7. IF (Interrupt Flag) - флаг прерывания int. При 0 – процессор даёт команду контроллёру прерываний, чтобы тот замаскировал их все, при 1 – прерывания разрешены. Этот флаг используется процессором только в реальном режиме работы, т.к. в защищённом режиме внешние прерывания обрабатываются иначе, через таблицу IDT – Interrupt Dispatch Table. Поэтому Оля не отображает его. Флагом оперируют инструкции CLI (Clear Int) и STI (Set Int).

8. DF (Direction Flag) - флаг направления. Используется в строковых операциях для определения направления передачи данных. При 0 - увеличивается содержимое регистров ESI/EDI, вызывая передачу данных обычным способом слева-направо, при 1 - уменьшает содержимое этих регистров, вызывая передачу данных справа-налево. Выставить в 1 этот флаг позволяет инструкция STD (Set), а сбросить CLD (Clear).

9. OF (Overflow Flag) - флаг переполнения. Фиксирует арифметическое переполнение, т.е. перенос вниз старшего (знакового) бита при знаковых/арифметических операциях. Используется редко.

Большинство из флагов регистра EFLAGS нельзя изменять напрямую, т.к. флаги просто показывают нам состояние процессора в текущий момент. Но есть 3 флага, которые программист может менять, в зависимости от поставленной задачи. Это флаги: CF, DF и IF. У них нет операндов и результатом является только изменение значения соответствующего флага.

В качестве примера напишем код, который выведет на экран значение любого регистра в двоичном виде. Это полезная фишка, т.к. большинство API-функций не позволяют этого сделать, например тот-же wsprintf(). Он преобразует только в HEX, DEC и OCT (16-10-8).

Для достижения цели, воспользуемся флагом-переноса CF. Поскольку он взводится при выходе числа за пределы разрядной сетки, то нам достаточно имитировать такую ситуацию вручную, сдвигая в цикле число влево и контролируя CF. Если выталкнится нуль, то процессор флаг не взведёт, а если взведёт – значит это единица. Пробуем..

C-подобный:
format   PE gui
include 'win32ax.inc'
;-------
.data
capt    db  'Binary converter v1.0',0
text    db  'Значение EDX в BIN = '
buff    db   64 dup(0)                  ; буфер для символов в BIN
;-------
.code
start:  push    edx        ; EDX в стек (будем выводить его значение)
        pop     ebx        ; снимаем его в регистр EBX
        mov     ecx,32     ; кол-во разрядов, и длина цикла
        mov     edi,buff   ; адрес приёмника
@binary:                   ; начало цикла
        mov     al,'0'     ; символ по-умолчанию - нуль
        shl     ebx,1      ; сдвинем EBX на 1 влево - Shift Left
        jnc     @fuck      ; переход, если CF=0
        mov     al,'1'     ; иначе: меням символ 0 на 1
@fuck:  stosb              ; записать AL по адрес EDI.
        loop    @binary    ; повторить цикл ECX-раз..

        invoke  MessageBox,0,text,capt,0    ; результат в форточку!
        invoke  ExitProcess,0
.end start
bin.png


Работа с флагами – неотъемлемая часть любой программы на ассемблере. Младший их байт можно сохранять инструкций LAHF, что означает Load AH Flag. При этом флаги попадают в 1-байтный регистр AH. Если изменить в нём нужные биты взвести\сбросить, то после этого обновить регистр флагов можно командой SAHF – Store.
 

CKAP

Green Team
10.06.2019
68
39
BIT
0
Такс.. тут уже практики не на один вечер. Ну если не тупо наспех повторить.
Вопрос. Какими справочниками пользоваться?
 

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 312
BIT
114
Какими справочниками пользоваться?
В скрепке есть описание инструкций,
ещё можно почитать Питер Абель - Ассемблер и программирование для IBM PC
этот его док считается классикой в этом жанре.
 

Вложения

  • Инструкции IA.zip
    19,9 КБ · Просмотры: 501
  • Нравится
Реакции: Mikl___ и CKAP

CKAP

Green Team
10.06.2019
68
39
BIT
0
Понято. Благодарю, вовремя. ВМ уже настроил пойду выполнять практику.
 
  • Нравится
Реакции: Marylin

CKAP

Green Team
10.06.2019
68
39
BIT
0
Угумс..
Если у кого кракозябры в дебаге то ..
31102
 

CKAP

Green Team
10.06.2019
68
39
BIT
0
Нашёл дополение к "скрепки". Основной мануал по Фасм.
 

Вложения

  • Основной Мануал к FASM.txt
    162,1 КБ · Просмотры: 706
  • Нравится
Реакции: Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 312
BIT
114
3.1.6. Управляющая структура типа CASE на ассемблере

Структура CASE проверяет значение некоторой переменной и передаёт управление на различные участки кода. Пусть переменная ‘a’ принимает значения от 0 до 2, в зависимости от которого надо выполнить процедуры case0, case1 или case2. На асме это звучит так:
C-подобный:
.code
start:  mov   eax,[a]     ; входное значение "a" из переменной
        cmp   eax,0       ; "a" равен нулю?
        jne   @01         ; если не равно, тогда проверка на 1
        call  case0       ; значит равно. выполняем процедуру "case0"
        jmp   @endcase    ; условие выполнено!
@01:    cmp   eax,1       ; проверка "a" на 1
        jne   @02
        call  case1
        jmp   @endcase
@02:    call  case2       ; процедура "case2"
@endcase:
        retn
Но это довольно тормознутый алгоритм, и напоминает наскальные рисунки недочеловеков. Ассемблер предоставляет более удобный способ описания таких структур – ‘Косвенный вызов с таблицей переходов’. Пусть мы имеем те-же условия, тогда код выше мутирует в такой вид:
C-подобный:
.data
jump_table    dd  @00          ;// таблица переходов с адресами процедур
              dd  @01
              dd  @02
;------
.code
start:  mov   eax,[a]          ; входное значение "a"
        shl   eax,2            ; EAX * 4 (размер адреса в таблице переходов)
        jmp   jump_table[eax]  ; выбираем адрес в таблице переходов

@00:    call  case0            ; зовём процедуру "case0"
        jmp   @endcase         ; условие выполнено!
@01:    call  case1
        jmp   @endcase
@02:    call  case2

@endcase:
        retn
Если переменных много, то способ с таблицей переходов гораздо быстрее (не требуется проверок), + при таком подходе, код получается значительно компактней. Рассмотрим ещё один пример, но уже с межблочным косвенным вызовом процедур. Данный тип структуры "CASE", так-же подразумевает создание таблицы, с базовыми адресами всех процедур:
C-подобный:
.data
;// таблица вызовов с базовыми адресами процедур
base_table  dd  case0
            dd  case1
            dd  case2
            ....
            dd  case_N
;----------
.code
;// косвенный вызов процедуры ========================
;// адрес таблицы передаётся в регистре ESI,
;// смещение процедуры от начала блока - в регистре EBX
    mov   ebx,[a]              ; EBX = входной параметр
    shl   ebx,2                ; умножаем его на 4 (размер адреса в таблице)
    mov   esi,base_table       ; ESI = база таблицы
    add   esi,ebx              ; добавляем к базе, смещение адреса процедуры
    lodsd                      ; читаем в EAX адрес процедуры из ESI
    call  eax                  ; косвенный вызыв процедуры!
    jmp   @endcase             ; условие выполнено
Косвенный вызов процедур и API-функций является отличным средством против дизассемблеров - они путаются в них не по-детски, не понимая, чего от них хотят. Кстати (насколько мне известно) си’шка не поддерживает такие финты, в чём и проигрывает ассемблеру.

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

C-подобный:
include 'win32ax.inc'
;-------
.data
text    db  'Hello World!',0
;-------
.code
start:
;// Ищем адрес MessageBox() в памяти
        invoke  LoadLibrary,<'User32.dll',0>            ; загружаем User32.dll в память
        invoke  GetProcAddress,eax,<'MessageBoxA',0>    ; получили адрес функции в ней,
        push    eax                                     ; запомнить его для вызова!
;// здесь какой-то код и позже..
        pop     eax                        ; EAX = адрес MessageBox()
        push    0 0 text 0                 ; засылаем в стек её аргументы
        call    eax                        ; косвенный вызов MessageBox() по адресу!
        invoke  ExitProcess,0
.end start
hiew_0.png


Если через GetProcAddress() получить адреса всех/используемых в нашем приложении функций и сохранить их в переменных, то получим фиктивную секцию-импорта, на изучении которой взломщику потребуется время. Кроме того, файлы формата РЕ поддерживают фишку под названием TLS-callback и альтернативные потоки, куда можно спрятать и засвеченные GetProcAddress() и LoadLibrary(). Вообщем вариантов хоть отбавляй, а который из них выбирать - зависит от нашего скила.
 
Последнее редактирование:

CKAP

Green Team
10.06.2019
68
39
BIT
0
> Другой вариант цикла – это когда мы знаем длину строки

А я чет не придумал полезной нагрузки
C-подобный:
.code
start:   sub     ecx,ecx        
           mov     ecx,45h
              
         ;------------------------------------------------------------
@cycle:   cmp     ecx,0    
               je      @ok            
              dec     ecx            
              jmp     @cycle         

@ok:     invoke  MessageBox,0,text,capt,0
             invoke  ExitProcess,0
;------
.end start

Ну речь короче шла о декременте. Ой.. фу как криво получилось.. мда
 

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 312
BIT
114
А я чет не придумал полезной нагрузки
..инструкции CMP и DEC - обе взводят флаг нуля ZF, поэтому можно применить только одну из них. По такому принципу можно посчитать, например, 'контрольную сумму' своей секции-кода. Тут мы знаем, что она занимает одну страницу памяти размером 4К-байт, а это 1000h байт.

Будем читать эту секцию по-байтно, по ходу суммируя их. Если потом сохранить полученную сумму где-нибудь в переменной и периодически сверять её с текущей, то можно узнать, модифицировал нашу секцию кто-нибудь, или нет. Вот пример:
C-подобный:
include 'win32ax.inc'
.data
capt    db  'Цикл',0
frmt    db  'CRC секции-кода = %04X',0
text    db  32 dup(0)
;---------
.code
start:  mov   ecx,0x001000         ; счётчик цикла (4Кб страница)
        mov   esi,0x402000         ; указатель на секцию-кода
        xor   eax,eax              ; обнуляем AX (будет сумма)
        xor   ebx,ebx              ; обнуляем BX (сюда будем читать)
@check: mov   bl,byte[esi]         ; берём в BL байт из ESI
        add   ax,bx                ; AX + BX (считаем сумму в АХ)
        inc   esi                  ; указатель +1
        dec   ecx                  ; ..счётчик -1
        jnz   @check               ; Jump No-Zero (проверяем флаг нуля ZF)

      cinvoke  wsprintf,text,frmt,eax      ; переводим EAX в строку,
       invoke  MessageBox,0,text,capt,0    ;  ..и выводим на экран.
       invoke  ExitProcess,0               ;
.end start
 
  • Нравится
Реакции: North Wind, Mikl___ и CKAP

CKAP

Green Team
10.06.2019
68
39
BIT
0
..инструкции CMP и DEC - обе взводят флаг нуля ZF, поэтому можно применить только одну из них.

Оооо.. я когда отладчиком прогонял не заметил.. хмм.. понято.

> В качестве демки напишем программу, которая выведет в окно Environment машины, на которую попала.

А у меня тут хлам.. причем всегда рандомный..
31322


хм.. мож чё в винде поломалось хз ...
до этого делал это

.code
start: mov ecx,69 ; ECX = длина строки для LOOP
mov esi,text ; ESI = адрес строки

@crypt: xor byte[esi],7Eh ; цикл! шифруем байт из ESI нашим ключом
inc esi ; сл.байт в строке..
loop @crypt ; повторить ECX-раз!

invoke MessageBox,0,text,capt,0
invoke ExitProcess,0
.end start


:)
 

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 312
BIT
114
А у меня тут хлам.. причем всегда рандомный..
интересно.. может винда не ХР, а семёрка?
проверить принадлежность региона памяти можно во-второй Оле,
при помощи MemoryMap (буттон М в окне).
у меня например ХР и видно, что по этому адресу лежит энвиронмент:

env.png
 
  • Нравится
Реакции: Mikl___ и CKAP

CKAP

Green Team
10.06.2019
68
39
BIT
0
интересно.. может винда не ХР, а семёрка?

Ну да.. я же ХРюшку снес :), да и была она на ВМ. А тут на физ семку поставил..
--------------------------------------------------------------------------------------------
Ооо.. ну завтра уже посмотрю адрес Environmenta..
зы. Кстати да.. на ХР работало.. я потом через облако перетащил на семку.. хм
 
Последнее редактирование:

CKAP

Green Team
10.06.2019
68
39
BIT
0
-----------------------------------------------------
Вот
31325


Вот что то похожее на 7FFDB000 и размер похож, но у меня пока не получилось туда добраться.
 
Последнее редактирование:
  • Нравится
Реакции: Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 312
BIT
114
но у меня пока не получилось туда добраться.
да, это и есть блок окружения процесса.
значит на семёрке он лежит по другому адресу - возьму на заметку.
а так - проблем с чтением возникнуть не должно,
т.к. область памяти 0x7FFDB000 имеет атрибут приватной с флагами R/W, т.е. принадлежит процессу.
 
  • Нравится
Реакции: Mikl___ и CKAP
Мы в соцсетях:

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