|
|
|||||||||||||||||||||||||||||
|
Developer vs Cracker: Косвенная адресацияИсточник: delphisources
Открою вам одну вещь - я с другой стороны баррикад. Я занимаюсь reversing engineering. Это означает, что мне не чуждо копание в чужом коде, причем не в исходниках, а в уже откомпилированном. Но поскольку я занимаюсь этим только ради образовательных целей (я не зарелизил ни одного крэка), то я решил поделиться с вами той информацией, которая поможет усложнить взлом ваших творений. Как говориться - врага нужно знать в лицо, поэтому знание ассемблера в данном случае просто необходимо. Конечно писать целиком приложение на ассемблере мы не будем, но знать, что какая команда делает нужно. Также необходимо наличие таких инструментов как дизассемблер (подойдет WinDASM), редактор РЕ-файлов (PEBrowser, можно взять и мой), редактор двоичных файлов (Hiew, WinHex) ну и конечно же отладчик OllyDebugger. Все вышеперечисленные инструменты можно бесплатно скачать и бесплатно пользоваться. Также необходимо знать устройство РЕ-файлов, а также уметь извлекать информацию из map-файла. Часть 1. PE-ФайлыЯ не буду глубоко копаться в формате РЕ-файлов, поскольку это не является основной задачей статьи. Если кому-то понадобятся эти знания, то на просторах Интернета есть огромное количество материала, посвященного описанию данного формата. Я лишь ограничусь описанием тех моментов, которые пригодятся в дальнейшем. РЕ-файлы состоят из так называемых секций. Каждая секция имеет свое предназначение - в ней может находиться код, ресурсы, таблица импорта, данные пользователя, короче всё что угодно. В отличии от NE-файлов, РЕ-файлы совершенно по-другому загружаются в память. Дело в том, что РЕ-файл можно загружать по разным адресам, т.е начало файла может быть расположено там, где это удобно системе (хотя некоторые компиляторы, делают так, чтобы РЕ-файл грузился именно туда, куда ему указали). Но суть не в этом. Для более толкового понятия введем такие понятия: Image Base - адрес в памяти с которого начинается РЕ-файл. Обычно равен 0400000h. Relocated Virtual Adress RVA- адрес в памяти относительно Image Base. Virtual Adress VA- адрес в памяти с учетом Image Base. Image Base - прописан в заголовке РЕ-файла. Когда системный загрузчик загружает файл в память, он считывает значение Image Base, а потом выделяет необходимое количество памяти для загружаемого файла. Адресация в этом участке памяти будет начинаться как раз с ImageBase. Как было сказано выше, РЕ-файл состоит из секций. Каждая из секций имеет такие параметры: Object Name - Имя объекта. (Имя секции. ) Virtual Size - виртуальный размер секции, именно столько памяти будет отведено под секцию Section RVA - размещение секции в памяти, виртуальный ее адрес относительно Image Base. Physical Size - размер секции (ее инициализированной части) в файле, Physical Offset - Object Flags - битовые флаги секции. Теперь немножечко подробнее. Object Name - имя секции. Вообще может быть любым, но большинство компиляторов дают секциям выразительные имена. Например CODE, DATA, .rsrc. Только посмотрев на них сразу понятно - что содержится в данной секции. Virtual Size - Количество памяти, которое отводится для секции. Т.е секция на диске может занимать меньше места, чем в памяти. Яркий пример этому программы запакованные каким-нибудь упаковщиком, например UPX. Physical Size - Размер секции в файле. Должен быть меньше или равен Virtual Size. Physical Offset - физическое смещение начала секции относительно начала EXE файла. Object Flags - битовые флаги. Определяют какие операции можно делать с секцией
Логически предположить, что для секции кода значение Object Flags будет равно: 20000000h +40000000h+00000020h= 60000020 h Для секции ресурсов значение Object Flags будет равно : 40000000h + 10000000h + 00000040h = 50000040h Часть 2. Map-файлДля тех кто не знает - map-файл нужен для отладки приложения под сторонними отладчиками. В этом файле содержится информация о глобальных переменных, адреса процедур и т.д. Выставить опцию генерации детального map-файла нужно в настройках проекта (см. рисунок). Теперь скомпилируем приложение. И посмотрим на наш map-файл. Сначала идет таблицасегментов, которые присутствуют в скомпилированном приложении: Start Length Name Class 0001:00000000 0004F5C4H .text CODE 0002:00000000 00001170H .data DATA 0002:00001170 00000BD9H .bss BSS Итак что есть что. Start - адрес начала сегмента. (Не путать с секцией! Подробнее - ниже.) Length - виртуальный размер секции. (Количество памяти, которое отводится для сегмента) Name - имя секции . Class - тип секции. Самое интересное, что имя секции в исполняемом файле не совпадает с именем секции, которое указано в map-файле, а совпадает с типом секции. В столбце Start описывается префикс и смещение относительно начала сегмента. Сегмент - это данные, которые объединяются по типу их обработки. Тип сегмента описывается префиксом и принимает следующие значения: 0001 - это сегмент исполняемого кода, 0002 - это сегмент данных. В сегменте могут размещаться несколько секций. Смещение указывается относительно начала сегмента. Как видно из таблицы, сегмент исполняемого кода состоит из одной секции, а сегмент данных из двух. Смещение отсчитывается от начала сегмента, а начало сегмента - это начало первой по расположению секции, из которых этот сегмент состоит. Нам важен сегмент с исполняемым кодом, поэтому другие мы попросту рассматривать не будем. Да, еще такой момент - адресация в map-файле идет относительно начала сегмента. Далее в map-файле располагается информация о подключенных модулях: Detailed map of segments 0001:00000000 00004B03 C=CODE S=.text G=(none) M=System ACBP=A9 0001:00004B04 00000140 C=CODE S=.text G=(none) M=SysInit ACBP=A9 0001:00004C44 00000078 C=CODE S=.text G=(none) M=Types ACBP=A9 0001:00004CBC 00000BFC C=CODE S=.text G=(none) M=Windows ACBP=A9 0001:000058B8 00000038 C=CODE S=.text G=(none) M=Messages ACBP=A9 0001:000058F0 00000310 C=CODE S=.text G=(none) M=SysConst ACBP=A9 0001:00005C00 00006378 C=CODE S=.text G=(none) M=SysUtils ACBP=A9 0001:0000BF78 000007FB C=CODE S=.text G=(none) M=VarUtils ACBP=A9 0001:0000C774 00002A42 C=CODE S=.text G=(none) M=Variants ACBP=A9 ... ... ... После идет информация о размещении процедур, функций, обработчиков, методов класса. Address Publics by Name 0001:000449F4 DoneApplication 0001:000398E4 DoneControls 0001:0000A7B8 DoneExceptions 0001:00049010 DoNestedActivation 0001:00017D38 DoneThreadSynchronization 0001:0004BDE8 DoPosition ... ... ... 0001:0004EA10 TApplication.ActivateHint 0001:0004D664 TApplication.BringToFront 0001:0004E778 TApplication.CancelHint 0001:0004CC48 TApplication.CheckIniChange 0001:0004C9C4 TApplication.ControlDestroyed 0001:0004C50C TApplication.Create 0001:0004DA64 TApplication.CreateForm 0001:0004C814 TApplication.CreateHandle 0001:0004C6F8 TApplication.Destroy ... ... ... 0001:00023BBC TTimer.Create 0001:00023C0C TTimer.Destroy 0001:00023C44 TTimer.WndProc 0001:00023CB8 TTimer.UpdateTimer 0001:00023D44 TTimer.SetEnabled 0001:00023D54 TTimer.SetInterval 0001:00023D64 TTimer.SetOnTimer 0001:00023D7C TTimer.Timer ... ... ... Далее идет информация о расположении глобальных переменных, адресах системных функций и классов Address Publics by Value 0002:FFBAF010 TlsLast 0001:000001F4 GetStdHandle 0001:000001FC RaiseException 0001:00000204 RtlUnwind 0001:0000020C UnhandledExceptionFilter 0001:00000214 WriteFile 0001:0000021C CharNext 0001:00000224 ExitProcess 0001:0000022C MessageBox 0001:00000234 FindClose 0001:0000023C FindFirstFile 0001:00000244 FreeLibrary 0001:0000024C GetCommandLine 0001:00000254 GetLocaleInfo 0001:0000025C GetModuleFileName 0001:00000264 GetModuleHandle 0001:0000026C GetProcAddress ... ... ... 0001:00013144 TStrings.Error 0001:0001317C TStrings.Error 0001:000131D4 TStrings.Exchange 0001:000132BC TStrings.GetCapacity 0001:000132C4 TStrings.GetObject 0001:000132C8 TStrings.GetText 0001:0001331C TStrings.GetTextStr 0001:00013444 TStrings.IndexOf 0001:000134D0 TStrings.IndexOfName 0001:000135A4 TStrings.IndexOfObject ... ... ... В конце содержится информация соответствия каждой строки кода адресу в откомпилированном коде для каждого модуля: Line numbers for MainF(MainF.pas) segment .text 35 0001:0004F1D4 36 0001:0004F1EE 37 0001:0004F1F2 38 0001:0004F205 39 0001:0004F218 40 0001:0004F222 41 0001:0004F224 42 0001:0004F23A 43 0001:0004F248 42 0001:0004F257 44 0001:0004F25B 45 0001:0004F288 49 0001:0004F290 50 0001:0004F2AC 54 0001:0004F2D8 55 0001:0004F2F4 56 0001:0004F300 60 0001:0004F324 62 0001:0004F32D 63 0001:0004F332 65 0001:0004F333 66 0001:0004F338 67 0001:0004F36C 67 0001:0004F373 Line numbers for IndirectA(C:\Program Files\Borland\Delphi7\Projects\indirect addressing\IndirectA.dpr) segment .text 9 0001:0004F57C 10 0001:0004F58C 11 0001:0004F598 12 0001:0004F5B0 13 0001:0004F5BC Какие ресурсы были подлинкованы: Bound resource files c:\program files\borland\delphi7\Lib\Buttons.res c:\program files\borland\delphi7\Lib\ExtDlgs.res c:\program files\borland\delphi7\Lib\Controls.res MainF.dfm IndirectA.res IndirectA.drf И точка входа в программу: Program entry point at 0001:0004F57C. Возникает вполне нормальный вопрос, а как найти адрес нужной нам процедуры? Все очень просто - открываем в редакторе РЕ-файлов наш откомпилированный проект, смотрим на значение ImageBase и Виртуальное смещение (RVA) секции кода: Далее открываем map-файл проекта, и ищем расположение (адрес) необходимой нам функции (например Button1Click): 0001:0004F324 TMainForm.Button1Click Дальше вычисляем адрес данной функции: 04F324 (Это адрес из map-файла)+01000 (А это адрес начала секции с кодом в РЕ-файле)+0400000 (Это ImageBase)= 0450324. Теперь мы можем узнавать адреса необходимых нам функций. И теперь пора переходить от теории к практике. Часть 3. Косвенная адресацияДля чего нужна косвенная адресация? И вообще что это такое. Для более глубокого понимания напишем тестовое приложение, в котором будет проводиться проверка правильного серийного номера. Алгоритм генерации серийного номера выберем максимально простой: сначала поднимем в верхний регистр все символы имени, а потом сложим их значение. В случае неправильного ввода будет выдано сообщение и программа завершит работу. В качестве пары имя/серийный номер я выбрал Thrasher/609. Кто не ленивый, напишет свой генератор правильных серийных номеров для этого приложения. Текст модуля главной формы: unit MainF; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMainForm = class(TForm) Button1: TButton; Button2: TButton; Edit1: TEdit; Edit2: TEdit; Label1: TLabel; Label2: TLabel; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var MainForm: TMainForm; implementation {$R *.dfm} function CheckSerial:boolean; Var SN_num,SN_num2:integer; SN,Name:string; I:byte; begin Result:=false; Name:=MainForm.Edit1.Text; SN:=MainForm.Edit2.Text; SN_num:=StrToInt(SN); SN_num2:=0; Name:=UpperCase(Name); For I:=1 to Length(Name) do SN_num2:=SN_num2+Ord(Name[i]); If SN_num=SN_num2 then Result:=true; end; procedure GoodCheck; begin messageBox(Application.Handle,'Проверка прошла удачно.','Поздравления!',0); end; procedure BedCheck; begin messageBox(Application.Handle,'Проверка не прошла.','Неудача!',0); Application.Terminate; end; procedure TMainForm.Button1Click(Sender: TObject); begin if CheckSerial=true then begin GoodCheck; Exit; end; BedCheck; end;
end. Обратите внимание - я вынес в отдельные процедуры функции выдачи сообщений и эти процедуры не являются методами класса TMainForm. Да, чуть не забыл, нужно включить режим создания детального map-файла. Теперь дизассемблируем полученный ЕХЕ (я использую WinDASM). Пока файл дизассемблируется, найдем необходимые нам адреса процедур GoodCheck и BedCheck: Для GoodCheck - 0450290. Для BedCheck - 04502D8. Теперь посмотрим на листинг, что сгенерировал дизассемблер, начиная с указанных адресов: * Referenced by a CALL at Address: /:0045032D <= Это адрес откуда вызывалась данная процедура / :00450290 6A00 push 00000000 :00450292 68B0024500 push 004502B0 :00450297 68C0024500 push 004502C0 :0045029C A108204500 mov eax, dword ptr [00452008] :004502A1 8B00 mov eax, dword ptr [eax] :004502A3 8B4030 mov eax, dword ptr [eax+30] :004502A6 50 push eax * Reference To: user32.MessageBoxA, Ord:0000h / :004502A7 E8EC61FBFF Call 00406498 :004502AC C3 ret Перейдем на адрес откуда вызывалась процедура GoodCheck. :00450329 3C01 cmp al, 01 :0045032B 7506 jne 00450333 :0045032D E85EFFFFFF call 00450290 <= А тут происходит вызов :00450332 C3 ret Данный тип адресации процедур называется прямым - т.е. когда адрес перехода указывается конкретно. Чем это хорошо - для отладки. Чем плохо - можно быстро вычислить место вызова и подправить. Поэтому для усложнения нахождения нужно использовать косвенную адресацию. Немного перепишем наше приложение. unit MainF; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMainForm = class(TForm) Button1: TButton; Button2: TButton; Edit1: TEdit; Edit2: TEdit; Label1: TLabel; Label2: TLabel; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end;
var MainForm: TMainForm; GoodCheckAddress,BedCheckAddress:LongInt; <= Переменные для адреса процедур
implementation {$R *.dfm} function CheckSerial:boolean; Var SN_num,SN_num2:integer; SN,Name:string; I:byte; begin Result:=false; Name:=MainForm.Edit1.Text; SN:=MainForm.Edit2.Text; SN_num:=StrToInt(SN); SN_num2:=0; Name:=UpperCase(Name); For I:=1 to Length(Name) do SN_num2:=SN_num2+Ord(Name[i]); If SN_num=SN_num2 then Result:=true; end; procedure GoodCheck; begin messageBox(Application.Handle,'Проверка прошла удачно.','Поздравления!',0); end; procedure BedCheck; begin messageBox(Application.Handle,'Проверка не прошла.','Неудача!',0); Application.Terminate; end; procedure TMainForm.Button1Click(Sender: TObject); begin if CheckSerial=true then begin asm call GoodCheckAddress <= вызываем GoodCheck end; Exit; end; asm call BedCheckAddress asm <= вызываем BedCheck end; end; procedure TMainForm.FormCreate(Sender: TObject); begin asm <= заносим в GoodCheckAddress адрес GoodCheck push eax mov eax, offset GoodCheck mov GoodCheckAddress,eax pop eax end; asm <= заносим в BedCheckAddress адрес BedCheck push eax mov eax, offset BedCheck mov BedCheckAddress,eax pop eax end;
end;
end. Компилируем. Открываем map-файл и ищем адреса процедуры GoodCheck. Это будет 045029F. Снова пропускаем файл через дизассемблер, переходим на адрес откуда начинается GoodCheck :0045029F 90 nop :004502A0 6A00 push 00000000 :004502A2 68C0024500 push 004502C0 :004502A7 68D0024500 push 004502D0 :004502AC A108204500 mov eax, dword ptr [00452008] :004502B1 8B00 mov eax, dword ptr [eax] :004502B3 8B4030 mov eax, dword ptr [eax+30] :004502B6 50 push eax * Reference To: user32.MessageBoxA, Ord:0000h / :004502B7 E8DC61FBFF Call 00406498 :004502BC C3 ret Сразу бросается в глаза тот факт, что не видно откуда вызывается данная процедура. Ставим в Delphi во встроенном отладчике точку останова на строке procedure TMainForm.Button1Click(Sender: TObject); begin if CheckSerial=true then begin asm call GoodCheckAddress<= тут точка останова end; Exit; end; asm call BedCheckAddress asm <= вызываем BedCheck end; end; Когда она сработает, откроем окно с командами процессора. (Для его вызова нужно нажать Alt+C либо выбрать в меню View->Debug Window->CPU). Смотрим адрес вызова процедуры GoodCheck. 0045033D FF15D43B4500 call dword ptr [GoodCheckAddress] Переходим на данный адрес в дизассемблерном листинге: :00450339 3C01 cmp al, 01 :0045033B 7507 jne 00450344 :0045033D FF15D43B4500 call dword ptr [00453BD4] <= Вызов GoodCheck :00450343 C3 ret Такой вызов называется косвенный, либо неявный. Когда крекер пропустит все через декомпилятор или дизассемблер, то в данном случае будет намного тяжелее найти какую функцию, и кто вызывал. Часть 4. Глобальные и локальные переменные. Влияние на косвенную адресациюКак вы могли заметить, я хранил адреса процедур в глобальных переменных. А как измениться код вызова, если для этих целей использовать локальные переменные. Изменим наш код: unit MainF; interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMainForm = class(TForm) Button1: TButton; Button2: TButton; Edit1: TEdit; Edit2: TEdit; Label1: TLabel; Label2: TLabel; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end; var MainForm: TMainForm; implementation {$R *.dfm} function CheckSerial:boolean; Var SN_num,SN_num2:integer; SN,Name:string; I:byte; begin Result:=false; Name:=MainForm.Edit1.Text; SN:=MainForm.Edit2.Text; SN_num:=StrToInt(SN); SN_num2:=0; Name:=UpperCase(Name); For I:=1 to Length(Name) do SN_num2:=SN_num2+Ord(Name[i]); If SN_num=SN_num2 then Result:=true; end; procedure GoodCheck; begin messageBox(Application.Handle,'Проверка прошла удачно.','Поздравления!',0); end; procedure BedCheck; begin messageBox(Application.Handle,'Проверка не прошла.','Неудача!',0); Application.Terminate; end; procedure TMainForm.Button1Click(Sender: TObject); Var GoodCheckAddress,BedCheckAddress:LongInt; begin asm push eax mov eax, offset GoodCheck mov GoodCheckAddress,eax pop eax end; asm push eax mov eax, offset BedCheck mov BedCheckAddress,eax pop eax end; if CheckSerial=true then begin asm call GoodCheckAddress end; Exit; end; asm call BedCheckAddress end; end;
end. Скомпилируем и поставим точку останова на строке begin asm call GoodCheckAddress<= тут точка останова end; Exit; end;
asm call BedCheckAddress end; end; После срабатывания точки останова вызываем окно процессорных команд: Обратите внимание, насколько поменялся вызов: При использовании локальной переменной: :00450357 FF55FC call dword ptr [ebp-$04] При использовании гловальной переменной : :0045033D FF15D43B4500 call dword ptr [00453BD4] Как нетрудно заметить, в случае использования локальных переменных, определить адрес процедуры становиться сложнее. А если скомбинировать эти два способа, то определить что куда вызывалось еще сложнее. Часть 5. Избавление от "хвостов" или адрес возврата своими рукамиОпять немного теории. В ассемблере все подпрограммы вызываются при помощи команды call. Вот пример вызова: push eax call SomeThing mov ebx,eax inc ebx Что же делает команда call? Во-первых помещает адрес возврата в стек, а во-вторых совершает переход на указанную метку. Т.е. фактически это есть безусловный переход, только с возвращением на последующую поcле call команду. Осталось дело за малым - узнать адрес возврата. Оказывается - это не так уж и сложно. Дело в том, что адресация в ассемблере относительная, а не абсолютная. Это означает, что переходы при прямой адресации осуществляются относительно команды перехода. Т.е Такая конструкция как 004019EF: EB04 jmps .0004019F5 означает не прямой переход на адрес 4019F5, а переход на инструкцию, адрес которой больше на 4 адреса команды перехода. Поэтому можно немного "пошаманить". Предупреждаю - этот способ сложнее, но хочется почувствовать себя немного хакером! Изменим код обработчика на следующий: procedure TMainForm.Button1Click(Sender: TObject); Var GoodCheckAddress,BedCheckAddress:LongInt; begin asm push eax mov eax, offset GoodCheck mov GoodCheckAddress,eax pop eax end; asm push eax mov eax, offset BedCheck mov BedCheckAddress,eax pop eax end; if CheckSerial=true then begin asm push eax ;сохраняем значение регистра eax call @1 ;узнаем адрес следующей за call команды @1: ;он будет на верхушке стека pop eax ;выталкиваем из стека адрес в регистр eax add eax,8; ;увеличиваем значение адреса на 8 (ниже объясню) push eax ;проталкиваем в стек значение адреса возврата jmp GoodCheckAddress ;прыгаем на начало функции GoodCheck pop eax ;восстанавливаем значение eax end; Exit; end; asm push eax call @1 @1: pop eax add eax,8 push eax jmp GoodCheckAddress pop eax end; end;
Я же предупреждал - этот способ сложнее. Предвижу вопрос - почему увеличилось значение адреса возврата на 8. Все очень просто - это длина команд между call @1 и pop eax. Дело в том, что каждая команда имеет определенную длину в байтах. Узнать эти длины можно из отладчика, а можно просто вычислить разницу между адресами команд. Умело применив вышеописанный способ можно добиться весьма сложной системы перехода из одного места программы в другой, что может сильно затруднить взлом. Часть 6. Скрытый переходПри написании програм с испытательным сроком или с проверкой серийного номера неизбежно используется конструкция: If EnteredSerialNumber<>ValidSerialNumber then ErrorMessageProc else CongratulationProc Или такая контсрукция: if Registration<>true then ErrorMessageProc else CongratulationProc Встречаются и другие конструкции, типа Registration:=RegistrationProc; Messsage(Registration); //В зависимости от значения переменной Registration //будут выполнятся различные действия Недостатком таких конструкций перед взломом есть то, что явно видно место проверки (в 2 первых конструкциях) или явное присваивание (в последней). Дело в том, что все операции результатом которых есть булевая переменная, а сравнение строк относятся к данному типу операций, в ассемблере выглядят одинаково, а именно call StringCompare test al,al jz Failed В случае третьей конструкции, функция RegistrationProc возвращает в регистр eax 1 либо 0 (True или False соответственно). Путем исследования можно найти где идет проверка и с минимальными усилиями нейтрализовать ее либо сделать так, что будет возвращаться необходимый крекеру результат. Для усложнения взлома воспользуемся применением косвенной адресации. Напишем тестовое приложение, в котором будет проверка пароля. Пароль шифроваться не будет, поскольку нам важен сам механизм скрытого перехода. Вот текст главного модуля: unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Edit1: TEdit; Label1: TLabel; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; ResultAddress:LongInt; Passed:boolean; const password='simsim'; implementation {$R *.dfm} procedure Good; begin MessageBox(Application.Handle,'PASSWORD PASSED!','Congratulation',MB_OK); end; procedure Bad; begin MessageBox(Application.Handle,'PASSWORD FAILED.','Error',MB_OK); end; Procedure CheckPassword; Var psw:String; begin psw:=Form1.Edit1.Text; Passed:=False; if psw=password then Passed:=true; asm pushad ; сохранить значение всех регистров в стек mov al,passed ; занести в al значение passed test al,al ; сравнить - если jz @no ; false, тогда перейти на метку @no mov eax, offset Good ; поместить в eax адрес процедуры Good mov ResultAddress, eax ; поместить в ResultAddress значение eax jmp @finish ; перейти в конец процедуры @no: mov eax, offset Bad ; поместить в eax адрес процедуры Bad mov ResultAddress, eax ; поместить в ResultAddress значение eax @finish: popad ; восстановить значение всех регистров end; end; procedure TForm1.Button1Click(Sender: TObject); Var Addr:LongInt; begin CheckPassword; Addr:=ResultAddress; asm call Addr end; end;
end. Глобальной переменная ResultAddress используеться только для передачи значения адреса процедур, этот прием конечно усложнит жизнь взломщику, поскольку придется анализировать весь код процедуры CheckPassword. Если использовать таблицы косвенных переходов или генератор случайных чисел для вызова процедур проверки (например, таких процедур будет 10 и вызываться они будут в случайном порядке из одного и того же места), то взломать такую программу будет весьма трудно. Как вариант можно использовать такую конструкцию:
procedure TForm1.Button1Click(Sender: TObject); Var Addr:LongInt;
procedure CheckPasswordLocal; begin CheckPassword; Addr:=ResultAddress; end;
begin CheckPasswordLocal; asm call Addr end; end;
При таком варианте тяжелее вычислить, где находиться проверка. Конечно, многим после прочитанного, захочется попробовать эту технологию, как маньяку новую бензопилу. Однако не стоит применять косвенную адресацию везде. Применяйте ее только там где это необходимо. Помните, что это низкоуровневое программирование в языке высокого уровня, и возможно придется самому делать присваивания переменным. Но поверьте - это этого стоит. Ссылки по теме
|
|