"Заражение (или вакцинация) EXE-файлов" by DrMAD Милостивые государи! В вирусологии (и не только) давно известны способы внедрения своих исполнимых фрагментов в код ЕХЕ-программы. Метод с приписыванием фрагмента к концу ЕХЕ-файла и модифицированием нескольких полей в его заголовке (обычно это ReloCS, ExeIP, PageCnt и PartPag, иногда еще ReloSS и ExeSP) даже получил наименование "стандартного". Между тем, способ имеет ряд крупных недостатков (вспомните, например, проблему "оверлейных" файлов). Не случайно все "прогрессивное человечество" (авторы пристыковочных защит от НСК, вакцин, упаковщиков и пр.) давно отказалось от "стандартного" метода. Ими активно и с успехом используется способ собственного загрузчика, включающий саостоятельное размещение кода программы в памяти, настройку перемещаемых адресов и пр. Разумеется, это сложней, но в несколько раз надежней! Что же требуется, чтобы выполнить ряд функций командного процессора и самостоятельно загрузить ЕХЕ-программу? Обратимся за справочной информацией к нестареющему TechHelp: Смещ.Длина Содержимое **** ***** *************************************************** +-------+ +0 2|4Dh 5aH|"подпись" файла .EXE ('MZ') +---+---+ +2 2|PartPag|длина неполной последней страницы +---+---+ +4 2|PageCnt|длина образа в 512-байтниках, включая заголовок +---+---+ +6 2|ReloCnt|число элементов в таблице перемещения +---+---+ +8 2|HdrSize|длина заголовка в 16-байтовых параграфах +---+---+ +0aH 2|MinMem |минимум требуемой памяти за концом программы +---+---+ +0cH 2|MaxMem |максимум требуемой памяти за концом программы +---+---+ +0eH 2|ReloSS |сегментное смещениестека (для установки SS) +---+---+ +10H 2|ExeSP |значение регистра SP при запуске +---+---+ +12H 2|ChkSum |К/сумма (отрицательная сумма всех слов в файле) +---+---+ +14H 2|ExeIP |значение регистра IP при запуске +---+---+ +16H 2|ReloCS |сегментное смещение кода (для установки CS) +---+---+ +18H 2|TablOff|смещение в файле 1-го элемента перемещения +---+---+ +1aH 2|Overlay|номер оверлея (0 для главного модуля) +---+---+ 1cH размер форматированной порции заголовка EXE +-------+-------+ - - Таблица перемещения. Начало + ? 4*? | смещ. сегмент| по смещению [EXE+18H]. Имеет +---+---+---+---+ - - [EXE+6] 4-байтовых элемента. + ? ? пропуск до границы параграфа + ? ? начало образа программы Поскольку EXE-файл может быть загружен в любой сегмент, все абсолютные сегментные ссылки (FAR CALL, далекие указатели, ссылки типа MOV AX,data_seg) должны быть приведены к адресам памяти, соответствующим загрузке. Ниже приведены шаги, используемые программой загрузки DOS (функция 4bH ) при загрузке файла EXE: 1. создать PSP посредством функции DOS 26H 2. прочитать 1cH байт файла EXE (форматированную порцию заголовка EXE) в локальную область памяти 3. определить размер модуля = ((PageCnt*512)-(HdrSize*16)) - PartPag 4. определить файловое смещение загружаемого модуля = (HdrSize * 16) 5. выбрать сегментный адрес, START_SEG, для загрузки (обычно PSP + 10H) 6. считать модуль в область памяти, начинающуюся с адреса START_SEG:0000 7. LSEEK (уст. указатель файла) на начало таблицы перемещения (TablOff) 8. для каждого элемента перемещения (ReloCnt): a. считать элемент как два 16-битовых слова (I_OFF,I_SEG) b. вычислить RELO_SEG=(START_SEG+I_SEG) (адрес перемещаемой ссылки) c. извлечь слово по адресу RELO_SEG:I_OFF (прочитать текущее значение) d. прибавить START_SEG к этому слову (выполнить привязку сегмента) e. поместить результат обратно по его адресу (RELO_SEG:I_OFF) 9. Распределить память для программы согласно MaxMem и MinMem 10. Инициализировать регистры и запустить программу: a. ES = DS = PSP b. AX = отражает корректность идентификаторов дисков в командной строке c. SS = START_SEG+ReloSS, SP = ExeSP d. CS = START_SEG+ReloCS, IP = ExeIP (команды: PUSH seg; PUSH offset; RETF) Замечание: Последние добавления в формат EXE, особенно версии EXE-файлов "CodeView" и "Windows", содержат дополнительную информацию, включенную в выполнимый файл. Эти добавления не отражены в этой версии TECH Help!. ************************************************************** Как видим, все достаточно просто и понятно. Посмотрим, как это выглядит на практике. С давних пор на моем винчестере поселилась утилита e2com, созданная Сергеем Евтушенко (Elisoft Computer Group). Она предназначена для "преобразования" небольших EXE-программ в СОМ-формат. Достаточно долгое время я считал, что автор использовал какие-то крайне сложные преобразования исходного кода... пока не обратил внимание, что в результате длина преобразованной программы увеличивается всего на несколько сотен байт (208 байтов). Любопытство побудило "заразить" маленькую простую программку и дизассемблировать ее. Выяснилось, что вся операция "заражения-преобразования" включает всего одно простое действие: РАЗМЕЩЕНИЕ 208-БАЙТНОГО КОДА ЗАГРУЗЧИКА В НАЧАЛЕ ПРОГРАММЫ И ПРИПИСЫВАНИЕ К НЕМУ СОВЕРШЕННО НЕИЗМЕНЯЕМОГО ФАЙЛА ИСХОДНОЙ ПРОГРАММЫ. И ВСЕ !!! Разумеется, "заражению" могут подвергнуться лишь небольшие EXE-программы. Есть и другие недостатки. Зато все они компенсируются двумя огромными достоинствами: ПРОСТОТОЙ ОПЕРАЦИИ "ЗАРАЖЕНИЯ" и "ТОЛЕРАНТНОСТЬЮ" К ОВЕРЛЕЙНОСТИ. Короче, вот Вам дизассемблированный и комментированный листинг загрузочного кода программы e2com (может быть, кто-нибудь когда-нибудь уже делал подобное, но лично мне об этом неизвестно): ;########################################################################## ;## ## ;## PROBA ## ;## ## ;## Disassembled & commented by Constantin E. Climentieff, Samara 1997 ## ;## ## ;########################################################################## ; The following equates show data references outside the range of the program. = 0002 data_1e equ 2 ; (9059:0002=0) = 00EF data_2e equ 0EFh ; (9059:00EF=0) seg_a segment byte public assume cs:seg_a, ds:seg_a org 100h proba proc far ;-------------------------------------------------------------------------------------------------------------------- ; Сейчас в памяти имеет место быть следующая конфигурация: ; +---------------------------------+ ; | PSP загрузчика | ; +---------------------------------+ ; | Код загрузчика | ; +---------------------------------+ ; | Заголовок EXE-программы | ; +---------------------------------+ ; | Relocation Table EXE-программы | ; +---------------------------------+ ; | Код и данные EXE-программы | ; +---------------------------------+ ;----------------------------------------------------------------------------------------------------------------------------- 0100 start: 0100 ъBE 01D0 mov si,offset data_6 ; Адресуемся на начало EXE-программы. 0103 8B 04 mov ax,[si] ; Берем первое ее слово, оно д.б. 'MZ' 0105 3D 5A4D cmp ax,5A4Dh ; Это была EXE-программа? 0108 74 01 je loc_3 ; Если да, то все ОК - продолжаем 010A loc_ret_2: ; Отваливаем 010A ъCB retf ; 010B loc_3: ; ;----------------------------------------------------------------------------------------------------------------------------- 010B 8B C6 mov ax,si ; По известному смещению начала 010D B1 04 mov cl,4 ; сохраненной EXE-программы 010F D3 E8 shr ax,cl ; и текущему сегменту вычисляем сегмент 0111 8C DB mov bx,ds ; местоположения сохраненной 0113 03 C3 add ax,bx ; EXE-программы. ;----------------------------------------------------------------------------------------------------------------------------- 0115 03 44 08 add ax,[si+8] ; Прибавляем к нему размер заголовочной части 0118 A3 01CC mov data_4,ax ; EXE-программы и ховаем в data_4, ; теперь там у нас - сегмент кода EXE-программы. ;----------------------------------------------------------------------------------------------------------------------------- 011B 8C C8 mov ax,cs ; По текущему сегменту PSP вычисляем 011D 05 0010 add ax,10h ; сегмент кода загрузчика и 0120 A3 01CE mov data_5,ax ; ховаем в data_5. ;----------------------------------------------------------------------------------------------------------------------------- 0123 8B 44 10 mov ax,[si+10h] ; По известным значениям ReloSS и ExeSP 0126 B1 04 mov cl,4 ; из заголовка EXE-программы вычисляем 0128 D3 E8 shr ax,cl ; положение стека EXE-программы. 012A 03 06 01CE add ax,data_5 ; 012E 03 44 0E add ax,[si+0Eh] ; ;----------------------------------------------------------------------------------------------------------------------------- 0131 ъBB 0002 mov bx,data_1e ; bx := [PSP:2], т.е. сегмент конца 0134 8B 1F mov bx,[bx] ; выделенного блока памяти. 0136 3B D8 cmp bx,ax ; Стек умещается в выделенном блоке? 0138 73 02 jae loc_4 ; Да - продолжаем. 013A EB CE jmp short loc_ret_2 ; Нет - отваливаем, вероятно, не хватит памяти. 013C loc_4: ; 013C A1 01CE mov ax,data_5 ; ax := сегмент кода загрузчика. 013F 8B DE mov bx,si ; bx := смещение заголовка EXE-программы. 0141 03 5C 18 add bx,[si+18h] ; bx + длина заголовочной части, получаем адрес кода. 0144 8B 4C 06 mov cx,[si+6] ; cx := количество элементов в Relocation Table. 0147 E3 13 jcxz loc_6 ; Если Relocation Table пуста, то пропускаем. ;------------------------------------------------------------------------------------------------------------------------------ 0149 locloop_5: ; Здесь Relocation Table не пуста, ее адрес в BX. 0149 8B 57 02 mov dx,[bx+2] ; Берем I_SEG. 014C 03 16 01CC add dx,data_4 ; Прибавляем сегмент EXE-кода. 0150 8E C2 mov es,dx ; Это будет сегмент модифицируемого слова. 0152 8B 3F mov di,[bx] ; Берем I_OFS - это его смещение. 0154 26: 01 05 add es:[di],ax ; Настраиваем ссылку, прибавляя сегмент загрузчика. 0157 83 C3 04 add bx,4 ; Смещаемся на следующий элемент Relocation Table. 015A E2 ED loop locloop_5 ; И продолжаем в цикле настройку перемещаемых ссылок. ;------------------------------------------------------------------------------------------------------------------------------- 015C loc_6: ; xref 9059:0147 015C 56 push si ; (7) 015D 0E push cs ; 015E 07 pop es ; 015F ъBF 00EF mov di,data_2e ; Адрес хвоста PSP 0162 ъBE 01A5 mov si,1A5h ; Адрес кода завершения. 0165 B9 0011 mov cx,11h ; Длина кода завершения. 0168 F3/ A4 rep movsb ; Копируем код завершения в редко используемый хвост PSP. 016A 5E pop si 016B A1 01CE mov ax,data_5 ; Сегмент кода загрузчика. 016E 03 44 16 add ax,[si+16h] ; Прибавляем ReloCS, получаем сегмент точки входа и 0171 50 push ax ; помещаем в стек. (6) 0172 8B 44 14 mov ax,[si+14h] ; Берем ExeIP - смещение точки входа и 0175 50 push ax ; помещаем в стек. (5) 0176 1E push ds ; (4) 0177 1E push ds ; (3) 0178 A1 01CE mov ax,data_5 ; Сегмент кода загрузчика. 017B 8E C0 mov es,ax ; es := сегмент кода загрузчика (PSP+10h). 017D 33 FF xor di,di ; di := 0 017F 03 44 0E add ax,[si+0Eh] ; Смещаем сегмент стека на длину загрузчика. 0182 50 push ax ; Сохраняем новое значение ReloSS в стеке. (2) 0183 8B 44 10 mov ax,[si+10h] ; Берем ExeSp 0186 50 push ax ; и сохраняем в стеке. (1) 0187 8B 5C 04 mov bx,[si+4] ; Размер файла в 512-байтниках. 018A 8B 44 08 mov ax,[si+8] ; Размер заголовочной части в 16-байтниках. 018D B1 05 mov cl,5 018F D3 E8 shr ax,cl ; Длина заголовка/32 = она же в целых 512-байтниках. 0191 2B D8 sub bx,ax ; Вычисляем полезную длину EXE-программы в 512-байтниках. 0193 B1 08 mov cl,8 ; 0195 D3 E3 shl bx,cl ; Длина*512 = длина в байтах! 0197 8B CB mov cx,bx ; Регистр cx должен содержать длину кода для movsw! 0199 A1 01CC mov ax,data_4 ; 019C 8E D8 mov ds,ax ; ds := сегмент EXE-кода 019E 33 F6 xor si,si ; si := 0 01A0 ъBB 00EF ;* mov bx,offset loc_1 ; Переходим на копию кода завершения, которая теперь в PSP. 01A0 BB EF 00 db 0BBh,0EFh, 00h 01A3 FF E3 jmp bx ;*Register jump ; ----------------------------------------------------------------------------------------------------------------------------- ; Код завершения, копируется в другое место, потом на него передается управление. ; Сейчас ds:si указывает на EXE-код, es:di - на код загрузчика, сразу после PSP. 01A5 F3/ A5 rep movsw ; Копируем код EXE-программы наверх, затирая загрузчик. 01A7 58 pop ax ; ExeSP (1) 01A8 5B pop bx ; ReloSS (2) 01A9 07 pop es ; Старый ds (3) 01AA 1F pop ds ; Старый ds (4) 01AB 59 pop cx ; ExeIP (5) 01AC 5A pop dx ; ReloCS (6) 01AD FA cli ; 01AE 8E D3 mov ss,bx ; Настраиваем 01B0 8B E0 mov sp,ax ; положение стека по (1),(2) 01B2 FB sti ; 01B3 52 push dx ; Повторно помещаем в стек 01B4 51 push cx ; точку входа. 01B5 CB retf ; И переходим на нее командой retf ! ; Обратите внимание, в стеке осталось слово (7), т.е. 0 ! ; А ax,bx,cx,dx остались не совсем корректные. K ;-------------------------------------------------------------------------------------------------------------------------------------- ; Это всего лишь авторский копирайт 01B6 28 43 29 20 43 6F copyright db '(C) Copyright Elisoft.' 01BC 70 79 72 69 67 68 01C2 74 20 45 6C 69 73 01C8 6F 66 74 2E ;-------------------------------------------------------------------------------------------------------------------------------------- 01CC 0000 data_4 dw 0 ; xref 9059:0118, 014C, 0199 01CE 0000 data_5 dw 0 ; xref 9059:0120, 012A, 013C, 016B ; 0178 ;-------------------------------------------------------------------------------------------------------------------------------------- ; А отсюда начинается пристыкованное к концу содержимое файла EXE-программы 01D0 4D data_6 db 4Dh ; xref 9059:0100 01D1 5A 40 00 02 00 01 db 5Ah, 40h, 00h, 02h, 00h, 01h 01D7 00 20 00 00 00 FF db 00h, 20h, 00h, 00h, 00h,0FFh 01DD FF 00 00 20 00 91 db 0FFh, 00h, 00h, 20h, 00h, 91h 01E3 29 00 00 03 00 1E db 29h, 00h, 00h, 03h, 00h, 1Eh 01E9 00 00 00 01 00 01 db 00h, 00h, 00h, 01h, 00h, 01h 01EF 00 03 db 00h, 03h 01F1 01DF[00] db 479 dup (0) 03D0 2A 2A 2A 2A 2A 2A db '********************************' 03D6 2A 2A 2A 2A 2A 2A 03DC 2A 2A 2A 2A 2A 2A 03E2 2A 2A 2A 2A 2A 2A 03E8 2A 2A 2A 2A 2A 2A 03EE 2A 2A 03F0 48 65 6C 6C 6F 21 db 'Hello!$' 03F6 24 03F7 0009[00] db 9 dup (0) 0400 B8 02 00 8E D8 BA db 0B8h, 02h, 00h, 8Eh,0D8h,0BAh 0406 00 00 B4 09 CD 21 db 00h, 00h,0B4h, 09h,0CDh, 21h 040C B4 4C CD db 0B4h, 4Ch,0CDh 040F 21 db 21h proba endp seg_a ends end start #################### CROSS REFERENCE - KEY ENTRY POINTS ################### seg:off type label ---- ---- ---- -------------------------------- 9059:0100 far start ################## Interrupt Usage Synopsis ################## No Interrupts used. ################## I/O Port Usage Synopsis ################## No I/O ports used. |