Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #90, ИЮНЬ 2006 г.

Самый маленький эльф

Крис Касперски ака мыщъх

Хакер, номер #090, стр. 090-110-6


int 80h ; // exit(?);

msg db "hello,elf",0Ah

msg_end:

filesize equ $ - $$

Теперь, когда борьба идет за каждый байт, воспользуемся ассемблерными трюками, оптимизирующими размер ассемблерного кода. Во-первых, заменим "MOV EBX,1" на "XOR EBX,EBX", "INC EBX" (напоминаю, NASM использует синтаксис Intel'а), во-вторых, сохраним это значение в стеке однобайтовой командой "PUSH EBX" — позднее оно нам понадобится для системного вызова exit. В-третьих, не будем явно инициализировать код возврата — он ведь нам все равно не нужен.

$ nasm -f bin -o elf_tiny elf_tiny.asm

$ chmod +x elf_tiny

После сборки образуется двоичный elf-файл размеров всего в 118 байт, что в три с лишним раза короче аналогично файла, собранного стандартным линкером. Но это еще не предел!

Экстремальная оптимизация

Держись! Мы вошли в раж и не оторвемся от клавиатуры, пока не сократим файл хотя бы на десяток байт. Больше всего нас раздражают e_ident-байты, оставленные для выравнивания в количестве целых девяти штук, плюс один байт версии elf-файла, которую все равно никто не проверяет! А что если поместить строку "hello,elf" именно здесь?! Сказано — сделано! Ведь elf-заголовок отображается на память и вполне пригоден для хранения переменных.

Но это еще не все! Даже поверхностный взгляд показывает, что 8 последних байт elf-заголовка совпадает с 8-ми первыми байтами program header table, следующего непосредственно за ним. Вот они, красавчики: "01h 00h 00h 00h 00h 00h 00h 00h 01h 00h 00h 00h 00h 00h 00h 00h". А почему бы не сдвинуть начало program header table так, чтобы оба заголовка перекрывались? Для этого достаточно будет скорректировать поле e_phoff, переместив метку phdr вглубь elf-заголовка.

Оптимизировав служебные структуры насколько это возможно, займемся "несущим" кодом. Команда "MOV EAX,4" съедает целых 5 байт, но, если немного подумать, можно отвоевать 1 байт, заменив ее эквивалентной конструкцией: "XOR EAX,EAX", "MOV AL,4". То же самое относится и к "MOV EDX,MSG_END-MSG.

Проделав все эти операции, мы получим следующий файл:

Оптимизированный файл elf_tinix.asm с перекрывающимися заголовками

ehdr:

; db 7Fh, "ELF", 1, 1, 1 ; // e_ident

db 7Fh, "ELF", 1, 1 ; // e_ident

; // размещаем выводимую строку в поле e_ident

; // в EI_PAD байтах, оставленных для выравнивания,

; // "захватывая" и байт EI_VERSION

msg db "hello,elf",0Ah

msg_end:

...

phdr:

; // используем наложение program header table на elf header,

; // заголовки как бы проникают друг в друга, и это работает,

; // потому что конец elf header'а совпадает с prg header'ом

dd 1 ; // e_phnum

; dw 0 ; // e_shentsize

dd 0 ; // e_shnum

; dw 0 ; // e_shstrndx

ehdrsize equ $ - ehdr

...

_start:

xor eax,eax ; получаем ноль

mov ebx,eax ; копируем ноль в ebx

mov edx,eax ; копируем ноль в edx

mov al,4 ; // системный вызов #4 "write"

inc ebx ; // 1 - STDOUT

push ebx ; сохраняем ebx == 1 для syscall'a #1 exit

mov ecx,msg ; // смещение выводимой строки

mov dl,msg_end-msg ; // длина строки

int 80h ; // write(1, msg, len);

pop eax ; // системный вызов #1 "exit"

Назад на стр. 090-110-5  Содержание  Вперед на стр. 090-110-7