Перечитывая свой любимый номер 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

Итак, посмотрим, что у нас получилось, а потом рассмотрим как это у нас получилось :)...

Check Also

Исходный кот. Как заставить нейронную сеть ошибиться

Нейросети теперь повсюду, и распознавание объектов на картинках — это одно из самых популя…

1 комментарий

  1. Аватар

    g0rd1as

    13.03.2018 at 22:44

    А куда делись картинки?! Без них неинтересно и половина смысла теряется. 🙁

Оставить мнение