Наверное каждый из вас сталкивался с проблемой что-то изменить в какой-то чужой проге. Если это предусматривало просто замену информации, то вы, недолго думая, запускали свой любимый HEX вьюер и меняли все что можно. Не всегда для достижения результата удавалось сохранить размер программы. Но и тогда крутой программер не останавливался и запускал свой дизассемблер (дизассемблирование - процесс, в результате которого из программы вы получаете ее исходный код на ... ассемблере :). Ha сегодняшний день известна туева хуча приличных д/а, как то IDA, Periscope, Bubble
Chamber, Sourcer, etc...). Если у Вас такого еще нет, то я советую начать с IDA (Interactive DisAssembler Pro), который можно достать на
www.datarescue.com/idabase. Далее в полученных ASM файлах менялось что было нужно, и затем из них снова лепили
работающую прогу. Наиболее продвинутые программеры часто из полученных исходников пытаются собрать программы для языков высокого уровня, будь то С, Fortran или даже Basic и Pascal. Этот процесс называется декомпиляцией, из-за его сложности автоматические реализации встречаются крайне редко. Декомпилируют обычно вручную, при этом ищут конец и начало функций. Например, встретив строки 'push ebp; mov ebp,esp' вы можете сказать, что это начало функции, а 'pop ebp; retn' - ее конец. Такие продвинутые дизассемблеры как IDA уже выделяют все функции, и задача ставится только понять, что они делают.
Но вот вы начали дизассемблировать программу и ...облом :(. Ваша прога выдала очень маленький код и завалила какими-то db с непонятными цифрами. При этом вы явно заподозрили что-то неладное. Если программа закодирована или защищена чем-то очень страшным, не стоит сразу отбрасывать ее и орать "Я такую гадость и за сто лет не расшифрую !". Достаточно осознать, чем бы программа не была закодирована, ОНА САМА ЖЕ СЕБЯ РАСКОДИРУЕТ, чтобы работать !!!. Ну вот. А далее остается только действовать. Если программа себя дешифрует/распаковывает или еще_х_знает_что_делает, то можно эту часть программы запустить и взять уже РАСПАКОВАННЫЙ КОД. Есть и другой подход, в принципе не отличающийся от предыдущего: запустить прогу, и во время ее работы записать используемую ей память на диск.
Как правило, шифруемые программы, в том числе и полиморфные вирусы :), шифруют себя несколько раз, при этом они могут переносить или даже изменять свой код, запускать расшифрованную информацию для дальнейшей распаковки, ну а также применять и другие запутывающие подходы. Отсюда следует, что дизассемблером пользоваться невыгодно. Для этой цели выглядит выгодно последовательно прогу выполнять по одной инструкции (так называемое трассирование - trace) или выполнять программу до заданной инструкции/области памяти. Такими функциями обладают практически все отладчики (дебаггеры). Только не говорите мне, что у вас их нет ! Даже в DOSе (и мастдайке в т.ч) есть утилитка DEBUG - самый простой отладчик. Но он, к сожалению, 16-битный, и в общих чертах не удовлетворяет требованиям крутых программеров. Вообще, практически у любого языка программирования есть дебаггер. У Borlandовских (Turbo Assembler (TASM), Borland's C++) - это серия TD (Turbo Debugger). TD - для DOSовских программ, TDW - для виндовских. Но часто TDW не берет программу с заголовком PE/NE 32bit. Тогда смело запускайте TD32. Не стоит забывать и MicroSoft. В их продуктах (MicroSoft Assembler, MASM) можно найти отладчик CodeView (CV, CVW). Относительно недавно появился пакет утилит NuMega фирмы Compuware, смотрите на
www.numega.com. Отладчик NuMega SoftIce (winice) позволяет дебаггить даже ядро системы, а также с легкостью просматривать все треды, процессы и окна. Часто он входит в комплект BoundsChecker.
Теперь у нас есть все для взлома. Запускаете в отладчике нужную прогу, и выполняете по инструкции... 🙂 если же терпежа не хватит, то поставите breakpoint на выходе из цикла и запустите программу с того места, на котором остановились. Программа передаст управлению отладчику перед выполнением инструкции, помеченной breakpointом. Чаще всего breakpointы представляют собой прерывания (точнее, ловушки), которые заменяют помеченные инструкции. Зачем я это пишу ? - просто
будьте бдительны, чтобы программа не заменила инструкцию, на которой стоит breakpoint, а то он может
потеряться, и программа будет выполняться пока не закончиться :-(. Ну вот, допустим мы дошли до того места, когда начинается собственно распакованная программа... по крайней мере как Вы думаете, а на самом деле это может быть продолжение декодера :D. Теперь можете сохранить всю полученную кашу как EXEшник. Но только учтите вашу новую точку входа и
позаботьтесь о том, чтобы значение регистров сохранилось... а вдруг они далее будут использованы ? Заголовок вы можете посмотреть с помощью классной утилиты HIEW (Hacker's View) by SEN (ftp.kemsc.ru/pub/sen). Если прога DOSовская - заголовок MZ, виндовская - чаще всего PE.
Теперь рассмотрим некоторые способы защиты программ от дизассемблирования. Если Вы - крутой программер, сделали классную прогу, и по каким-то причинам хотите скрыть ее код ( а вдруг кто заметит там моего любимого троянчика ?!?), то первое, что придет вам на ум - зашифровать ее. Тут вы можете полистать веселые книжки по криптографии, но в конце понять, что распаковщик слишком прост. Я могу также предложить не только зашифровать, но и запаковать инфу. Если ничего не знаете - первое, что придумаете, будет RunLengthEncode, но паковать им код - дохлый номер. Лучше использовать какую-нибудь модификацию алгоритма LZW. Он, точнее его модификации, используются во многих архиваторах (RAR, ZIP), в графических файлах GIF, даже в PDFах, им запаковываются EXEшники (PKLITE). Теперь, когда есть основа, подумаем о том, как испортить жизнь любителям вскрывать программы. Во-первых, можно зашифровать часть распаковщика, вообще так можно шифровать программу по частям, и по несколько раз зашифровывать расшифровщики :), а потом еще все вместе зашифровать :))). Можно спокойно перемешивать части программы, что также может привести в замешательство.
Помимо этого, можно придумать и более действенные способы защиты ! Как вы думаете, что будет находиться в регистрах после выполнения следующего участка кода:
Label1: mov ax,1ab8h
mov bx,06bbh
mov cx,00b9h
mov dx,01bah
mov si,0ffbeh
mov di,32bfh
jmp Label1+1
Правильный ответ: bx = 0x0b906; cx = 0x0ba00; dx = 0x0be01; si = 0x0bfff; di = 0x0eb32. Не поняли почему ? Тогда откомпилируйте ее и протрассируйте ! Чтобы убирать наставленные breakpointы можно изменять выполняемый код, например, увеличивать или уменьшать следующую инструкцию, можно ставить на них маски, складывать, да и просто перезаписывать. Таким образом каждый раз последующая инструкция может быть изменена:
push cs
pop ds
mov ch,0ebh
mov esi,offset $ или Label2: mov esi, offset Label2
add byte ptr[esi+07],08
sub byte ptr[esi+10],5
db Rel
Этот фрагмент выполняет относительный переход на Rel байтов (равносильно Jmp short ptr Rel). Можно (даже нужно) защищать программы от... трассирования. Да, именно от трассирования. Ну, и ежу понятно, что трассирование гораздо дольше обычного выполнения программы. Это и можно использовать. Ниже я приведу фрагмент, вызывающий функцию DOS 'Get
Time':
mov ah,2ch ; Get Time
int 21h ; call DOS services
push dx; dh - seconds, dl - hundredths of a second (0-99)
mov ah,2ch ; Get Time
int 21h ; call DOS services
pop ax; ah - seconds
cmp dh,ah
jz continue
; GOTCHA !!!
Если время в секундах не изменилось, то осуществляется переход на метку continue, иначе продолжается выполнение программы. Ну а здесь... можно поставить и низкоуровневое форматирование диска 80H (ну, C: я имею в виду...) !!! Это для особо нехороших людей типа меня :p, а остальным... советую просто подвесить систему.
Даже вышеприведенный фрагмент, на самом деле, можно протрассировать гораздо быстрее секунды, поэтому советую вставлять запросы времени в разные участки программы,
можно даже по-разному запакованные, так как распаковка гораздо быстрее трассирования.
Более того, сравнение полученных секунд можно отложить на следующий участок кода, так чтобы он не особо выделялся, а то сразу заподозрят: сначала посмотрел сколько времени, потом сравнил с чем-то, а зачем ?!?... Часто у многих крекеров (меня в т.ч.) вызывают подозрения команды условного перехода, поэтому уместнее было бы написать все это без них:
; fragment 1
xor ecx,ecx
xor edx,edx
mov ah,01
int 1ah
;установить счетчик тиков на 0
;каждую секунду происходит примерно 18.2 тика
;поэтому предположим, что 2 тика уже слишком много
;fragment 2
xor eax,eax; здесь можно использовать ax,bx,cx,
;но для 32х разрядной машины 32х разрядные регистры занимают меньше памяти
int 1ah; взять число тиков
mov ax,cx
shl eax,16
mov ax,dx ;конвертируем 32-битное число CX:DX в EAX
and al,-1 ;Один тик - не страшно, очистим первый бит
mov edx,eax
mov ecx,31
Label3: shr edx,1
or eax,edx
loop Label3
and eax,2
shl eax,1
add ebx,eax ;допустим, в ebx был нами рассчитан какой-то адрес
call [ebx+xxx]; ebx+xxx - по этому адресу - нормальная процедура
;ebx+xxx+4 - а по этому адресу - перехват трассировщиков !!!
На самом деле, еще можно придумать туеву хучу способов запудрить мозги взломщикам, так что кто во что горазд :). Считаем, что программа зашифрована хорошо. Осталось это сделать на практике. Я надеюсь у Вас не появилось желание запаковать EXEшник целиком ? Ну, если так, и вы еще не придумали как это сделать, подсказываю: у EXEшников можно менять точку входа... i.e. вы запаковываете откомпилированную инфу, а дешифратор прикрепляете снизу :). Не хотите? Просто? Пожалуйста - я знаю, например, в
C++ Buildere из подобных есть функция компилирования вашего файла в... ассемблерный source (сам пользовался), так что смело вооружайтесь ассемблером и вперед. Ну, и один из самых веселых способов - запаковывать отдельно процедуры, просто вставляете ассемблерный код в программу... В конце концов, можно создать *.lib, *.obj или *.dll файл и извращаться над ними. Тут уже кому что больше
нравится. Главное, чтобы гимора поменьше и... работало !