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

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

    Скидки до 10%

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

Статья ASM – техники из старого сундука

Есть поговорка: "Программистами не рождаются – ими умирают". Исходя из этой парадигмы можно сделать вывод, что за долгий свой/программерский путь, у каждого из нас снежным комом накапливаются различные техники и алгоритмы, поделиться с общественностью которыми было-бы не грех. К примеру на этапе моего начинания, мир процессора для меня был так юн, что некоторые вещи не приобрели ещё свои имена и приходилось указывать на них пальцем. Позже, все просветления было решено записывать в блокнот, который постепенно разбух до увесистого романа. Фильтр элементарных заметок сделал своё дело и на данный момент в этом блокноте осталось ~1000 строк, которые я и решил сбросить сюда с краткими комментариями. Надеюсь у топика найдутся свои "евангелисты" и кто-нибудь почерпнёт с него что-то полезное.
-------------------------------------------

1. Логические операции на уровне битов

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


1.0 Инструкции SHR и ROR – циклический сдвиг и ротация

Для начала рассмотрим магию сдвиговых инструкций SHR и SHL (Shift to Right/Left, сдвиги вправо/влево соответственно). Операндами этих инструкций являются регистр-приёмник и счётчик сдвигаемых бит. Так, инструкция SHL EAX,5 сместит все биты регистра EAX на пять позиций влево. К этим инструкциям присовокупляются и кольцевые их варианты ROR/ROL (Rotate Right/Left), которые при сдвиге, например, вправо на 1-бит, устанавливают выдвинутый справа бит, старшим слева. То-есть получаем сдвиг (ротацию) без потери выдвинутых, в то время как при обычных циклических сдвигах, выдвинутые биты улетают безвозвратно в космос. Взяв за основу ротацию, можно организовать шифрование данных, т.к. информационная ценность при этом сохраняется.

Фишка в том, что сдвиг на 1-позицию влево равносилен умножению числа на 2; соответственно сдвиг на 2-позиции умножает число уже на 4, и т.д. Таким образом, если нам нужно быстро умножить содержимое регистра на степень(2), то можно применять сдвиги. Запустите виндовый калькулятор в режиме "Программист" и сравните полученные числа:


Код:
;// Умножение числа(1) на степень(2) сдвигом влево
-----------+-------------+------------------------
Сдвиг на.. |  Десятичное | Состояние бит в слове
-----------+-------------+------------------------
  0            1           0000000000000001       
  1            2           0000000000000010
  2            4           0000000000000100
  3            8           0000000000001000
  4            16          0000000000010000
  5            32          0000000000100000
  6            64          0000000001000000
  7            128         0000000010000000
  8            256         0000000100000000
  9            512         0000001000000000
  10           1024        0000010000000000
  11           2048        0000100000000000
  12           4096        0001000000000000
  13           8192        0010000000000000
  14           16384       0100000000000000
  15           32768       1000000000000000

Это-же касается и деления числа на степень(2), только сдвигать нужно не влево, а вправо. Например, что-бы моментально перевести некоторое значение из байтов в килобайты, можно применить инструкцию SHR EAX,10. При этом даже древний процессор AMD-K7 (Pentium-4) потратит на эту операцию всего 1/3 тактов, не в пример к операции обычного деления DIV аж 23-такта (см.растактовку Агнера Фога). Правда у этого способа есть один минус – доступно только целочисленное деление, без остатка.


1.1 Связка SHR и RCL – зеркальное отражение бит

Интересный эффект можно получить контролируя выдвигаемые инструкцией SHR биты, через флаг(CF) процессора – CarryFlag, флаг переноса битов. Есть несколько инструкций, которые используют этот флаг для своих нужд – одной из них является RCL. Она снимает значение флага(CF) в регистр и сдвигает этот регистр на указанное кол-во бит влево или вправо. Если выдвигая младшие биты вправо устанавливать их старшими слева, то в регистре-приёмнике получим зеркальное отражение оригинальных бит, как на схеме ниже. Область применения – шифрование, без потери информационной ценности (т.е. кол-во разрядов в числе остаётся прежним, только меняется их позиция). Повторив эту операцию ещё раз, можно восстановить оригинальные данные:

Код:
   Было: 0x1234           Стало: 0x2c48
