Rambler's
Top100

Продвинутый туториал по AT&T ассемблеру

Автор: Ares
Дата: 08.11.2003
Раздел: Низкоуровневое программирование в Linux

Advanced AT&T asm tutorial

Несколько слов о том, для чего собственно писалась эта статья... Когда я начал учить ассемблер под линукс, я столкнулся с некоторыми проблемами, потому что AT&T синтаксис мало документирован, а существующие документы в основном содержат информацию о различии Intel и AT&T синтаксиса + пример программы "Hello world". Конечно, если ты достаточно хорошо знаешь Intel'овский синтаксис, то писать под linux гораздо проще, но лично я мало знаком с ним, также, как и многие другие люди, пытающиеся программировать на ассемблере под линукс\юникс. Программировать под линукс заметно проще нежели под DOS, из-за особенности синтаксиса, он выглядит более дружественным (логичным?) для пользователя. Это может быть одной из причин выбора AT&T синтаксиса, который является основным для unix систем, но я выбрал его лишь потому, что предпочитаю юникс для каких-либо разработок. Рассказывать о ЦПУшных регистрах, памяти етс ...я не собираюсь, поэтому тебе необходимо иметь хоть какие-то знания в асме, иначе иди на google.com "at&t asm basic tutorial" или поищи некоторые туторы по написанию шеллкодов... Все написанное ниже будет в виде вопросов и ответов, в основном это описание написания (каламбур =)) основных\часто применяемых функций, но без использования библиотеки libc, которая в себе уже содержит их.

[ Описываемые вопросы ]:

  1. Как сравнить 2 строковые переменные ?
  2. Как соединить строковые переменные ?
  3. Как сделать счетчик ?
  4. Как динамически вычислить длину строк ?
  5. Как работать с массивами ?
  6. Как сконвертировать число (integer) в его ASCII эквивалент (string) ?
  7. Как сконвертировать integer переменную в network byte order ?
[ Итак, начнем... ]

[1]. Как сравнить 2 строковые переменные ?:

Столкнуться с необходимостью такого сравнения можно в любой программе, принимающей входные данные от пользователя, например авторизация. Для этого существует специальная инструкция сравнения строк - cmpsb, она берет данные из регистров %esi, %edi и %ecx:

-------------------------------------------------------------------------
/* strcmp.s */
.globl _start
_start: movl $str1,%esi    # кладем первую строку в %esi
movl $str2,%edi            # вторую в %edi
movl $4,%ecx               # количество байт для сравнения
cld                        # очистить флаг D, иначе сравнение 
                           # пойдет в обратном порядке
rep  cmpsb                 # сравнение
jz   yes                   # если строки равны то прыгаем в yes,
                           # если нет, то exit
exit:
movl $1,%eax               # выход
int  $0x80 yes:
movl $4,%eax               # write
movl $1,%ebx               # stdout
movl $msg,%ecx             # наше сообщение
movl $msglen,%edx          # его длина
int  $0x80 
jmp exit 
.data
str1:   .string "asdf"
str2:   .string "asdf"
msg:    .string "The strings are equal! Quiting...\n"
msglen = . -msg            # вычислить длину msg
/* End */
-------------------------------------------------------------------------
Компиляция:
as strstr.s -o strstr.o
ld strstr.o
Можно использовать gcc: gcc strstr.s , предварительно заменив "_start" на "main".

[2]. Как соединить строковые переменные ?:

Для этого будем использовать интсрукцию movsb, первую строку кладем в %esi и буфер, в котором получим результат, в %edi. Взгляни на пример:
-------------------------------------------------------------------------
/* strcat.s */
.globl main
main: 
movl $str1,%esi          # кладем строку "AT&T" в %esi
movl $buf,%edi           # буфер с будущим результатом
movl $6,%ecx             # размер стринга
cld   
rep  movsb               # после выполнения в $buf находится str1
movl $str2,%esi          # далее производим выше описаные операции с str2
movl $9,%ecx  
cld   
rep  movsb               # вуаля, теперь у нас в $buf имеется str1+str2,
                         # т.е "AT&T is cool\n"
movl $4,%eax             # write
movl $1,%ebx             # stdout
movl $buf,%ecx           # "AT&T is cool\n"
movl $15,%edx            # size of $buf
int  $0x80 
movl $1,%eax             # exit
int  $0x80 .data
str1: .string "AT&T "
str2: .string "is cool\n"
buf: .byte 15            # static buffer
/* End */
------------------------------------------------------------------------- 

[3]. Как создать счетчик ?:

Что за программа без счетчика ? Да, счетчик часто используемая функция. Для визуальной демонстрации работы нашего счетчика будет использован вывод строки через write, поэтому регистры %eax,%ebx,%ecx,%edx будут заняты, но %esi и %edi останутся, их-то мы и будем использовать:
-------------------------------------------------------------------------
/* counter.s */
.globl main
main: 
movl $5,%esi   # число повторений , используй %esi или %edi не важно 
loop:                # write (stdout,str,5);
movl $4,%eax  
movl $1,%ebx
movl $str,%ecx
movl $5,%edx
int  $0x80 decl %esi # уменьшить %esi на 1
tesl %esi,%esi       # если %esi 0
jz   exit            # jmp exit 
jmp  loop            # прыжок на начало 
exit:
movl $1,%eax
int  $0x80
/* End */
------------------------------------------------------------------------- 

[4]. Как динамически вычислить длину строки ?:

Для реализации этой функции мы прибегнем к небольшому трюку. Будем использовать инструкцию lodsb для проверки строки байт за байтом, попутно увеличивая счетчик для вычисления длины. Сперва необходимо поместить эфективный адрес строки в %esi, %edi будет счетчиком.

