Assembler - язык неограниченных возможностей
0e1cc9b4

Полурезидентные программы


Полурезидентные программы — это программы, которые загружают и выполняют другую программу, оставаясь при этом в памяти, а затем, после того как загруженная программа заканчивается, они тоже заканчиваются обычным образом. Полурезидентная программа может содержать обработчики прерываний, которые будут действовать все время, пока работает загруженная из-под нее обычная программа. Так что, с точки зрения этой дочерней программы, полурезидентная программа функционирует как обычная резидентная. Эти программы удобно использовать для внесения изменений и дополнений в существующие программы, если нельзя внести исправления прямо в их исполнимый код. Так создаются загрузчики для игр, которые хранят свой код в зашифрованном или упакованном виде. Такой загрузчик может отслеживать определенные комбинации клавиш и обманывать игру, добавляя игроку те или иные ресурсы, или, например, находить код проверки пароля и выключать его.

В качестве примера напишем простой загрузчик для игры «Tie Fighter», который устранит ввод пароля, требующийся при каждом запуске игры. Разумеется, это условный пример, так как игра никак не шифрует свои файлы, и тот же эффект можно было достигнуть, изменив всего два байта в файле front.ovl. Единственное преимущество нашего загрузчика будет состоять в том, что он оказывается годен для всех версий игры (от «X-Wing» до «Tie Fighter: Defender of the Empire»).

; tieload.asm ; Пример полурезидентной программы - загрузчик, устраняющий проверку пароля ; для игр компании Lucasarts: ; "X-Wing", "X-Wing: Imperial Pursuit", "B-Wing", ; "Tie Fighter", "Tie Fighter: Defender of the Empire" ; .model tiny .code .386 ; для команды LSS org 100h ; СОМ-программа start: ; освободить память после конца программы (+ стек) mov sp,length_of_program ; перенести стек mov ah,4Ah ; функция DOS 4Ah mov bx,par_length ; размер в параграфах int 21h ; изменить размер выделенной памяти

; заполнить поля ЕРВ, содержащие сегментные адреса mov ax,cs mov word ptr EPB+4,ax mov word ptr EPB+8,ax mov word ptr EPB+0Ch,ax


; загрузить программу без выполнения mov bx,offset EPB ; ES:BX - EPB mov dx, offset filename ; DS:DX - имя файла (TIE.EXE) mov ax,4B01h ; функция DOS 4B01h int 21h ; загрузить без выполнения jnc program_loaded ; если TIE.EXE не найден, mov byte ptr XWING,1 ; установить флаг для find_passwd mov ax,4B01h mov dx,offset filename2 ; и попробовать BWING.EXE, int 21h jnc program_loaded ; если он не найден, mov ax,4B01h mov dx,offset filename3 ; попробовать XWING.EXE, int 21h jc error_exit ; если и он не найден ; (или не загружается по ; какой-нибудь другой причине) ; выйти с сообщением об ошибке program_loaded: ; Процедура проверки пароля не находится непосредственно в исполняемом файле ; tie.exe, bwing.exe или xwing.exe, а подгружается позже из оверлея front.ovl, ; bfront.ovl или fcontend.ovl соответственно. Найти команды, выполняющие чтение ; из этого оверлея, и установить на них наш обработчик find_passwd cld push cs pop ax add ax,par_length mov ds,ax xor si,si ; DS:SI - первый параграф после конца нашей ; программы (то есть начало области, в которую ; была загружена модифицируемая программа) mov di,offset read_file_code ; ES:DI - код для сравнения mov cx,rf_code_l ; CX - его длина call find_string ; поиск кода, jc error_exit2 ; если он не найден - выйти ; с сообщением об ошибке ; заменить 6 байт из найденного кода командами call find_passwd и nop mov byte ptr [si],9Ah ; CALL (дальний) mov word ptr [si+1],offset find_passwd mov word ptr [si+3],cs mov byte ptr [si+5],90h ; NOP

; запустить загруженную программу ; надо записать правильные начальные значения в регистры для ЕХЕ-программы ; и заполнить некоторые поля ее PSP mov ah,51h ; функция DOS 51h int 21h ; BX = PSP-сегмент загруженной программы mov ds,bx ; поместить его в DS mov es,bx ; и ES. Заполнить также поля PSP: mov word ptr ds:[0Ah],offset exit_without_msg mov word ptr ds:[0Ch],cs ; "адрес возврата" mov word ptr ds:[16h],cs ; и "адрес PSP - предка" lss sp,dword ptr cs:EPB_SSSP ; загрузить SS:SP jmp dword ptr cs:EPB_CSIP ; и передать управление на ; точку входа программы



XWING db 0 ; 1/0: тип защиты X-wing/Tie-fighter ЕРВ dw 0 ; запускаемый файл получает среду DOS от tieload.com, dw 0080h,? ; и командную строку, dw 005Ch,? ; и первый FCB, dw 006Ch,? ; и второй FCB EPB_SSSP dd ? ; начальный SS:SP - заполняется DOS EPB_CSIP dd ? ; начальный CS:IP - заполняется DOS

filename1 db "tie.exe",0 ; сначала пробуем запустить этот файл, filename2 db "bwing.exe",0 ; потом этот, filename3 db "xwing.exe",0 ; а потом этот

; сообщения об ошибках error_msg db "Ошибка: не найдены ни один из файлов TIE.EXE, " db "BWING.EXE, XWING. EXE",0Dh,0Ah,'$' error_msg2 db "Ошибка: участок кода не найден",0Dh,0Ah,'$'

; команды, выполняющие чтение оверлейного файла в tie.exe/bwing.exe/xwing.exe: read_file_code: db 33h,0D2h ; xor dx,dx db 0B4h,3Fh ; mov ah,3Fh db 0CDh,21h ; int 21h db 72h ; jz (на разный адрес в xwing и tie) rf_code_l = $-read_file_code

; Команды, вызывающие процедуру проверки пароля. ; Аналогичный набор команд встречается и в других местах, поэтому find_passwd ; будет выполнять дополнительные проверки passwd_code: db 89h,46h,0FCh ; mov [bp-4],ax db 89h,56h,OFEh ; mov [bp-2],dx db 52h ; push dx db 50h ; push ax db 9Ah ; call far passwd_l = $-passwd_code

error_exit: mov dx,offset error_msg ; вывод сообщения об ошибке 1 jmp short exit_with_msg error_exit2: mov dx,offset error_msg2 ; вывод сообщения об ошибке 2 exit_with_msg: mov ah, 9 ; Функция DOS 09h int 21h ; вывести строку на экран exit_without_msg: ; сюда также передается управление после ; завершения загруженной программы (этот адрес ; был вписан в поле PSP "адрес возврата") mov ah,4Ch ; Функция DOS 4Ch int 21h ; конец программы

; эту процедуру вызывает программа tie.exe/bwing.exe/xwing.exe каждый раз, когда ; она выполняет чтение из оверлейного файла find_passwd proc far ; выполнить три команды, которые мы заменили на call find_passwd xor dx,dx mov ah,3Fh ; функция DOS 3Fh int 21h ; чтение из файла или устройства deactivation_point: ; по этому адресу мы запишем код команды RETF, ; когда наша задача будет выполнена, pushf ; сохраним флаги push ds ; и регистры push es pusha push cs pop es mov si,dx ; DS:DX - начало только что прочитанного участка ; оверлейного файла mov di,offset passwd_code ; ES:DI - код для сравнения dec si ; очень скоро мы его увеличим обратно search_for_pwd: ; в этом цикле найденные вхождения эталонного кода ; проверяются на точное соответствие коду проверки пароля inc si ; процедура find_string возвращает DS:SI указывающим на ; начало найденного кода - чтобы искать дальше, надо ; увеличить SI хотя бы на 1 mov cx,passwd_l ; длина эталонного кода call find_string ; поиск его в памяти, jc pwd_not_found ; если он не найден - выйти ; find_string нашла очередное вхождение нашего эталонного кода вызова ; процедуры - проверим, точно ли это вызов процедуры проверки пароля cmp byte ptr [si+10],00h ; этот байт должен быть 00 jne search_for_pwd cmp byte ptr cs:XWING,1 ; в случае X-wing/B-wing jne check_for_tie cmp word ptr [si+53],0774h ; команда je должна быть здесь, jne search_for_pwd jmp short pwd_found check_for_tie: ; а в случае Tie Fighter - cmp word ptr [si+42],0774h ; здесь jne search_for_pwd pwd_found: ; итак, вызов процедуры проверки пароля найден - отключить его mov word ptr ds:[si+8],9090h ; NOP NOP mov word ptr ds:[si+10],9090h ; NOP NOP mov byte ptr ds:[si+12],90h ; NOP ; и деактивировать нашу процедуру find_passwd mov byte ptr cs:deactivation_point,0CBh ; RETF pwd_not_found: popa ; восстановить регистры pop es pop ds popf ; и флаги ret ; и вернуть управление в программу find_passwd endp



; процедура find_string ; выполняет поиск строки от заданного адреса до конца всей общей памяти ; ввод: ES:DI - адрес эталонной строки ; СХ - ее длина ; DS:SI - адрес, с которого начинать поиск ; вывод: CF = 1, если строка не найдена, ; иначе: CF = 0 и DS:SI - адрес, с которого начинается найденная строка find_string proc near push ax push bx push dx ; сохранить регистры do_cmp: mov dx,1000h ; поиск блоками по 1000h (4096 байт) cmp_loop: push di push si push cx repe cmpsb ; сравнить DS:SI со строкой pop cx pop si pop di je found_code ; если совпадение - выйти с CF = 0, inc si ; иначе - увеличить DS:SI на 1, dec dx ; уменьшить счетчик в DX jne cmp_loop ; и, если он не ноль, продолжить ; пройден очередной 4-килобайтный блок sub si,1000h ; уменьшить SI на 1000h mov ax,ds inc ah ; и увеличить DS на 1 mov ds,ax cmp ax,9000h ; если мы добрались до jb do_cmp ; сегментного адреса 9000h - pop dx ; восстановить регистры pop bx pop ax stc ; установить CF = 1 ret ; и выйти ; сюда передается управление, если строка найдена found_code: pop dx ; восстановить регистры pop bx pop ax clc ; установить CF = 0 ret ; и выйти find_string endp

end_of_program: lengtn_of_program = $-start+100h+100h ; длина программы в байтах par_length = length_of_program + 0Fh par_length = par_length/16 ; длина программы в параграфах end start


Содержание раздела