--------------------|---------------------
00010010.00110100 <---> 00101100.01001000

Вот как можно организовать подобное зеркало:

C-подобный:
.code
start:  mov    ebx,12345678h    ;// исходное число в регистре
        xor    eax,eax          ;// здесь будет результат
        mov    ecx,32           ;// кол-во разрядов в числе (длина цикла)
@@:     shr    ebx,1            ;// выдвинуть из EBX бит вправо - он попадает во-флаг(CF)
        rcl    eax,1            ;// снять флаг(CF) в EAX, и сдвинуть EAX влево
        loop   @b               ;// повторить цикл ECX-раз..
;//---------------
;// в регистре EAX лежит зеркало от значения 12345678h = 1E6A2C48h

Что-то подобное делает и инструкция BSWAP, только она переставляет местами не биты 4-байтного (32-битного) числа, а целиком его байты. Например если в регистре EAX было значение 12345678h, то после инструкции BSWAP EAX получим 78563412h. Как видим, рассеиваемость бит по сравнению с оригиналом оставляет желать лучшего, хотя вариант тоже имеет право на жизнь. По-сути, эта фурия меняет порядок байт в памяти от "Big-Endian" на "Litle-Endian", чего требует архитектура некоторых процессоров.


1.2 Связка SHR и флаг(CF) – преобразование в BIN

По непонятной причине, в системе нет ни одной API-функции, которая могла-бы преобразовать число, в строку двоичного вида. Например, тот-же wsprintf() из Kernel32.dll не имеет спецификатора для BIN, а только OCT, DEC, HEX. В общем случае, в природе для группы функций printf() предусмотрены следующие спецификаторы, которые мы должны вскормить им в первом аргументе:


C-подобный:
  %f - плавающая точка fpu,
  %u - десятичное беззнаковое целое DEC,
  %d - десятичное знаковое целое DEC,
  %o - восьмеричное представление OCT,
  %x - шестнадцатеричное представление HEX,
  %p – выводит указатель,
  %c - символьное представление,
  %s - строка,
  %I64x - 64 битное HEX-число.
  %I64u - 64 битное DEC-число.

Для перевода числа в бинарную строку программисты изворачиваются кто-как может, а лично я предпочитаю по-битное выталкивание разрядов числа, с последующим преобразованием их в символы. Если заглянуть в ASCII-таблицу, то в ней наблюдается закономерность, что числа в диапазоне 0..9 отличаются от символьного своего представления ровно на 30h. То-есть числу(1) соответствует код 31h, двойке – 32h, девятке – 39h. Теперь, при помощи SHR выталкивая биты вправо, можно следить за состоянием флага(CF) и прибавив к нему 30h записывать в буфер, для последующего вывода в форточку, или на консоль. Вот пример:

C-подобный:
.data
buff   db  0    ;// приёмный буфер, до конца 4К-байтной секции-данных.
                ;// если обозначить его в самом хвосте данных,
                ;// то это предотвратит его переполнение.
.code
start:
       mov    ebx,12345678h      ;// число для преобразования в строку BIN
       mov    ecx,32             ;// кол-во разрядов в числе (32-бит), и длина цикла
       mov    edi,buff           ;// указатель на приёмник для STOSB
       clc                       ;// очистить флаг(CF) – Clear CF
@bin2str:                        ;// начало цикла..
       mov    al,30h             ;// в дефолте сохранять в буфере символ нуль
       shr    ebx,1              ;// выдвинуть из числа бит вправо (он запоминается во-флаге CF)
       jnc    @skip              ;// если CF=0 (Jump No Carry) – выдвинулся бит со-значением нуль
       inc    al                 ;// иначе: AL+1 и получили символ "1" = 31h
@skip: stosb                     ;// сохранить его в EDI, и сместить EDI на 1
       loop   @bin2str           ;// промотать цикл ECX-раз..

      cinvoke  printf,<'%s',0>,buff  ;// вывод буфера на консоль, в виде строки "%s"

Если к этому коду прицепить вложенный цикл на 4 или 8 итераций, то можно организовать форматированный бинарный вывод, вставляя после каждой тетрады/байта точку или пробел. Это на порядок повысит читабельность бинарной строки.


1.3 Инструкция AND (лог.умножение(И), конъюнкция)

