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. Стек не работает с глобальными переменными
|