Суббота, 23.11.2024, 06:43
Приветствую Вас Гость | RSS
Главная | | Регистрация | Вход
Меню сайта
Форма входа
Поиск
Календарь
«  Февраль 2012  »
ПнВтСрЧтПтСбВс
  12345
6789101112
13141516171819
20212223242526
272829
Архив записей
Наш опрос
Оцените мой сайт
Всего ответов: 20
Мини-чат
Друзья сайта
  • Заказ Художник
  • Рыбалка
  • Новости
  • Наш университет io-96
  • Железо и другие
  • Наш Counter-Strike
  • Google ot Turbokherson
  • Google ot Turbokherson87
  • Android
  • Программы и Софт
  • Программирование
  • Лечение здоровья
  • О Ремонт компьютер
  • Наш сайт Turbokherson
  • Программирование ICQ 378204653 от Turbokherson
    Главная » 2012 » Февраль » 20 » Минималистичная программа в формате ELF
    14:58
    Минималистичная программа в формате ELF
    Вдохновившись статьёй Привет из свободного от libc мира, я так же решил проделать нечто подобное. Чтобы не заниматься этим бесцельно, я решил поставить перед собой следующую задачу. Сделать программу, выводящую какую-нибудь простую строку, вроде «ELF, hello!». Разобраться с тем, как именно она будет представлена в исполняемом файле. Ну и попутно, постараться уложиться в 100 байт.

    Для начала, стандартный helloworld на C++

    #include <iostream>
    using namespace std;
    int main()
    {
     cout << "ELF, hello!\n";
     return 0;
    }

    Компилируем, смотрим размер:

    $ g++ test.cpp -static && ls -s -h a.out
    1,3M a.out


    Сколько, сколько? 1.3 Мб? Для вывода одного единственное сообщения размером в 12 байт? Хм… Ладно, попробуем Си.

    #include "stdio.h"
    int main()
    {
     printf("ELF, hello!\n");
     return 0;
    }

    Так же компилируем и его. При компиляции я указал опцию -static — мне интересен целиком весь код, который будет выполнятся. При динамической компиляции размеры конечно меньше, но всё равно не настолько как хотелось бы.

    $ gcc test.c -static && ls -s -h a.out
    568K a.out


    На пол мегабайта меньше. Вот она, плата за STL. Но, всё равно очень много. Видимо, без тяжелой артиллерии в виде ассемблера не обойтись. Пишем helloworld на асме, и без stdlib. Я предпочитаю AT&T-шный синтаксис.

    .data
    str:
     .ascii "ELF, hello!"
     .byte 10
    .text
    .global _start
    _start:
     movl $4, %eax
     movl $1, %ebx
     movl $str, %ecx
     movl $12, %edx
     int $0x80
    
     movl $1, %eax
     movl $0, %ebx
     int $0x80


    Две секции, в секции данных — наше сообщение (и 10-ка для перевода на новую строку), в секции кода (.text) — два раза вызываем 80-е прерывание (с нужными параметрами в регистрах), первый раз для вывода сообщения, второй раз для корректного завершения.

    Компилируем (а вернее транслируем и линкуем) созданную программу:

    $ gcc easy.s -nostdlib && du -sb a.out
    752 a.out


    752 байта — вот это уже намного ближе к тому, что требуется. Уберём отладочные символы утилитой strip:

    $ strip a.out && du -sb a.out
    476 a.out


    Лучше, но всё ещё не достаточно. Что-же в нашем файле на целых 476 байт? Дизассемблируем a.out используя objdump:

    $ objdump -D a.out
    
    a.out: file format elf32-i386
    
    Disassembly of section .note.gnu.build-id:
    
    08048094 <.note.gnu.build-id>:
     8048094: 04 00 add $0x0,%al
     ...
     Какой-то код, который мы не писали
     ...
     80480b6: b6 08 mov $0x8,%dh
    
    Disassembly of section .text:
    
    080480b8 <.text>:
     80480b8: b8 04 00 00 00 mov $0x4,%eax
     80480bd: bb 01 00 00 00 mov $0x1,%ebx
     80480c2: b9 dc 90 04 08 mov $0x80490dc,%ecx
     80480c7: ba 0c 00 00 00 mov $0xc,%edx
     80480cc: cd 80 int $0x80
     80480ce: b8 01 00 00 00 mov $0x1,%eax
     80480d3: bb 00 00 00 00 mov $0x0,%ebx
     80480d8: cd 80 int $0x80
    
    Disassembly of section .data:
    
    080490dc <.data>:
     80490dc: 45 inc %ebp
     80490dd: 4c dec %esp
     80490de: 46 inc %esi
     80490df: 2c 20 sub $0x20,%al
     80490e1: 68 65 6c 6c 6f push $0x6f6c6c65
     80490e6: 21 0a and %ecx,(%edx)


    И так, мы видим три секции, хотя писали только две. В секции .text находится наш код. В секции data — наш elf hello в виде 12-и байт (objdump их тоже дизассемблировал). А что ещё за секция .note.gnu.build-id? Мы её не заказывали, поэтому смело удаляем:

    $ strip -R .note.gnu.build-id a.out && du -sb a.out
    416 a.out


    Выиграли ещё 60 байт. Неплохо. Давайте попробуем немного оптимизировать наш код. Во первых, программа в принципе может завершаться с любым кодом, а не обязательно с нулевым. Во вторых — при запуске программы регистры обнуляются (однако не стоит на это полагаться при создании реальных программ — проверяйте ABI той системы, для которой пишите).
    В итоге вместо movl $4, %eax, которая транслируется в 5 байт, мы можем использовать movb $4, %al, которые транслируются в 2 байта. В третьих, избавимся от секции .data, разместив нашу строку в коде после последнего прерывания (всё равно программа дальше не выполняется):

    .text
    .global _start
    _start:
     movb $4, %al
     movb $1, %bl
     movl $str, %ecx
     movb $12, %dl
     int $0x80
    
     movb $1, %al
     int $0x80
    str:
     .ascii "ELF, hello!"
     .byte 10


    Компилируем, удаляем лишнее, смотрим размер:

    $ gcc -nostdlib easy.s
    $ strip a.out
    $ strip -R .note.gnu.build-id a.out
    $ du -sb a.out
    320 a.out


    Кажется, мы достигли предела. 320 байт — ничего лишнего. Или нет? Откуда вообще эти 320 байт? Наш код — явно меньше. Однако кроме кода в нашем бинарном файле есть ещё ELF заголовок. И если мы хотим сделать по настоящему минимальную программу, то придется открывать описание ELF (например, тут), и формировать заголовок вручную.

    Вручную — это не значит в hex редакторе. Просто можно дать понять линкеру что к нашему файлу ничего приписывать не надо, и он выдаст на выходе именно то, что мы напишем. Правда в этом случае вся ответственность за то, чтобы файл запустился ложиться на нас.
    Реализация программы с вручную составленным заголовком у меня получилась такой:

     .set ofs, 0x10000 /* ofs - тут храним смещение */
    /* ELF Заголовок: */
     .byte 0x7F
     .ascii "ELF"
     .long 0, 0, 0 /* ident */
     .word 2 /* type */
     .word 3 /* machine */
     .long 0 /* version */
     .long _start + ofs /* entry - адрес начала кода (абсолютный) */
     .long phdr /* phoff - адрес программного заголовка
     (phdr) (относительный ) */
     .long 0 /* shoff */
     .long 0 /* flags */
     .word 0 /* ehsize - размер elf заголовка */
     .word phdrsize /* phentsize - размер прогр. заголовка */
     .word 1 /* phnum - количество пр. заголовк. */
     .word 0 /* shentsize */
     .word 0 /* shnum */
     .word 0 /* e_shstrndx */
    /* Программный заголовок */
    phdr:
     .long 1 /* type */
     .long 0 /* offset */
     .long ofs /* vaddr - абсолютный адрес начала кода
     программы (с учетом смещения) */
     .long 0 /* paddr */
     .long filesize /* filesz - размер программы на носителе */
     .long filesize /* memsz - размер программы в памяти */
     .long 5 /* pflags */
     .long 0 /* palign */
    .set phdrsize, . - phdr
    _start:
    /* Код программы */
     movb $4, %al
     movb $1, %bl
     movl $(str+ofs), %ecx
     movb $12, %dl
     int $0x80
    
     movb $1, %al
     int $0x80
    str:
     .ascii "ELF, hello!"
     .byte 10
    .set filesize, .


    Теперь нам так же приходится вручную оперировать со смещением программы. Под смещением, упрощенно, можно понимать разницу в адресации между кодом который лежит в нашей программе, и тем, где он будет размещён в оперативной памяти (на самом деле в оперативной памяти он будет лежать совсем не там, но это уже другая история). Обычно определением нужных смещений занимается линкер, но теперь мы сами по себе. Смещение я поместил в параметр ofs. Размер смещения взял минимально возможный на моей машине (10 000). По умолчанию он равен 8048000, но это не обязательное условие.

    Сам ELF заголовок — это на самом деле не один ELF заголовок. Их должно быть как минимум два — elf заголовок, и программный заголовок. Вообще есть ещё заголовки секций, но мы их не будем использовать для экономии места. Опытным путём были установлены поля заголовков, которые используются. Остальные были заполнены нулями.

    Транслируем программу, на этот раз вручную вызывая as и ld:

    $ as w3test.s -o w3test.o
    $ ld -Ttext 0 --oformat binary -o w3test w3test.o
    $ du -sb w3test
    115 w3test


    115 байт! В ~10 000 раз меньше чем первоначальный вариант. Казалось бы всё. Есть только необходимый для запуска минимум, и ничего лишнего. И начальную задачу преодолеть 100 байт выполнить не удастся. Однако это не предел! В заголовке есть неиспользуемые байты а это значит, что мы можем использовать их для своих целей. Сам код к сожалению ни в одно поле не влезет, слишком большой. Зато влезет строка.

    Если внимательно присмотреться, то сразу после идентификатора ELF у нас идёт три неиспользуемых поля типа long (по четыре байта каждое). Это значит что мы можем положить туда строку. Да к тому же не всю строку, а только последнюю её часть, ведь «ELF» в виде ascii символов у нас и так уже есть.

    Кроме этого мы можем сократить код, разместив заголовок phd не после elf, а сразу после последнего используемого в ELF байта. Т. е. заголовок phd будет немного наслаиваться на elf, но это не вызовет никаких последствий, т. к. те поля, которые наслаиваются, в elf не используются.
    Точно так же мы можем разместить нашу программу, «наслаивая» её на phd заголовок (по тем же самым причинам).

    В итоге получится следующий код:

     .set ofs, 0x10000 /* ofs - тут храним смещение */
    /* ELF Заголовок: 8*/
     .byte 0x7F
    str: .ascii "ELF"
     .ascii ", hello!"
     .byte 10, 0, 0, 0
     .word 2 /* type */
     .word 3 /* machine */
     .long 0 /* version */
     .long _start+ofs /* entry - адрес начала кода (абсолютный) */
     .long phdr /* phoff - адрес программного заголовка
     (phdr) (относительный ) */
     .long 0 /* shoff */
     .long 0 /* flags */
     .word 0 /* ehsize - размер elf заголовка */
     .word phdrsize /* phentsize - размер прогр. заголовка */
    /* Программный заголовок */
    phdr:
     .long 1 /* type */
     .long 0 /* offset */
     .long ofs /* vaddr - абсолютный адрес начала кода
     программы (с учетом смещения) */
     .long 0 /* paddr */
     .long filesize /* filesz - размер программы на носителе */
     .long filesize /* memsz - размер программы в памяти */
     .long 5 /* pflags */
    .set phdrsize, . - phdr + 4
    _start:
    /* Код программы */
     movb $4, %al
     movb $1, %bl
     movl $(str+ofs), %ecx
     movb $12, %dl
     int $0x80
    
     movb $1, %al
     int $0x80
    .set filesize, .


    После трансляции получаем программу размером в 89 байт. Можно считать задачу выполненной.

    Ещё была идея по оптимизации — запихнуть phd заголовок внутрь elf заголовка. Но эта идея провалилась, т. к. минимальное смещение в 10 000 не позволило подобрать такие параметры, чтобы нужные поля структур совпадали.

    P. S. В комментариях был предложен ещё более оптимизированный вариант, размером в 61 байт, в котором таки удалось наложить phd на elf. Компилируется с помощью nasm/yasm с параметром -f bin.

    BITS 32;
    ORG 05430000h;
    
    DB 0x7F, "ELF";
    DD 01h, 00h, $$;
    DW 02h, 03h;
    DD @main;
    DW @main - $$;
    
    @main:
     INC EBX;
     DB 05h; <-- ADD EAX,
     DD 04h; <-- LONG(04h)
     MOV ECX, @text;
     MOV DL, 12;
     INT 80h;
     AND EAX, 00010020h;
     XCHG EAX, EBX;
     INT 80h;
    
    @text:
     DB "ELF, hello!", 0Ah;


    Источники информации


    wikibooks.org — Ассемблер в Linux для программистов C
    stackoverflow.com — "Hello World” in less than 20 bytes
    muppetlabs.com — Teensy ELF Executables for Linux
    Просмотров: 938 | Добавил: Turbokherson | Рейтинг: 0.0/0
    Всего комментариев: 1
    1 Turbokherson  
    0

    Имя *:
    Email *:
    Код *:
    Создать бесплатный сайт с uCozCopyright MyCorp © 2024