Whatis.Ru

Информация о компьютерах доступным языком
Назад на сайт

Вы не зашли.


#51 21-09-2015 03:41:44

Rumit
Мастер
Откуда: Из матки
Зарегистрирован: 18-01-2008
Сообщений: 992
Вебсайт

Re: Исходники на ассемблере

ПАРАМЕТРЫ КОМАНДНОЙ СТРОКИ

Как и любая ось, DOS занимается загрузкой и выполнением программы. При загрузке, в начале отводимого для неё блока памяти, создаётся структура данных "PSP" или "Префикс программного сегмента". Эта структура имеет размер 100h байт и таит в себе много интересного. Когда мы указываем компилятору ORG 100h, он зарезервирует место под этот "Префикс". Давайте посмотрим на него.. Запустив отладчик, открываем исполняемый файл и просматриваем память начиная с адреса нуль:

**************************************************************************************
GRDB version 1.7 Copyright (c) LADsoft

History enabled
eax:00000000  ebx:00000000  ecx:00000000  edx:00000000  esi:00000000  edi:00000000
ebp:00000000  esp:000BFFEE  eip:00000100  eflags:000B0202  NV UP EI PL NZ NA PO NC
ds: 0E8A  es:0E8A  fs:0E8A  gs:0E8A  ss:0E8A  cs:0E8A
0E8A:0100 E3 E7          jcxz        00E9

->L TESTFILE.COM
Size: 0000029D
->D  0
0E8A:0000  CD 20 FF 9F 20 9A F0 FE 1D F0 DE 01 15 04 4B 01  =  Я ЪЁ¦-Ё¦O§¦KO
0E8A:0010  15 04 56 01 15 04 15 00 01 01 01 00 02 FF FF FF  §¦VO§¦§ OOO O   
0E8A:0020  FF FF FF FF FF FF FF FF FF FF FF FF 39 0E 22 E8              9d*ш
0E8A:0030  91 36 14 00 18 00 8A 0E FF FF FF FF 00 00 8C 26  С6¶ ^ Кd      М&
0E8A:0040  05 00 89 36 15 00 E8 8B 33 E8 07 00 E8 93 33 F8  ¦ Й6§ шЛ3ш. шУ3°
0E8A:0050  CD 21 CB E8 D8 59 8C E0 E8 EB 59 B2 00 20 20 20  =!Tш+YМршыY-
0E8A:0060  20 20 20 20 20 20 20 20 59 E8 BA 59 00 20 20 20          Yш¦Y
0E8A:0070  20 20 20 20 20 20 20 20 E8 FF 02 C7 06 C0 00 00          ш O¦¦L
->D
0E8A:0080  00 0D 01 01 01 01 01 01 01 01 01 01 01 01 01 01   .OOOOOOOOOOOOOO
0E8A:0090  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00A0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00B0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00C0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00D0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00E0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00F0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
->
**************************************************************************************

Помимо прочего, в блоке 'PSP' имеется поле "Командной строки", которое начинается с адреса 80h и имеет длинну 128 байт. Здесь валяются параметры, которые мы передаём приложению при запуске (т.н. ключи). Например, ключ "/?" при запуске CMD.EXE покажет хэлп.

Поле 80h хранит длинну ком.строки, а с поля 81h - начинается сама/командная строка, которая заканчивается символом переноса 0Dh. Попробуем запустить приложение с каким-нибудь ключом.. Пусть это будет "/*SIM-SIM":

**************************************************************************************
GRDB version 1.7 Copyright (c) LADsoft

