Известно, что фасм по умолчанию генерит .bin файл, содержащий код без каких-либо заголовков, т.е. если написать в фасме "use32/pushd 0x12345678", после нажатия shift-f9 он сгенерит файл из 5 байт: "68 78 56 34 12". Но каждый раз, переключаясь на коммандер, и нажимая просмотр (F3-3) мне вспоминалось что в фасме есть замечательные директивы "load ..." и "display". В результате были написаны макросы: Code (Text): macro _display_byte_hex_ prefix,B,postfix { local C C="0"+(B) shr 4 if C>"9" C=C+"A"-"9"-1 end if display prefix,C C="0"+(B) and 0x0F if C>"9" C=C+"A"-"9"-1 end if display C,postfix } macro _dump_ A,N,delim { local B repeat N load B byte from A+%-1 _display_byte_hex_ "",B,delim end repeat display 13,10 } macro PATCH va* { use32 virtual at va display "-----[ Begin of Patch at ",`va," ]-----",13,10 define _need_line_ 0} macro DUMP_PATCH delim* { match =1,_need_line_ \{display "-----------------------",13,10\} _dump_ $$, $-$$, delim _display_byte_hex_ "-----[ End of Patch, size = 0x", $-$$, <" ]-----",13,10> end virtual } macro P [line]{ common local A A=$ line _display_byte_hex_ "+",A-$$,":" irps e,line \{ display " ",\`e \} display "; " _dump_ A, $-A, " " define _need_line_ 1 } которые для файла Code (Text): include "patch.inc" PATCH 0x12345678 pushd 0x12345678 popd [0xABCDEF] DUMP_PATCH "," PATCH 0x30001000 P pushd 0x12345678 P popd [0xABCDEF] DUMP_PATCH "" выдают Code (Text): -----[ Begin of Patch at 0x12345678 ]----- 68,78,56,34,12,8F,05,EF,CD,AB,00, -----[ End of Patch, size = 0x0B ]----- -----[ Begin of Patch at 0x30001000 ]----- +00: pushd 0x12345678; 68 78 56 34 12 +05: popd [ 0xABCDEF ]; 8F 05 EF CD AB 00 ----------------------- 68785634128F05EFCDAB00 -----[ End of Patch, size = 0x0B ]----- Может у кого есть идеи по улучшению данного метода создания патчей?
Изначально идея была такой: получить в фасме байты патча, скопипастить их например в винхекс, и уже им патчить. Но потом меня осенило, что фасм все же предназначен для написания прог, и я подумал, что бы фасму не генерить прогу которая сама пропатчит файл. После нескольких дней копания в хелпе фасма был сделан инклуд patch.inc (см. аттач). В нем содержится набор макросов позволяющих выполнять следующие задачи: - отображение байт откомпилированного кода в окне вывода компилятора, - запись байт откомпилированного кода в целевой PE файл. Для этого используются две конструкции, первая: DUMP address [_D] codeline1 ... DUMP_END Компилирует строки между DUMP и DUMP_END, считая что они начинаются с адреса address. Выводит размер блока в байтах, группирует байты в двойные слова и выводит примерный код патча: mov dword[address], dw1 / mov dword[address+4], dw2 / ... Если строка начинается с _D, в окне компилятора выводятся байты этой строки. Вторая конструкция FILE "file.name" PATCH address1 codeline1 ;фрагмент 1 ... PATCH address2 codeline2 ;фрагмент 2 ... PATCH_END Эта конструкция создает программу, которая записывает в файл "file.name" байты кода фрагментов, пересчитывая виртуальные адреса address в файловые смещения. Если address меньше rva 1й секции, он рассматривается как файловое смещение. При запуске программа выводит список патчей (адрес:размер) и спрашивает подтверждение патча файла. Также, при разборе заголовка целевого PE файла создаются константы __base - база файла __entrypoint - виртуальный адрес точки входа __import - виртуальный адрес таблиц импорта __PE - смещение PE заголовка в файле __section_table - смещение таблицы секций в файле
Пример использования. Есть запакованная длл, в ее запакованной области по VA 0x30ABCDE6 расположен вызов функции, которая почемуто возвращает не 1 а 0. Создаем "скрипт" Code (Text): include "patch.inc" ;0) mem patch, replaces ;___:30ABCDE6 00C call Get_@eax_AL ;___:30ABCDEB 00C mov _xxx_, al DUMP 0x30ABCDE6 _D mov al,1 _D jmp short 0x30ABCDEB DUMP_END компилим, в окне компилятора видим Ищем в секции ресурсов место с адресом покрасивее, и дописываем в наш "скрипт": 1) разрешение выполнения кода в секции ресурсов 2) замена адрес перехода на точке входа 3) код подмены адреса возврата из DllEntry() и патча нужных байт Code (Text): FILE "target.dll" ;1) fix .rsrc (#3) section characteristics PATCH __section_table+0x24+(3-1)*0x28 dd 0xE0000040 ;2) DllEntryPoint, replaces ;jmp 0x3006759D ;EP_0 PATCH __entrypoint jmp _new_EP ;3) Main code PATCH 0x30062700 _new_EP: popd [_saved_ret_addr] pushd _make_patch jmp 0x3006759D ;EP_0 _make_patch: mov dword[0x30ABCDE6], 0x01EB01B0 ;<<<< mem patch jmp dword[_saved_ret_addr] align 4 _saved_ret_addr dd ? PATCH_END Компилим, запускаем, видим окошко "...нажмите ОК чтобы патчить", и получаем пропатченый файл.
После долгих медитаций мне открылось, что все вышенаписанное есть муть, т.к. неоптимально и избыточно. Во-первых о патчах в памяти. Конструкция DUMP va / <код> /END_DUMP, выводящая в окне компилятора число байт откомпилированного кода, с кодом самого патча (mov [va],xxx), интересна, но: 1) mov [va],xxx надо вручную кобпировать в нужное место 2) если какой-то патч в памяти занимает больше 4х байт, то это должен быть jmp или call на код перехвата (5 байт). В крайнем случае это должен быть mov edi,va/../rep movs. Тем не менее, задача патча в памяти остается, поэтому есть смысл встраивать код патча в памяти в основной код патча: MEM va/dd 1,2/END_MEM. Результатом работы такой конструкции будет код mov dword[va],1/mov dword[va+4],2. Также есть смысл в наборе отдельных макросов под частные задачи, например подмена относительных адресов перехода в памяти: macro FIX_REL va,hook { mov dword[va+1],hook-(va+5) } Во-вторых, макрос "_D xxx" компилящий строку xxx и выводящий ее байты, хорош сам по себе, и может использоваться в любом месте кода. Кроме того, для него нужна куча других макросов, поэтому его можно засунуть в отдельный инклуд "%fasminc%\tool.inc". Во-третьих, необходимо большое количество мелких изменений, улучшающих синтаксис скрипта патча, например макрос macro DWORD raw,value{ PATCH raw / dd value } Возможно следует устанавливать базу программы равной базе целевого файла, чтобы в коде патча работала директива rva. Также, следует пересмотреть имена макросов. Пока что, пример скрипта выглядит так: Code (Text): include "%fasminc%\patch.inc" FILE "xxx.dll" DWORD __section_table+0x24+(3-1)*0x28, 0xE0000040 ;1) fix .rsrc (#3) section characteristics DWORD __entrypoint_ofs,rva _new_EP ;2) Fix DllEntryPoint PATCH 0x30062700 ;3) Main code _new_EP: popd [_saved_ret_addr] pushd _make_patch jmp __entrypoint ;--------------------------- _make_patch: ;m1) replace ___:30033B26 call Get_@eax_AL ; ___:30033B2B mov _xxx_, al MEM 0x30033B26 mov al,1 jmp short 0x30033B2B MEM_END ;m2) set function hook, replace ___:3003ABCD call 3003DEF0 FIX_REL 0x3003ABCD,_hook jmp dword[_saved_ret_addr] ;--------------------------- _hook: ... jmp 0x3003DEF0 ;original func ;--------------------------- align 4 _saved_ret_addr dd ? PATCH_END
Чтоб зря не валялось... инклуд tool.inc В нем макрос "DUMP <строка>", который выводит текущее смещение ($), саму строку и откомпилированные байты строки в окно компилятора (+их количество). Можно использовать просто "DUMP" чтобы посмотреть $. Также в инклуде есть макрос INLINE - позволяет писать несколько инструкций в одной строчке, через "/". Макросы можно использовать совместно, т.е. DUMP INLINE xxx/yyy/zzz Пример использования: Code (Text): include "%fasminc%\tool.inc" use32 db 0x23 dup (0) DUMP ;displays "00000023:" DUMP jmp $ ;displays "00000023: jmp $; EB FE (#02)" ;displays "00000025: INLINE mov ecx , 5 / @@ : push ecx / loop @r; B9 05 00 00 00 51 E2 FD (#08)" DUMP INLINE mov ecx,5 / @@:push ecx /loop @r
Не совсем по теме, но в продолжение идеи макроса выводящего откомпилированный код Code (Text): macro DUMP.BEGIN comment{DUMP.start=$} macro DUMP.END { local ..B,..C,..N ..N=$-DUMP.start display ";========================",13,10 repeat ..N if ((%-1) and 0x0F)=0 display "db " end if load ..B byte from DUMP.start+%-1 C="0"+(..B) shr 4 if C>"9" C=C+"A"-"9"-1 end if display "0x",C C="0"+(..B) and 0x0F if C>"9" C=C+"A"-"9"-1 end if display C if ((%-1) and 0xF)=0xF display 13,10 else if %=..N display 13,10 else display "," end if end repeat display ";========================",13,10} - пара макросов выводящих код между собой в виде db 0x6A,0xF5,0xFF,0x15,0x3C,0x10,0x40,0x00,0xA3,0x60,0x1B,0x40,0x00,0x6A,0xF6,0xFF db 0x15,0x3C,0x10,0x40,0x00 ... Полезно для скрытия исходного кода в исходнике. Скомпилил, вставил вместо кода его байты и никто не узнает что там было