Остаток от деления любого числа на степень(2) можно получить при помощи логической инструкции обнуления битов AND. Второй операнд этой инструкции представляет собой двоичную маску, которая указывает, какие именно биты числа мы хотим сбросить. Обнуляются только те биты исходного числа, которые в маске имеют значение нуль.

Допустим имеем в регистре число 18'500 и нужно узнать остаток от деления его на 128.
Для этого, отнимаем от 128 единицу и получаем число 127, двоичное представление которого состоит из шести последовательных единиц: 01111111 = 127. Теперь применяем эту маску к регистру, в результате чего в нём сбросятся все биты, кроме младших шести: AND EAX,127. Вот как это выглядит в двоичном виде.. Кстати, не нужно путать остаток, с дробной частью (которую показывает калькулятор). Чтобы в регистрах получить дробную часть, нужно использовать сопр FPU:

C-подобный:
;// Вычисление остатка от 18500/128 логикой
;//----------------------------------------
EAX = число..:  01001000.01000100 = 18500
AND = маска..:  00000000.01111111 = 127
----------------------------------------------------
Остаток в EAX:  00000000.01000100 = 68


;// Вычисление остатка от 385/256 логикой
;//----------------------------------------
EAX = число..:  00000001.10000001 = 385
AND = маска..:  00000000.11111111 = 255
----------------------------------------------------
Остаток в EAX:  00000000.10000001 = 129

Другое применение инструкции AND – это выравнивание адреса на границу степени(2).
Например, если в регистре EBX лежит некоторый указатель, то инструкция AND EBX,1000h позволит установить его на начало 4-Кбайтной страницы вирт.памяти. В данном случае, эта инструкция сбросит 12-младших бит адреса (три тетрады нулей), а поскольку 12^2=4096, получаем размер страницы 4К. Старшие биты могут быть любыми и выбирают номер конкретной страницы из всего пула, а младшие – смещают указатель на начало, внутри страницы(N).

Выравнивание особенно актуально для регистра-указателя на стек ESP. Если в управляющем регистре процессора CR0 взведён бит(АМ) и в регистре флагов EFLAGS выставлен флаг(АС) Alignment Control, то любое обращение к стеку по нечётным адресам будет возбуждать исключение #AC. Кроме того и функция выделения памяти VirtualAlloc() требует выравнивания адреса на 4К-страницу. Здесь и пригодится инструкция AND, которая за треть такта решит проблему.


2. Баг с флагом ZF – определение вендора CPU

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

Более того, человеку присущи ошибки и поскольку разработки ведутся независимо, то соответственно и баги в железяках у них свои. На запрос "Intel CPU bugs" можно выловить в гугле длинную портянку характерных конкретному производителю ошибок, от чего в последующем и плясать.

Одной из таких принципиальных отличий процессоров Intel от AMD является отношение инженеров к флагу(ZF) процессора, который информирует нас о нулевом состоянии какого-либо регистра – Zero Flag. К примеру, если взвести его на процессорах Intel и следом выполнить операцию деления DIV, то на выходе из операции Intel сбрасывает флаг(ZF) в нуль, а вот инженеры AMD посчитали иначе и в данном случае не трогают его – он как был взведённым до операции, так и останется после.

Если по существу, то продуманы из AMD на мой взгляд здесь правы, поскольку оперируют этим флагом только тогда, когда мы требуем этого явно, ..например проверяем конкретный регистр на нуль инструкцией OR EAX,EAX. Тут мы сами говорим процессору, мол "взведи флаг-нуля, если в регистре EAX лежит нуль". Однако непонятно, чем руководствуются инженеры Intel сбрасывая ZF после операции деления? Ведь мы не знаем, какой именно регистр обнулился, хотя флаг ZF об этом сигнализирует.

Таким образом имеем сигнатурный штамм, по которому можем вычислить производителя. В примере ниже мы явно взводим флаг(ZF) при обнулении регистра EDX инструкцией XOR EDX,EDX, поэтому оба вендора его исправно устанавливают в единицу. Зато после деления – AMD его игнорирует, а Intel сбрасывает, на чём и палится:

C-подобный:
.data
intel   db  'Intel',0
amd     db  'AMD',0

.code
start:  xor    edx,edx     ;// обнуляем EDX, устанавливая флаг ZF=1
        mov    eax,5       ;// EDX:EAX = делимое
        mov    ebx,2       ;// EBX = делитель
        div    ebx         ;// произвести операцию деления EAX на EBX !!!

        mov    eax,intel   ;// в дефолте выводим INTEL
        jz     @prn        ;// пропустить, если флаг ZF=0 сброшен (Jump if Zero)
        mov    eax,amd     ;// иначе ZF=1 остался взведённым, и поменять мессагу на AMD.
@prn:
      cinvoke  printf,eax  ;// вывод сообщения на консоль!

Способ проверенный и работает на всех современных процессорах, расходуя всего с десяток тактов CPU. Можно сравнить его со-специально предназначенной для идентификации профессоров инструкцией CPUID, которая растягивается на несколько тысяч тактов, хотя и возвращает намного больше конфиденциальной инфы о своей тушке. Так-что область применения глюка строго ограничена тупой проверкой вендора.


3. Как получить массив случайных чисел "RANDOM"

Перед каждым программером рано или поздно встаёт вопрос генерации случайных чисел, что на буржуйский манер звучит как Random. У мастдая с этим проблемы, и всё-что может предложить нам система – это сишную библиотеку времени исполнения msvcrt.dll (C-Run-Time library, или CRT), внутри которой имеется функция rand(). Она возвращает всего-лишь 16-битное случайное значение в пределах 32767, и то с очень плохим рассеиванием бит, поэтому никак не годится для криптографии.

Правда современные процессоры поддерживают уже инструкцию ассемблера RDRAND (опкод 0FC7h), которая организована на уровне микро/архитектуры. Но и в этом случае макс.размер псевдослучайного числа не превышает 8-байт, или 64-бит.

Однако мало кто знает, что в Windows имеется своя крипто-система, сосредоточенная в библиотеках Advapi32.dll и Crypt32.dll. Этот мощный механизм известен под общим названием Crypto-API и позволяет генерировать случайные числа без приставки "псевдо", т.к. заточен специально под шифрование. Можно получить криптографически стойкие ключи буквально неограниченного размера, а источниками для энтропии являются дребезжание контактов физической аппаратуры, или-же частотный анализ системы питания. Рассмотрим этот механизм в двух словах..

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


C-подобный:
BOOL CryptAcquireContextA()
   phProv         dd 0   ;// указатель на переменную, под дескриптор поставщика
   szContainer    dd 0   ;// указатель на строку с именем контейнера ключей – ставим нуль
   szProvider     dd 0   ;// указатель на строку с именем поставщика – ставим нуль
   dwProvType     dd 0   ;// константа с типом поставщика (провайдера)
   dwFlags        dd 0   ;// флаг – ставим CRYPT_VERIFY_CONTEXT = 0xF0000000

На рисунке ниже представлена общая схема крипто-системы Win, с краткой информацией о провайдерах. Надеюсь она прояснит ситуацию:

cryptoAPI.png


После того-как поставщик выбран (в примере я воспользуюсь услугами первого PROV_RSA_FULL=1), мы запрашиваем у него ключ функцией CryptGenRandom(), не забыв при этом указать необходимую длину ключа. Из таблицы выше видно, что мой провайдер с константой(1) генерит и шифрует ключ по алгоритму RC2 или 4, после чего применяет к нему хеш SHA или MD5 (на своё усмотрение). По окончании, нужно обязательно оповестить поставщика, что мы не нуждаемся более в его услугах. Закрывает открытый дескриптор функция CryptReleaseContext().

Таким образом в буфере получим Random требуемой длины, и теперь нам нужно как-минимум вывести его на консоль. Значит задача – число неизвестной длины перевести в строку соответствующего вида. Для этого, выталкивая при помощи SHR из каждого байта ключа по 4-бита, будем использовать эту тетраду как индекс в представляющей HEX-числа символьной строке "0123456789ABCDEF". Макс.значение, которое можно закодировать в четырёх битах как-раз F=16=1111b, поэтому проблем не будет. Вот пример реализации рандома, с размером ключа 128 байт х8 = 1024 бит. Зачем он такой длинный нужен? Да был-бы рандом, а применение ему обязательно найдётся:


C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//--------
.data
PROV_RSA_FULL      = 1           ;// код провайдера
CRYPT_VER_CONTEXT  = 0xF0000000  ;// его флаги

