Рассмотрим сущность перекрывающегося кода и применим его для защиты
нашей программы.
Рассмотрим листинг:
mov eax,04ebh
jmp $-4
next:
Что же делает эта часть кода?
- В eax помещается значение 04ebh(это опкод команды jmp $+4)
- jmp $-4, переходит на значение 04ebh
- jmp $+4, переходит на метку next
Таким образом, можно строить сколь угодно сложный перекрывающийся код. Ещё в
перекрывающемся коде есть один большой плюс, с его помощью можно прятать неприятные для кодоанализаторов инструкции.
Теперь напишем функцию для нашей программы, которая использует все прелести
перекрывающегося кода. Рассмотрим листинг:
int a_dizasm()
{
asm mov eax,04ebh
asm jmp $-4
}
Благодаря применению антиотладочных и антидизассемблерных трюков наша программа
стала гораздо более защищённой. Но всё равно этого недостаточно. Поэтому я
рекомендую каждому программисту разработать свою внешнюю защиту, которая будет
сочетать трюки, охраняющие от отладки и дизассемблирования. А так же
использовать полное криптование программного кода.
Для демонстрации всех описанных выше возможностей, мной была написана
следующая программа:
;------------------------------------
; CODE SECURE V. 1.0 BY SLON
;(+) AntiDizasm
;(+) AntiDebug
;(+) Crypt 8 bit XOR
;(+) Armoured by SEH
;
;------------------------------------
.386
.model flat
extrn ExitProcess:PROC
extrn GetFileSize:PROC
extrn CreateFileA:PROC
extrn CreateFileMappingA:PROC
extrn MapViewOfFile:PROC
extrn UnmapViewOfFile:PROC
extrn CloseHandle:PROC
extrn SetFilePointer:PROC
extrn SetEndOfFile:PROC
extrn GetTickCount:PROC
extrn printf:PROC
extrn GetCommandLineA:PROC
extrn lstrcpyA:PROC
.data
start:
;------------------------------------
call delta ;
delta: ; Получаем дельта смещение
pop ebp ;
sub ebp,offset delta ;
push offset message2
call printf
call Work_command
call infect
;------------------------------------
End_it:
push 0
call ExitProcess
;------------------------------------
infect:
lea esi,[ebp + name12] ; esi = имени файла
OpenFile:
push 0
push 00000080h
push 3
push 0
push 00000001h OR 00000002h
push 40000000h OR 80000000h
push esi
call CreateFileA ; Открытие файла
; для чтения/записи
mov [ebp + offset fHnd],eax ; Сохранение хэндла файла
cmp eax,-1
je InfectionError
;------------------------------------
push 0
push eax
call GetFileSize
mov [ebp + offset filesize], eax ; Сохраняем размер файла
add eax, finish - start + 1000h ; filesize + decryptor
mov [ebp + offset memory], eax ; +workspace=memory
;------------------------------------
CreateFileMapping:
; выделяем память
push 0 ; имя файла хэндл = 0
push dword ptr [ebp+ offset memory] ; макс. размер = memory
push 0 ; минимальный размер = 0
push 4 ; доступ чтение/запись
push 0 ;
push dword ptr [ebp + offset fHnd]
call CreateFileMappingA
; eax = хэндл
mov [ebp + offset mHnd],eax
or eax,eax
jz CloseFile
MapViewOfFile1:
push dword ptr [ebp + offset memory] ; количество памяти
; для работы
push 0 ;
push 0 ;
push 2 ; Режим записи
push eax ; хэндл
call MapViewOfFile ; Вызываем функцию
or eax,eax
jz CloseMap
mov esi,eax ;
mov [ebp + offset mapaddress],esi ; Сохраняем базу памяти
DoSomeChecks:
cmp word ptr [esi],'ZM' ; Это EXE файл?
jne UnmapView
cmp word ptr [esi + 38h],'nf' ; Уже усиленный файл?
je UnmapView
OkGo:
mov ebx,dword ptr [esi + 3ch]
cmp ebx,200h
ja UnmapView
add ebx,esi
cmp word ptr [ebx],'EP' ; Это PE файл ?
jne UnmapView
mov [ebp + offset PEheader],ebx ; сохраняем PE заголовок
mov esi,ebx
mov eax,[esi + 28h]
mov [ebp + offset oldip],eax ; Сохраняем старую точку
; входа
mov eax,[esi + 34h]
mov [ebp + offset imagebase],eax ; Сохраняем
; виртуальный адрес
; начала программы
call find_code
;------------------------------------
LocateBeginOfLastSection:
mov ebx,[esi + 74h] ;
shl ebx,3 ;
xor eax,eax
mov ax,word ptr [esi + 6h] ; Количество объектов
dec eax ; (нам нужен последний-1
mov ecx,28h ; заголовок секции)
mul ecx ; * размер заголовка
add esi,78h ; теперь esi указывает
; на начало последнего
add esi,ebx ; заголовка секции
add esi,eax
ChangeLastSectionHeader:
or dword ptr [esi + 24h],00000020h or 20000000h or 80000000h
NewPhysicalSize:
mov eax,dword ptr [esi+10h] ; Старый физический
; размер
add eax,finish-decrypt
mov dword ptr [esi+10h],eax ; Сохраняем его
VirtualSizeCheck:
mov edi,dword ptr [esi + 8h] ; Получаем старый
cmp eax, edi ; виртуальный размер
jge NewVirtualSize
VirtualSizeIsVirtual:
add edi,finish-decrypt
mov eax,edi
NewVirtualSize:
mov ecx,[ebp + offset PEheader]
mov ecx,[ecx + 38h]
div ecx ; и выравниваем к
inc eax ; секции выравнивания
mul ecx
mov [esi + 8h],eax ; Сохраняем новое
; значение
NewAlignedImageSize:
mov eax,dword ptr [esi + 0ch] ; получаем виртуальное
; смещение
add eax,dword ptr [esi + 8h] ; + новый виртуальный
; размер
mov [ebp+ imagesize],eax ; = новый виртуальный
; размер
NewAlignedFileSize:
mov eax,dword ptr [esi+10h] ; получаем новый
; физический размер
add eax,dword ptr [esi + 14h] ; добавляем смещение
; физического
mov [ebp + offset filesize],eax ; размера = размер файла
CalculateNewIp:
mov eax,dword ptr [esi+10h] ; новый физический
; размер
add eax,dword ptr [esi + 0ch] ; + виртуальное смещение
sub eax,finish-decrypt ; - размер декриптора
mov [ebp + newip],eax ; новая точка входа
CopyDecryptorToEndOfFile:
mov edi,dword ptr [esi+10h] ; Новый физический
; размер
sub edi,finish-decrypt
add edi,[ebp + offset mapaddress] ; mapaddress
add edi,[esi + 14h] ; добавляем смещение
; потоковых данных
lea esi,[ebp + decrypt] ;
mov ecx,(finish-decrypt)/4 + 4
cld
rep movsd
UpdatePEHeaderWithChanges:
mov esi,dword ptr [ebp + offset mapaddress]
mov word ptr [esi + 38h],'nf' ; Устанавливаем метку
mov esi,dword ptr [ebp + offset PEheader] ; изменённости
mov eax,dword ptr [ebp + offset newip]
mov [esi + 28h],eax ; Устанавливаем новую
; точку входа
mov eax,[ebp + offset imagesize] ;
mov [esi + 50h],eax ; Устанавливаем новый
; виртуальный размер
UnmapView:
push dword ptr [ebp + offset mapaddress] ;
call UnmapViewOfFile ; Закончиваем изменение
; файла в памяти и ложим
; его обратно
CloseMap:
push dword ptr [ebp + offset mHnd] ;
call CloseHandle ; Закрываем хэндл
push 0
push 0
push dword ptr [ebp + offset filesize] ;
push dword ptr [ebp + offset fHnd] ; Переходим в конец
; файла
call SetFilePointer ;
push dword ptr [ebp + offset fHnd] ;
call SetEndOfFile ; Устанавливаем символ
; конца файла
;------------------------------------
CloseFile:
push dword ptr [ebp + offset fHnd] ;
call CloseHandle ; Закрываем файл
InfectionError:
ret ; Выходим из процедуры
;------------------------------------
find_code:
pusha
mov esi,ebx ; Теперь esi указывает
; на PE
mov edi,esi ;
mov ebx,[esi + 74h] ;
shl ebx,3 ; Получаем
xor eax,eax
mov ax,word ptr [esi + 6h] ; Количество объектов
find2:
mov esi,edi
dec eax
push eax
mov ecx,28h ;
mul ecx ;
add esi,78h ; теперь esi указывает
; на начало последнего
add esi,ebx ; заголовка секции
add esi,eax ;
mov eax,[ebp+oldip] ; В eax точку входа
mov edx,dword ptr[esi+0ch] ; В edx адрес куда будет
; мапиться
; текущая секция
cmp edx,eax ; Проверяем
pop eax ; Вынимаем из стэка eax
jg find2 ; Если больше ищем дальше
add edx,dword ptr[esi+08h] ; Добавляем виртуальный
; размер секции
cmp edx,[ebp+oldip] ; Проверяем
jl find2 ; Если меньше ищем
; дальше
mov edx,dword ptr[esi+0ch] ; Далее вычисляем
; физическое
mov eax,[ebp+oldip] ; смещение кода в файле
sub eax,edx ;
add eax,dword ptr[esi+14h] ;
add eax,[ebp+mapaddress] ; И добавляем базу
; памяти
mov [ebp+start_code],eax ; Сохраняем начало кода
mov eax,dword ptr[esi+10h] ; Сохраняем размер кода
mov [ebp+size_code],eax ;
or dword ptr [esi + 24h],00000020h or 20000000h or 80000000h
; Меняем аттрибуты кодовой секции
call crypt
popa
ret ; Возврат из процедуры
;------------------------------------
crypt: ; Процедура криптования
; Исполнимого кода
mov esi,[ebp+start_code]
mov edi,esi
xor ecx,ecx
call GetTickCount
and eax,12345678h
mov [ebp+key],al
crypt1:
lodsb
xor al,[ebp+key]
stosb
inc ecx
cmp ecx,[ebp+size_code]
jl crypt1
ret
;------------------------------------
Work_command:
; Процедура получения аргументов
; командной строки
; (на C это argv[0], argv[1])
call GetCommandLineA
push eax
push offset cmd_line
call lstrcpyA
lea esi,cmd_line
lea edi,host_name
test1:
lodsb
cmp al,20h
jne test1
push esi
mov ecx,esi
sub ecx,offset cmd_line
sub esi,ecx
rep movsb
test2:
lodsb
cmp al,0h
jne test2
pop ecx
lea edi,name12
sub esi,ecx
xchg ecx,esi
rep movsb
ret
;------------------------------------
decrypt: ; Декриптор с антиотладочными
pop ebx ; и антидизассемблерными
call setup_SEH ; Трюками
mov esp,[esp+8]
;------------------------------------
mov eax,04ebh
jmp $-4
f_diz:
mov eax,fs:[018h]
mov eax,[eax+30h]
movzx eax,byte ptr [eax+02]
cmp eax,0
jne final
call supa_delta
;------------------------------------
supa_delta:
pop ebp
sub ebp,offset supa_delta
mov eax,dword ptr [ebp + offset oldip]
; Восстанавливаем старую точку входа
add eax,dword ptr [ebp + offset imagebase]
; и виртуальный адрес начала
push eax
mov esi,eax
mov edi,esi
xor ecx,ecx
decrypt1:
lodsb
xor al,[ebp+key]
stosb
inc ecx
cmp ecx,[ebp+size_code]
jl decrypt1
final:
ret
;------------------------------------
setup_SEH:
push dword ptr fs:[0] ; Push'им оригинальный
; обработчик SEH
mov fs:[0],esp ; И помещаем новый (который
; находится после первого call)
; Пытаемся писать в ядро (что
mov eax,012345678h ; вызовет исключение)
xchg eax,[ebx]
;------------------------------------
oldip dd 0h
imagebase dd 0h
size_code dd 0h
key db 0h
finish:
message2 db 79 dup("-")
db 10
db "[ c0de secure program by sl0n]",10
db "Usage: secure.exe [your_program.exe]",10
db 79 dup ("-")
db 10,0
no_file db "Program can not open this file",10,0
host_name db 100 dup(?)
cmd_line db 200 dup(?)
name12 db 100 dup(?) ; Место для имени файла
start_code dd 0h
size2read dd 0h ;
fHnd dd 0h ;
mHnd dd 0h ;
memory dd 0h ;
mapaddress dd 0h ;
PEheader dd 0h
filesize dd 0h
imagesize dd 0h
newip dd 0h
Fhandle dd 0h
;------------------------------------
.code
nop
;------------------------------------
end start
;------------------------------------
Теперь рассмотрим как работает данная навесная защита.
Вот так выглядит программа до изменения её Secure cod'ом:
После же того, как на программу была установлена защита Secure code, она будет
выглядеть следующим образом:
Декриптор был усилен всеми описанными выше приёмами, а так же в нём
использовался один из мощнейших антиотладочных трюков с применением
SEH'a. Данный приём заключается в том, что в качестве SEH обработчика у нас
устанавливается наш декриптор. Затем мы намерено вызываем ошибку, попыткой
записи в ядро. При обработке этой ошибки управление передаётся на наш декриптор
при нормальном исполнении программы и завершается в контекстах отладчика и кодоэмулятора.
Для использования программы после компоновки следует запустить программу
следующим образом:
C:\secure\secure.exe your_program.exe
Где your_program.exe - программа, которую вы хотите усилить.
В данной статье были рассмотрены основные приёмы защиты программного кода и их
применение для разработки программы, которая оберегает код от взлома.