Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Making Plain Binary files using a C compiler(i386+)

Cornelis Frank 2000

Создадим исходник test.c

         int main() {}
 
Откомпилируем его :
         gcc -c test.c
         ld -o test -Ttext 0x0 -e main test.o
         objcopy -R .note -R .comment -S -O binary test test.bin
 
Будет создан бинарник test.bin . Запустим команду
         ndisasm -b 32 test.bin
 
Яковлев С : На своей 3-й федоре я получил вывод :
 00000000  55                push ebp
 00000001  89E5              mov ebp,esp
 00000003  83EC08            sub esp,byte +0x8
 00000006  83E4F0            and esp,byte -0x10
 00000009  B800000000        mov eax,0x0
 0000000E  83C00F            add eax,byte +0xf
 00000011  83C00F            add eax,byte +0xf
 00000014  C1E804            shr eax,0x4
 00000017  C1E004            shl eax,0x4
 0000001A  29C4              sub esp,eax
 0000001C  C9                leave
 0000001D  C3                ret
 

Данный код является основой для любой функции . Регистр ebp на всякий сохраняем . GNU GCC может создавать только 32-битный код . Еще один вариант создания бинарника :

         gcc -c test.c
         ld test.o -o test.bin -Ttext 0x0 -e main -oformat binary
 

Теперь создадим локальную переменную

   int main()
   {
         int i;
         i = 0x12345; (16-ричная переменная)
   }
 
Откомпилируем его :
         gcc -c test.c
         ld -o test -Ttext 0x0 -e main test.o
         objcopy -R .note -R .comment -S -O binary test test.bin
 
Будет создан бинарник test.bin . Запустим команду
         ndisasm -b 32 test.bin
 
 00000000  55                push ebp
 00000001  89E5              mov ebp,esp
 00000003  83EC08            sub esp,byte +0x8
 00000006  83E4F0            and esp,byte -0x10
 00000009  B800000000        mov eax,0x0
 0000000E  83C00F            add eax,byte +0xf
 00000011  83C00F            add eax,byte +0xf
 00000014  C1E804            shr eax,0x4
 00000017  C1E004            shl eax,0x4
 0000001A  29C4              sub esp,eax
 0000001C  C745FC45230100    mov dword [ebp-0x4],0x12345
 00000023  C9                leave
 00000024  C3                ret
 
Команда mov dword [ebp-0x4],0x12345 помещает переменную в локальный стек , где 0x4 - зарезервированные 4 байта для типа int . Обратите на порядок байтов во втором столбике напротив этой команды - 452301 . Такой тип хранения называется backwards storage . Если комбинация
         int i;
         i = 0x12345; 
 
будет заменена на
         int i = 0x12345;
 
разницы никакой не будет .

Теперь создадим глобальную переменную :

         int i;
   int main()
   {
         i = 0x12345; 
   }
 
Получим следующий бинарный код :
 00000000  55                push ebp
 00000001  89E5              mov ebp,esp
 00000003  83EC08            sub esp,byte +0x8
 00000006  83E4F0            and esp,byte -0x10
 00000009  B800000000        mov eax,0x0
 0000000E  83C00F            add eax,byte +0xf
 00000011  83C00F            add eax,byte +0xf
 00000014  C1E804            shr eax,0x4
 00000017  C1E004            shl eax,0x4
 0000001A  29C4              sub esp,eax
 0000001C  C705281000004523  mov dword [0x1028],0x12345
          -0100
 00000026  C9                leave
 00000027  C3                ret
 
Наша глобальная переменная теперь пишется в память командой mov dword [0x1028],0x12345 по адресу 0x1028 в отдельный сегмент данных. Мы можем заставить линкер сохранить переменную поближе с помощью опции -N . Или можно для команды ld набрать например -Tdata 0x1234 , и наша глобальная переменная будет размещена по адресу 0x1234 . Хранение переменной в сегменте данных делает ее доступной за пределами функции main .

Теперь попробуем команду objdump :

         objdump --disassemble-all test.o
 
Опять же на моей 3-й федоре это дало результат :
 test.o:     file format elf32-i386
 Disassembly of section .text:
 00000000 