title    db  'RANDOM Gen v.0.1',0
capt     db  10
         db  10,' 128-byte RANDOM value'
         db  10,'=======================',10,0

ascii    db  '0123456789ABCDEF'  ;// шаблон перевода в HEX
keyLen   =   128                 ;// длина RANDOM ключа
handle   dd  0                   ;// под хэндл провайдера
counter  db  3                   ;// счётчик вывода на консоль
buff     db  0                   ;// буфер для HEX-ASCII строки

;//--------
.code
start:    invoke  SetConsoleTitle,title

;//------ Выбираем поставщика из Advapi32.dll
          invoke  CryptAcquireContext,handle,0,0,PROV_RSA_FULL,CRYPT_VER_CONTEXT

;//------ Цикл генерации случайных ключей
;// в функцию CryptGenRandom() передаём длину ключа, и адрес приёмного буфера
@getRnd:
         cinvoke  printf,capt
          invoke  CryptGenRandom,[handle],keyLen,buff

;//------ Процедура вывода ключа на консоль
;// переводит из HEX в ASCII таблично-индексным методом
;// преобразование идёт в обратном порядке, от хвоста к голове
          mov     esi,keyLen         ;// ESI = индекс хвоста источника
          mov     edi,keyLen *2      ;// EDI = хвост приёмника
          mov     [buff + edi],0     ;//  ..(вставить туда терминальный нуль)
          xor     eax,eax            ;// EAX = 0
@bin2hex: mov     al,[buff +esi -1]  ;// AL = очередной байт
          shr     al,4               ;// оставить ст.тетраду
          mov     cl,[ascii +eax]    ;// взять по ней символ из таблицы
          mov     [buff +edi -1],cl  ;// запомнить символ в EDI
          dec     edi                ;// указатель влево..
          mov     al,[buff +esi -1]  ;// взять тот-же байт
          shl     al,4               ;// оставить мл.тетраду
          shr     al,4               ;//  ...^^^
          mov     cl,[ascii +eax]    ;// взять по ней символ из таблицы
          mov     [buff +edi -1],cl  ;// запомнить символ в EDI
          dec     edi                ;// сместить указатели влево..
          dec     esi                ;//  ...^^^
          jnz     @bin2hex           ;// повторить по всему буферу, пока ESI > 0

;//------ Вывод на консоль и пауза 2-сек.
         cinvoke  printf,buff
          invoke  Sleep,2000

          dec     [counter]          ;// уменьшить счётчик повторов RANDOM
          jnz     @getRnd            ;// повторить пока счётчик > 0.

;//------ Освобождаем поставщика по его хэндлу.
          invoke  CryptReleaseContext,[handle],0
         cinvoke  gets,buff
         cinvoke  exit, 0
;//-----------
section '.idata' import data readable
library  msvcrt,  'msvcrt.dll',\
         advapi32,'advapi32.dll',\
         kernel32,'kernel32.dll'
import   msvcrt, printf,'printf',gets,'gets',exit,'exit'
include 'api\advapi32.inc'
include 'api\kernel32.inc'

rand.png


На сайте мелкомягких лежит утверждение, что мол функция CryptGenRandom() уже устарела и не следует использовать её в своих программах. Однако она прекрасно отрабатывает не только на моей Win-7, но даже и на последней десятке Win-10. Поэтому думаю рановато сбрасывать её со-счётов.

Попробуйте изменить длину ключа в константе "keyLen", и получите рандомное значение исполинских размеров, например 2048-байт и больше. Проецируя такой ключ на массив данных, можно организовать блочное шифрование по типу бородатого алгоритма Виженера.


Послесловие...

Что-то букаф много стало для одной статьи, поэтому по мере возможности буду расширять её позже. Если у кого-то имеются интересные на их взгляд алгоритмы и техники, то добро пожаловать под капот. Не обязательно, чтобы это был ассемблер – можно на сишке, или других языках. Лишь-бы с краткими пояснениями сути дела, а дальше реализовать можно на любом сленге. А пока закругляюсь и до скорого..
 

DragonFly