-------------------------------------------------------------------------
/* strlen.s */
.globl main
main: 
xorl %edi,%edi   
leal str,%esi                # эфективный адрес в %esi abc:
lodsb                        # читает байт в строке и автоматически устанавливает 
                             # следующую позицию
cmpb $0,%al                  # если байт равен 0, значит end of string
jz   continue    
incl %edi                    # счетчик
jmp  abc                     # повторить... continue:
decl %edi ...                # в %edi готовый результат - длина (int) .data
str: .string "CheckMylength" # если в строке присутствует \n, длина увеличивается 
                             # дополнительно на 1 байт
/* End */
------------------------------------------------------------------------- 
Небольшой перерыв для нескольких советов. Почему бы не использовать переменные в более дружественном виде ? int $sysint - выглядит лучше чем int $0x80, объявляется это так - sysint = 0x80, все что захочешь:
int = 1
char = 97 # character "a"
(dec) char = 0x61 # character "a" (hex)
etc...

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

[ Continue... ]

[5]. Как работать с массивами ?:

 -------------------------------------------------------------------------
/* Small C example */
int main()
{
char a[10];
int b; for (b=0;b!=10;b++)
 {
  a[b]='a';
  printf("%c",a[b]);
 }
}
/* End */
------------------------------------------------------------------------- 
Этот пример пихает символ "a" в каждый элемент массива, вывод программы выглядит так:aaaaaaaaaa На мой взгляд это подходящий пример.
-------------------------------------------------------------------------
/* array.s */ count  = 10  # счетчик
a      = -12               # смещение в %ebp, место для нашего массива - 
                           # как char a[10];
b      = -16               # int b;
char   = 97                # символ 'a' (dec)
sysint = 0x80 
.globl main
main: 
movl $0,b(%ebp)      # for(b=0; loop:
cmpl $count,b(%ebp)        # b != 10;
jnz  lp                    # jump if not equal
jmp  exit                  # jump if counter reached lp:
leal a(%ebp),%edi          # a[
movl b(%ebp),%esi          # b]
movb $char,(%esi,%edi)     # ='a';
incl b(%ebp)               # b++);
                           # write(stdout,a[b],1);
movl $4,%eax
movl $1,%ebx
leal (%esi,%edi),%ecx
movl $1,%edx
int  $sysint
 
jmp loop 
exit:
movl $1,%eax
int  $sysint
/* End */
------------------------------------------------------------------------- 

Как можно заметить, область массива в %edi и его элементы в %esi, готовый массив находится в (%esi,%edi).

[6]. Как сконвертировать число(integer) в его ASCII эквивалент(string)?:

Для чего это надо ? Например syscall write работает только со строками, и если мы сделаем write(stdout,numeric,length), будет выдан символ из ASCII таблицы с номером numeric. Числа в ASCII таблице начинаются с 48 (decimal 0), поэтому мы просто увеличиваем наш numeric на 48 и получаем аскишный эквивалент необходимой цифры. Перед тем как выполнить это на асме, взглянем на процедуру выполненную в С...Так как система счисления десятеричная, то соответственно и делить необходимо на 10:

-------------------------------------------------------------------------
/* num2ascii.c */
int  main()
 {
char a[10];
int x = 1234,b=0; do    // отделяем каждую цифру от целого числа
 {
   a[b]=48 + x % 10;    // кладем в массив, первый элемент содержит "4", второй "3"
                        // etc... - обратный порядок
          b++;          
 } while (x /= 10);
do                      // перевернем 4321 в необходимое 1234
 {
   b--;
   printf("%d\n",a[b]);
 } while(b!=0);
}
/* End */
-------------------------------------------------------------------------
Это приблизительная реализация, если перевести ее шаг за шагом на асм, мы получим большой код в пределах ~1kb, поэтому пример на асме имеет немного другую структуру.
-------------------------------------------------------------------------
/* num2ascii.s */ 
int = 1234 .globl main
main: 
xorl %esi,%esi
movl $int,%eax 
loop:
movl $0,%edx
movl $10,%ebx
divl %ebx            # деление, результат в %eax (123) и остаток в %edx (4)
addb $48,%dl         # +48
pushl %edx           # на стек
incl %esi            # счетчик цифр
cmpb $0,%al
jz   next 
jmp loop 
next:   
popl (%ecx)          # взять со стека
testl %esi,%esi
jz   exit
decl %esi
movl $4,%eax
movl $1,%ebx
movl $2,%edx
int  $0x80 
jmp  next
exit:
movl $1,%eax
int $0x80
/* End */
-------------------------------------------------------------------------  

[7]. Как сконвертировать числовую переменную в сетевой байтовый порядок (network byte order) ?:

Такая нужда у нас появляется при работе с сокетами, а точнее htons(port).
-------------------------------------------------------------------------
/* htons.s */
int = 10
.globl main
main: 
movl $int,%edi
movl %edi,%eax
rol $8,%ax  # сдвиг, rol или ror здесь роли не играют 

# сейчас %eax содержит 10 в nbo
... /* End */
------------------------------------------------------------------------- 

Этого должно быть достаточно на первых парах... Где можно применить эти знания? Да например при написании более сложных шеллкодов, вирусов, а так же это хорошая возможность оптимизировать некоторые функции в большом С коде. Все примеры были написаны под linux, но нет ничего сложно в портировании этого например под *BSD. И в завершении я написал port scanner так сказать в образовательных целях, там ты найдешь еще несколько фишек, например конвертирование IP в network byte order, работа с аргументами командной строки etc..
Well this is end...enjoy.
http://ares.x25zine.org/Codes/0x4553_asm_portscan.tar.gz


[Главная] [Другие статьи] [Обсудить в форуме]
©2003-2004 Lowlevel.RU

Rambler's Top100