Перечитывая свой любимый номер x25zine’a (3ий), я вспомнил о статье madcr’a в рубрике back 2 start о самодельных elf
файлах. Помниться, когда я первый раз прочитал эту статью, я был
поражен до глубины души! Да еще на каком-то из форумов недавно увидел фразу, что мол нельзя создавать
РЕ файлы меньше 1,5 Кб 🙂 (Тогда, на масме, совершенно ничего сверхъестественного не предпринимая, просто создал приложение в 1 Кб...). В общем, у меня одно на другое наложилось и меня переклинило :). Я решил, что умру, но создам полноценное 32-битное
РЕ приложение, которое занимает не более 500 байт. Справедливости ради стоит отметить, что под полноценным я понимаю то, которое может работать в XP. Из всех загрузчиков только он порадовал меня своей милостью :). Дело в том, что загрузчик, к примеру, Win2k, как оказалось, не воспринимает никак
полей в файловом заголовке, отвечающем за размер опционального заголовка, поэтому последний, мол, нельзя сократить :(... Загрузчик WinME, который представляет линейку 9х, вообще не переносит
не выравненных файлов... Можно было бы совместить вариант под ХР с 2000, но у почему-то получаются различные точки входа... Хочу заметить, что тот каркас программы, который я создам, не имеет особой ценности
(кроме людей, пишущих трояны и черви пишет под хр, и то им тоже не критично все это...) Я же делал это just 4 fun ;)... Надеюсь, что кому-нибудь будет также интересна эта тема как и мне... К тому же здесь будет рассмотрены базовые приемы построения
РЕ заголовка, которые, кто знает, может вам и пригодяться (ждем новый линкер) 😉
Для начала убедимся, что в РЕ формате есть избыточность, которую можно устранить. Для этого просто сделаем совсем простенькое win32 приложение на последнем masm’e (у меня 8ой):
; ### Lil prog ###
; ### About ###
; Lil prog made in masm with help of standart link...
; Made in MASM v8.0
; Here is MakeIt.bat:
;--- Start of MakeIt.bat ---
; @echo off
;
; if exist 1.obj del 1.obj
; if exist 1.exe del 1.exe
;
; \masm32\bin\ml /c /coff /nologo 1.asm
; \masm32\bin\Link /SUBSYSTEM:WINDOWS /MERGE:.rdata=.text 1.obj
;
; dir 1.*
;
; pause
.386
.model flat, stdcall
option casemap :none
; ###
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
; ###
.code
start:
push 0
call ExitProcess
end start
; ###
Теперь смотрим, что у нас получилось... Ровно 1 Кб, как я и говорил раньше... Заглянем «под капот», открыв
файл простым хекс редактором (я оставил там свои комментарии):
MZ - сигнатура, с которой начинается дос заглушка.
Это знаменитый e_lfanew, который указывает на РЕ заголовок.
В этом можно убедиться найдя по смещению b0 в этом
файле сигнатуру ‘PE’,0,0. Соответственно на этом месте можно закончить
DOS заглушку, но наш многоуважаемый линкер решил, что дос жив
и затолкал далее глупый код, говорящий, мол, нельзя из-под доса
запустить W32 приложение 🙂
Шоу начинается 🙂 Сигнатура РЕ заголовка...
Размер опционального заголовка, его (заголовок) можно сократить 😉
Здесь начинается описание секции, сократить которое не представляется возможным 🙁
Зато где-то до этого момента есть куча описаний ненужных
нам таблиц... 😉
Смещение секции выравнивается по границе 0х200 🙁 Исправим 😉
ЭТО НАШ КОД!!! Всего каких-то 7 байт %)
Смерть таблице импорта! 🙂
Ну а это, вообще, безобразие! Нафига нам все эти нули?
Выравнивание must die 🙂
Ну что, нашли что сократить? :). В общем, вот наш план:
- Убираем выравнивание первым делом;
- У меня давно чесались руки убить dos stub, это и сделаем;
- Сокращаем опциональный заголовок. Убираем кучу бесполезных таблиц;
- Раз уж убили таблицу импорта в прошлом пункте придется написать свой GetProcAddress 🙁
- Запихиваем несколько переменных прямиком в остатки dos stub 🙂
Теперь более насущные проблемы. Для всего этого безобразия, что я задумал, явно надо будет делать бинарник и самостоятельно описывать все поля заголовка. Таким образом, нам нужен ассемблер, который умеет делать бинарники. Из моего арсенала нашлись fasm и nasm. Ну да ладно, я люблю nasm так же, как и masm, так что возьмем именно его :). Еще одна проблема – это сделать компактный GetProcAddress. Вроде как у меня получилось, судить тебе. Найдешь способ сделать его короче – не забудь мне написать. 😉
Ну, хватит разглагольствовать, приступим :
; ### SELFMADE PE ###
; This is selfmade pe file, compile with help of nasm like a bin:
; nasmw -f bin 1.s -o 1.exe
; [warning: It works only in XP systems]
; Made by R4D][
[BITS 32]
; ### SOME MACROSES AND CONSTS ###
base equ 0x00400000 ; Image base
ep equ 0x10e0 ; Entry point
b equ base+ep
flags equ 0xE0000008 ; Exec+Write+Read 😀
%define $c b+($-start_code) ; in nasm when u compile bin file $=file_offset!
%define $d(x) b+(x-start_code) ; and all data offsets is file offsets too 🙁
; ### EXECUTABLE FILE HEADER ###
header:
; DOS Header (size = 0x40)
dw 'MZ' ; magic 'MZ'
GPA db "GetProcAddress",0
so_GPA equ $-GPA
LL db "LoadLibraryA",0
so_LL equ $-LL
; Other data better be avoid
times 60-($-header) db 0
dd 0x00000040 ; e_lfanew
; PE Header
pe_hdr:
; File Header (size = 0x18)
file_hdr:
db 'PE',0,0 ; magic 'PE'
dw 0x014c ; machine = i386
dw 0x0001 ; num of sections
dd 'Made' ; time date stamp 🙂
dd 0 ; pointer 2 sym table
dd 0 ; num of syms
dw sect-opt ; sizeof opt_header
dw 0x010F ; flags = executable
; Optional Header (size = 0xe0, but we made it 0x60)
opt:
dw 0x10b ; magic
db 'by' ; linker versions 🙂
dd codesize ; codesize
dd 0 ; sizeof init data
dd 0 ; sizeof uninit data
dd ep ; ep 😉
dd 0x00001000 ; code base
dd 0x00002000 ; data base
dd base ; image_base
dd 0x00001000 ; section align
dd 0x00000200 ; file align (fuck it :))
dd 0x00000004 ; os ver
dd 'R4DX' ; user ver
dd 0x00000004 ; subsystem ver
dd 'spec' ; reserved 😉
dd 0x00002000 ; imagesize
dd sect-header ; header size
dd '4 u,' ; check sum 😉
dw 0x0002 ; gui
dw 0 ; dll flags
dd 0x00100000 ; 1 mb for stack
dd 0x00001000 ; /\ without page_guard
dd 0x00100000 ; max heap value
dd 0x00001000 ; real heap size
dd ' man' ; loader flags 😉
dd 0x10 ; reserved, must be 0x10
; times 0xf8 - ($-pe_hdr) db 0 ; rvas & sizes to lots of tables & another trash 🙂
; Code section entry (size = 0x28)
sect:
db 'minimi:)' ; Section name
dd codesize ; virtual sizeof code
dd 0x00001000 ; rva of section
dd codesize ; physical sizeof code
dd start_code ; physical offset of code
times 0x0c db 0 ; reserved
dd flags ; flags of section
; ### MAIN CODE ###
start_code:
jmp data_end
; ### DATA ###
; user32 db "user32.dll",0
; msg db "Look at my size ;)",0
; MB db "MessageBoxA",0
; so_MB equ $-MB
EP db "ExitProcess",0
so_EP equ $-EP
data_end:
; ### CODE ###
%ifdef comment
push so_LL
push $d(LL)
call GPA_proc
push $d(user32)
call eax ; LoadLibrary("user32.dll")
push eax
; Searchin 4 GetProcAddress
push so_GPA
push $d(GPA)
call GPA_proc
pop ebx
; Searchin 4 MessageBoxA
push $d(MB)
push ebx
call eax ; GetProcAddress(base_of_u23, "MessageBoxA")
push 0
push 0
push $d(msg)
push 0
call eax ; MessageBox(0,”Look at my size ;)”,NULL,MB_OK)
%endif
; Searchin 4 ExitProcess
push so_EP
push $d(EP)
call GPA_proc
push 0
call eax ; ExitProcess(0)
; ### GetProcAddress ###
GPA_proc:
; assume fs: nothing
mov esi, [esp+4] ; esi = name_of_da_func
mov ecx, [esp+8] ; ecx = $-esi 😉
; Lets get kernel32.dll base 😉
mov eax, [fs:30h]
mov eax, [eax+0Ch]
mov eax, [eax+0Ch]
mov eax, [eax]
mov eax, [eax]
mov eax, [eax+18h]
; Down 2 pe header without any cmp 😉
mov ebp, eax ; in ebp the base
; assume eax: PTR IMAGE_DOS_HEADER
mov eax, [eax+3Ch] ;.e_lfanew
add eax, ebp
; assume eax: PTR IMAGE_NT_HEADERS32
mov eax, [eax+78h] ;.OptionalHeader.DataDirectory[0].VirtualAddress
add eax, ebp
; assume eax: PTR IMAGE_EXPORT_DIRECTORY
mov ebx, dword [eax+18h] ;.NumberOfNames
push eax
mov eax, dword [eax+20h] ;.AddressOfNames
add eax, ebp
; Enuming all da functions
floop:
push ecx
push esi
mov edi, dword [eax]
add edi, ebp
repz cmpsb
pop esi
pop ecx
jz FuncFound
add eax, 4
dec ebx
jnz floop
ret
; WE FOUND IT!!! 🙂
FuncFound:
; assume edx: PTR IMAGE_EXPORT_DIRECTORY
pop edx
mov edi, dword [edx+18h] ;.NumberOfNames
sub edi, ebx
mov esi, dword [edx+24h] ;.AddressOfNameOrdinals
add esi, ebp
lea edi, [edi*2+esi]
movzx eax, word [edi]
mov edi, dword [edx+1Ch] ;.AddressOfFunctions
add edi, ebp
lea eax, [eax*4+edi]
mov eax, dword [eax]
add eax, ebp
ret 0008
codesize equ $-start_code
Итак, посмотрим, что у нас получилось, а потом рассмотрим как это у нас получилось :)...