Green Team
01.06.2020
221
65
BIT
3
Допустим имеем в регистре число 18'500 и нужно узнать остаток от деления его на 128.
Для этого, отнимаем от 128 единицу и получаем число 127, двоичное представление которого состоит из шести последовательных единиц: 01111111 = 127. Теперь применяем эту маску к регистру, в результате чего в нём сбросятся все биты, кроме младших шести: AND EAX,127. Вот как это выглядит в двоичном виде.. Кстати, не нужно путать остаток, с дробной частью (которую показывает калькулятор). Чтобы в регистрах получить дробную часть, нужно использовать сопр FPU:
Просто гениально ,завтра возьму ручку и бумагу и буду подробнее разбираться, чтобы эта логика и в голове осталась и блокноте, которы эй может толстым тоже станет :) буду завтра дочитывать! Спасибо 🙏 за то что решил поделиться записями из сундука !
 
  • Нравится
Реакции: Avik и Marylin

Mikl___

Green Team
01.09.2019
25
41
BIT
18
Просто гениально ,завтра возьму ручку и бумагу и буду подробнее разбираться, чтобы эта логика и в голове осталась и блокноте, которы эй может толстым тоже станет

DragonFly,​

могу посоветовать книгу Генри С. Уоррена (Henry S.Warren) "Алгоритмические трюки для программистов"
 
  • Нравится
Реакции: DragonFly и Marylin

Mikl___

Green Team
01.09.2019
25
41
BIT
18
Папа у Васи силен в математике,
Учится папа за Васю весь год.
Где это видано, где это слыхано, –
Папа решает, а Вася сдает?!

(© Драгунский В.Ю. "Где это видано, где это слыхано")
 

Muxtar

Green Team
02.06.2021
132
64
BIT
0
@Marylin, можно спросить, а сложно ли учить Assembler начинающему? Даже если у него нет опыта C/C++?
 
  • Нравится
Реакции: To4ka72

Mikl___

Green Team
01.09.2019
25
41
BIT
18
Операндами этих инструкций являются регистр-приёмник и счётчик сдвигаемых бит
Тимур, операндом инструкций сдвига вполне может быть ячейка памяти :)
SHL/SHR/ROR/ROL/RCL/RCR/SAR/SAL op1,op2
op1 - r/m8/m16/m32/m64
op2 - imm8/cl
Кодировка арифметических, логических и циклических сдвигов имеет следующий формат:
1​
1​
0​
C​
0​
0​
V​
B/W​
MOD​
КОП​
R/L​
R/M​
  • бит С количество сдвигов, C=0 – количество сдвигов задается в команде числом imm8, C=1 – количество сдвигов или равно 1, или задается регистром CL.
  • бит B/W размер операнда, B/W=0 – байт, B/W=1 – одинарное/двойное/учетверенное слово
  • бит V=0 – количество сдвигов равно 1, V=1 – количество сдвигов задается содержимым регистра CL.
  • биты 7 и 6 – режим адресации
  • биты 5 и 4 – код операции сдвига
  • бит R/L – направление сдвига, R/L=0 – сдвиг влево, R/L=1 – сдвиг вправо
  • биты R/M – операнд-получатель, определяется режимом адресации
Обычно по команде «SAL» транслятор генерирует код команды SHL, хотя существует код команды SAL – D0Fx, который не всегда распознается дизассемблером, но выполняется микропроцессором.
000циклический сдвиг операндаROL AL,1D0C0
010циклический сдвиг через переносRCL AL,1D0D0
100логический сдвигSHL AL,1D0E0
110арифметический сдвиг влевоSAL AL,1 (только в кодах)D0F0
111арифметический сдвиг вправоSAR AL,1D0F8
Так как логический сдвиг влево часто используется в алгоритмах, реализующих умножение и деление, поэтому коды типа D0Fx можно использовать вместо команды SHL.
_
ShiftOp​
ROL​
ROR​
RCL​
RCR​
SHL​
SHR​
SAL​
SAR​
_
_
0​
1​
2​
x3​
Cx
ShiftOp r/m8,imm​
ShiftOp r/m(16/32/64),imm​
Dx
ShiftOp r/m8,1​
ShiftOp r/m(16/32/64),1​
ShiftOp r/m8,CL​
ShiftOp r/m(16/32/64),CL​
 
Последнее редактирование:
  • Нравится
Реакции: Marylin
Мы в соцсетях:

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