: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 83 e4 f0 and $0xfffffff0,%esp 9: b8 00 00 00 00 mov $0x0,%eax e: 83 c0 0f add $0xf,%eax 11: 83 c0 0f add $0xf,%eax 14: c1 e8 04 shr $0x4,%eax 17: c1 e0 04 shl $0x4,%eax 1a: 29 c4 sub %eax,%esp 1c: c7 05 00 00 00 00 45 movl $0x12345,0x0 23: 23 01 00 26: c9 leave 27: c3 ret

Теперь поговорим об указателях

     int main()
     {
         int i;
         int *p;
         p=&i;
         *p=0x12345;
     }
 
Будет сгенерирован код :
 00000000  55                push ebp
 00000001  89E5              mov ebp,esp
 00000003  83EC08            sub esp,byte +0x8
 00000006  83E4F0            and esp,byte -0x10
 00000009  B800000000        mov eax,0x0
 0000000E  83C00F            add eax,byte +0xf
 00000011  83C00F            add eax,byte +0xf
 00000014  C1E804            shr eax,0x4
 00000017  C1E004            shl eax,0x4
 0000001A  29C4              sub esp,eax
 0000001C  8D45FC            lea eax,[ebp-0x4]
 0000001F  8945F8            mov [ebp-0x8],eax
 00000022  8B45F8            mov eax,[ebp-0x8]
 00000025  C70045230100      mov dword [eax],0x12345
 0000002B  C9                leave
 0000002C  C3                ret
 
Команда sub esp,byte +0x8 резервирует в стеке 8 байт для 2-х переменных . Команда lea eax,[ebp-0x4] грузит в регистр eax адрес переменной i . После чего команда mov [ebp-0x8],eax присваивает этот адрес указателю . Затем в 2 приема : mov eax,[ebp-0x8] и mov dword [eax],0x12345 указателю присваивается значение 12345 .

Теперь поговорим о структурах . Рассмотрим пример :

         typedef struct 
         {
                 int a,b,c,d;
                 int i[10];
         } mydef;
         
         mydef myfunc();
         int main () 
         {
                 myfunc();
         }
         
         mydef myfunc()
         {
                 mydef md;
                 return d;
         }
 
Этот пример со структурой сгенерит код :
 00000000  55                push ebp
 00000001  89E5              mov ebp,esp
 00000003  83EC48            sub esp,byte +0x48
 00000006  83E4F0            and esp,byte -0x10
 00000009  B800000000        mov eax,0x0
 0000000E  83C00F            add eax,byte +0xf
 00000011  83C00F            add eax,byte +0xf
 00000014  C1E804            shr eax,0x4
 00000017  C1E004            shl eax,0x4
 0000001A  29C4              sub esp,eax
 0000001C  8D45B8            lea eax,[ebp-0x48]
 0000001F  83EC0C            sub esp,byte +0xc
 00000022  50                push eax
 00000023  E805000000        call 0x2d
 00000028  83C40C            add esp,byte +0xc
 0000002B  C9                leave
 0000002C  C3                ret
 0000002D  55                push ebp
 0000002E  89E5              mov ebp,esp
 00000030  57                push edi
 00000031  56                push esi
 00000032  83EC40            sub esp,byte +0x40
 00000035  8B7D08            mov edi,[ebp+0x8]
 00000038  8D75B8            lea esi,[ebp-0x48]
 0000003B  FC                cld
 0000003C  B80E000000        mov eax,0xe
 00000041  89C1              mov ecx,eax
 00000043  F3A5              rep movsd
 00000045  8B4508            mov eax,[ebp+0x8]
 00000048  83C440            add esp,byte +0x40
 0000004B  5E                pop esi
 0000004C  5F                pop edi
 0000004D  C9                leave
 0000004E  C20400            ret 0x4
 
Как мы видим , команда sub esp,byte +0x48 резервирует в стеке 0x48 байт . Указатель на структуру передается командой call 0x2d по адресу 0x2d .

Особенности GCC-откомпилированного кода

         1.  Он 32-битный 
         2.  Регистры CS,DS,ES,FS,GS,SS  работают в одном сегменте памяти
         3.  Глобальные переменные хранятся в сегменте DATA внутри самого
             бинарного файла , сразу после кодового сегмента
         4.  Переменные типа const хранятся в read-only секции также внутри
             бинарника .
         5.  Стек не работает с глобальными переменными
         
                   
 

 
 
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье