[Назад] [Далее]

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

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

В качестве примера напишем простой загрузчик для игры «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

п»ї
"target=_blank><\/a>") //-->