->L TESTFILE.COM /*SIM-SIM
Size: 0000029D
->D 80
0E8A:0080  0A 20 2F 2A 73 69 6D 2D 73 69 6D 0D 01 01 01 01  . /*sim-sim.OOOO
0E8A:0090  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00A0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00B0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00C0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00D0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00E0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
0E8A:00F0  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  OOOOOOOOOOOOOOOO
->
**************************************************************************************

Здесь видно, что наш ключ имеет длинну 0Ah байт (адрес 80h), начинается с пробела (адрес 81h, код 20h) и заканчивается адресом 8Bh (код 0Dh).

Так..так.. А что если для защиты нашего приложения, потребовать от юзверя ввести ключ? Мы прочитаем его, и расшифровав по своему алгоритму, сравним с ключом дешифратора. Это распространённый приём, но здесь нужно придерживаться определённых правил: не проверять начало пароля, а так-же его конец и ..середину! Можно/нужно проверять через один символ или вообще вразброс, по-ходу снимая с символов хэш. Этим мы вконец запутаем взломщика.

В общем, имеем мы стратегию, но как воплотить её в жизнь? У нас есть (как минимум) три значения, которые мы должны обработать: 0 - нет ключа, 1 - неправильный ключ, 2 - правильный ключ. Для решения этой задачи, как/нельзя лучше подходит структура типа "CASE", или по-другому "IF..THEN..ELSE". Рассмотрим её...


Болтовня ничего не стоит. Покажите мне код.. (Linus Torvalds)

http://ne-kurim.ru/ncounter/134735-4.png

Неактивен

#52 18-12-2015 17:58:31

Rumit
Мастер
Откуда: Из матки
Зарегистрирован: 18-01-2008
Сообщений: 992
Вебсайт

Re: Исходники на ассемблере

УПРАВЛЯЮЩАЯ  СТРУКТУРА  ТИПА "CASE"

Структура CASE проверяет значение некоторой переменной и передаёт управление на различные участки кода. Пусть переменная X принимает значения от 0 до 2, в зависимости от которого надо выполнить процедуры case0, casel или case2. На ассме это "звучит" так:

   MOV   AX,X           ; входное значение "Х"
   CMP   AX,0           ; "Х" равен нулю?
   JNE   @1             ; если не равно, тогда проверка на 1
   CALL  case0          ; значит равно. выполняем процедуру "case0"
   JMP   endcase        ; условие выполнено!
@1:
   CMP   AX,1           ; проверка "Х" на 1
   JNE   @2
   CALL  case1
   JMP   endcase
@2:
   CALL  case2          ; процедура "case2"
endcase:
   RET

Но это довольно тормознутый алгоритм.. Ассемблер предоставляет более удобный способ описания таких структур - "Косвенный вызов с таблицей переходов". Пусть мы имеем те-же условия, тогда код выше мутирует и принимает такой вид:

;==============================================================================
;//    Таблица переходов:
;//         jump_table  DW  @0, @1, @2
;//
;//    ..что равносильно:
;==============================================================================

jump_table  DW  @0          ; таблица переходов с адресами процедур
            DW  @1
            DW  @2
           
   MOV   AX,X    ;<=========; принимаем значение
   SHL   AX,1               ; AХ * 2 (размер адреса в таблице переходов. 4 для EAX)
   JMP   jump_table[AX]     ; выбираем адрес в таблице переходов

@0:              ;<=========; адресс начала процедуры "case0"
   CALL  case0              ; зовём процедуру "case0"
   JMP   endcase            ; условие выполнено!
@1:   
   CALL  case1
   JMP   endcase
@2:   
   CALL  case2
endcase:
   RET   

Если переменных много, то способ с таблицей переходов гораздо быстрее (не требуется проверок), + при таком подходе, код получается значительно компактней.

Рассмотрим ещё один пример, но уже с межблочным/косвенным вызовом процедур. Данный тип структуры "CASE", так-же подразумевает создание таблицы с базовыми адресами всех процедур:

; таблица вызовов с базовыми адресами процедур
base_table  DW  Case_0
            DW  Case_1
            DW  Case_2
            ....
            DW  Case_N

; косвенный вызов процедуры "Case_2" ========================================//
; адрес таблицы передаётся в регистре SI,
; смещение процедуры от начала блока - в регистре BX

   MOV   BX,2                  ; BX = входной параметр
   SHL   BX,1                  ; умножаем его на 2 (длинна адреса в таблице)
   MOV   SI,base_table         ; SI = начальный адрес таблицы
   ADD   SI,BX                 ; добавляем к адресу, смещение процедуры
   LODSW                       ; читаем в AX адрес процедуры из SI
   CALL  AX                    ; вызываем процедуру
   JMP   EndCase               ; условие выполнено!

; процедура CASE_2 ==========================================================//
; выводит на экран ключ ком.строки из блока "PSP"

Case_2:
   MOV   SI,81h                ; SI = адрес ком.строки
Print:
   LODSB                       ; берём в AL символ из SI
   CMP   AL,0Dh                ; это последний символ ком.строки?
   JE    EndPrint              ; да - на выход!
   INT   29h                   ; значит нет. выводим символ на экран
   JMP   Print                 ; берём следующий символ..
EndPrint:        ;<============; ком.строка закончилась,
   RET                         ;    ..выходим из процедуры.
   
Чуть позже мы затронем тему "Динамического шифрования данных", в которой будем активно использовать структуры "CASE", поэтому нужно всерьёз настроить свои извилины на эти алгоритмы. А пока, давайте разберёмся ещё с одним направлением - работой со-строками..


Болтовня ничего не стоит. Покажите мне код.. (Linus Torvalds)

http://ne-kurim.ru/ncounter/134735-4.png

Неактивен

#53 18-12-2015 17:59:28

Rumit
Мастер
Откуда: Из матки
Зарегистрирован: 18-01-2008
Сообщений: 992
Вебсайт

Re: Исходники на ассемблере

КОМАНДЫ ДЛЯ РАБОТЫ СО-СТРОКАМИ

Строкой в ассемблере называется последовательность байт, которая заканчивается знаком доллара ,'$' (ASCII), или нулевым байтом ,0 (ASCIZ). Элементы такого формата известны как строковые данные. В природе существует 5 команд, облегчающих манипуляции со строкам:

   STOSB  - запись из AL в DI;
   LODSB  - чтение в AL из SI;
   MOVSB  - копирование из SI в DI;
   CMPSB  - сравнивает байт DI, с байтом SI (сравнивает строки);
   SCASB  - сравнивает байт AL, с байтом DI (поиск символа в строке).

Для организации циклов применяется префикс REP. Как и в случае с командой LOOP, счётчиком служит регистр CX, который при каждом цикле уменьшается на 1. При выполнении команд CMPSB/SCASB можно манипулировать флагами так, чтобы операция могла прекратиться сразу после обнаружения необходимого условия. Ниже приведены модификации префикса "REP" для этих целей. REP применяется для STOSB/LODSB/MOVSB, а REPE применяется для CMPSB/SCASB:

REP         -   продолжать, пока CX > 0;
REPE/REPZ   -   прекратить, при не совпадении (сравнение строк);
REPNE/REPNZ -   прекратить, при совпадении (поиск символа в строке).

********************************************************************************

Знакомство со-строковыми операциями мы начнём с команды пересылки строк: MOVSB
Здесь нет ничего сверх/естесственного: указываем источник с приёмником, и вперёд..
На что нужно обратить внимание, так это на спец.символы $ и $$.
Знак $ вычисляет адрес текущей позиции, т.е. мы можем создать бесконечный цикл командой JMP $. Другим/полезным применением этого спец.символа является вычисление длинны строки, как на примере ниже:

0B18:0100  string  DB  'Вычисляем длинну строки..'
0B18:0119  dlina   =   $ - string

Здесь, переменная "string" находится по адресу 100, а константа "dlina" займёт адрес, в аккурат равный адресу "string" плюс кол-во символов в строке (19h). Теперь, если мы укажем в коде "Адрес текущей позиции, минус адрес переменной STRING", то получим длинну строки.

Соответственно, знак $$ определяет адрес начала текущего сегмента.
Мы можем узнать, как далеко находимся от начала сегмента выражением: $-$$

Теперь, мы готовы для копирования строки командой MOVSB:

string_1   DB  'Хороша была Танюша, краше не было в селе!',0
length     =   $ - string_1        ; длинна строки "string_1"
string_2   DB  50 DUP(' ')         ; место для приёма строки, забитое пробелами

start:
   MOV   CX,length                 ; CX = количество повторов
   MOV   SI,string_1               ; SI = адрес строки-источника
   MOV   DI,string_2               ; DI = адрес приёмника
   REP   MOVSB                     ; копируем по-байтно
   ....

Со-сравнием - дела обстоят чуть-сложней..
Рассмотрим процесс сравнения двух строк: '12345' и '12456' командой CMPSB.
т.к. строки "не равны", операция должна прекратиться, как только будет обнаружено условие "не равно". Для этих целей юзается команда REPE, которая повторяет сравнение, пока сравниваемые элементы равны. Не отрывая взгляда от флагов, оттрасируем этот код в отладчике:

*****************************************************************************
E:\ Temp > DEBUG
-A
0B25:0100  DB '12345'           ; тестируемая строка(1)
0B25:0105  DB '12456'           ; тестирующая строка(2)
0B25:010A  MOV SI,100           ; SI = адрес строки(1)
0B25:010D  MOV DI,105           ; DI = адрес строки(2)
0B25:0110  MOV CX,5             ; CX = длинна строки
0B25:0113  REPE CMPSB           ; сканим до первого несовпадения
0B25:0115  RET                  ; на выход!
0B25:0116
-T
AX=3204  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=20CD  DI=0000
DS=0B25  ES=0B25  SS=0B25  CS=0B25  IP=0109   NV UP EI PL NZ NA PO NC
0B25:0109 36            SS:
0B25:010A BE0001        MOV     SI,0100
-T
AX=3204  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0100  DI=0000
DS=0B25  ES=0B25  SS=0B25  CS=0B25  IP=010D   NV UP EI PL NZ NA PO NC
0B25:010D BF0501        MOV     DI,0105
-T
AX=3204  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0100  DI=0105
DS=0B25  ES=0B25  SS=0B25  CS=0B25  IP=0110   NV UP EI PL NZ NA PO NC
0B25:0110 B90500        MOV     CX,0005
-T
AX=3204  BX=0000  CX=0005  DX=0000  SP=FFEE  BP=0000  SI=0100  DI=0105
DS=0B25  ES=0B25  SS=0B25  CS=0B25  IP=0113   NV UP EI PL NZ NA PO NC
0B25:0113 F3            REPZ        ; начало сканирования...
0B25:0114 A6            CMPSB
-T
AX=3204  BX=0000  CX=0004  DX=0000  SP=FFEE  BP=0000  SI=0101  DI=0106
DS=0B25  ES=0B25  SS=0B25  CS=0B25  IP=0113   NV UP EI PL NZ NA PO NC
0B25:0113 F3            REPZ        ; символ(1) совпал! (флаги на месте)
0B25:0114 A6            CMPSB
-T
AX=3204  BX=0000  CX=0003  DX=0000  SP=FFEE  BP=0000  SI=0102  DI=0107
DS=0B25  ES=0B25  SS=0B25  CS=0B25  IP=0113   NV UP EI PL NZ NA PO NC
0B25:0113 F3            REPZ        ; символ(2) совпал!
0B25:0114 A6            CMPSB
-T
AX=3204  BX=0000  CX=0002  DX=0000  SP=FFEE  BP=0000  SI=0103  DI=0108
DS=0B25  ES=0B25  SS=0B25  CS=0B25  IP=0115   NV UP EI NG NZ AC PE CY
0B25:0115 C3            RET         ; оп-па! флаги изменились. символ(3) - утка!
-
*****************************************************************************

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

   MOV   CX,5               ; длинна строки в байтах
   REPE  CMPSB              ; сравниваем байт DI c байтом SI
   OR    CX,CX              ; проверка CX на нуль (можно CMP CX,0)
   JZ    ok                 ; тест флага ZF на нуль..
   Message  ERROR           ; мессага ERROR!
   RET
ok: 
   Message  OK              ; мессага ОК!
   ....

Аналогично работает и команда SCASB. Она полезна при поиске определённых символов или знаков пунктуаций в строке. Давайте найдём символ "S" в строке "WHATIS.RU".. Т.к. команда "SCASB" должна продолжать сканирование пока результат сравнения "не равно", то используется префикс "REPNE". Обратите внимание, что длинна строки вычисляется другим способом: 

text   DB  'WHATIS.RU',0        ; ASCIZ-строка для поиска

start:             ;<===========; сначала вычисляем длинну строки..
   MOV   CX,0FFFFh              ; ставим счётчик на максимум
   XOR   AL,AL                  ; AL = 0 (искать будем нуль)
   MOV   DI,text                ; DI = адрес строки для поиска
   REPNE SCASB                  ; начинаем сканирование...
   NOT   CX                     ; инвертируем счётчик. CX = длинна строки

   MOV   DI,text                ; DI = адрес строки
   MOV   AL,'S'                 ; AL = символ для поиска
   REPNE SCASB                  ; начинаем сканирование...
   ...

Результатом работы данного участка кода, будет содержимое регистров CX и DI. Регистр "CX" указывает на порядковый номер найденного символа, а регистр "DI" - на сам символ. Если в строке нет такого символа, то цикл отработает до-конца и "CX" будет нуль.

При детальном рассмотрении команд работы со-строками, в глаза бросается закономерность:

   REP   - применяется для MOVSB (копирование строки)
   REPE  - применяется для CMPSB (сравнение строк)
   REPNE - применяется для SCASB (поиск символа в строке)

Вот вроде и всё, что касается команд работы со-строками.
На первый взгляд полезного выхлопа в них мало, но порой они сильно облегчают жизнь.

В заключении, вспомним "Таблицу условных переходов", где литер N втиснутый по-середине, просто обращает условие на противоположное. Литер E в конце, уточняет условие и означает равно (например, JBE - это "ниже или равно", а JNG - "не больше"):

  ================  П Е Р Е Х О Д Ы ====================
+----------------+-----------------+-------------------+
|   Символьный   |    Знаковый     |    По-флагам      |
+----------------+-----------------+-------------------+
|   JB - ниже    |   JL - меньше   |   JS - знак(-)    |
|   JE - равно   |   JZ - ноль     |   JC - перенос    |
|   JA - выше    |   JG - больше   |   JP - чётный     |
+----------------+-----------------+-------------------+


Болтовня ничего не стоит. Покажите мне код.. (Linus Torvalds)

http://ne-kurim.ru/ncounter/134735-4.png

Неактивен


Board footer

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson
Рейтинг@Mail.ru