---
title: "Полуперемещаемый код"
description: "Эту часть правильнее было бы назвать «Вызов функций по абсолютному адресу», но это звучитеще непонят..."
author: "huzrus"
published: "2013-02-03T09:36:54+00:00"
modified: "2013-02-03T09:36:54+00:00"
locale: "ru"
canonical_url: "https://yvision.kz/post/poluperemeshchaemyy-kod-318277"
markdown_url: "https://yvision.kz/post/poluperemeshchaemyy-kod-318277/markdown"
site_name: "Yvision.kz"
---

# Полуперемещаемый код

> Эту часть правильнее было бы назвать «Вызов функций по абсолютному адресу», но это звучитеще непонят...

Эту часть правильнее было бы назвать «Вызов функций по абсолютному адресу», но это звучитеще непонятнее, и куда менее амбициозно. Такую безумную идею я больше нигде не встречал исчитаю ее своим изобретением :)Возможно, ты слышал, что вирмейкеры при разработке своих вирусов частенько добавляют вних разные вредоносные функции. При этом они делают свой код «перемещаемым». Этозначит, что этот код сможет работать в любом месте памяти и не привязан жестко в каким-тообъектам (ведь вирмейкер фактически не знает в каком конкретном месте окажется его кодпосле переполнения буфера).Одна из основных проблемперемещаемого кода - вызов внешних функций. Дело в том, что при вызове функции,компилятор использует не абсолютный адрес, а смещение относительно места вызова функции.Поясню на примере: program Project1; uses Dialogs; begin ShowMessage('Пример'); end. Ставим бряк(breakpoint) на ShowMessage и запускаем. Смотрим окно CPU (если ты уже забыл,то его удобнее вызывать с помощью CTRL-ALT- C):http://www.stranger.nextmail.ru/del4.jpgПо адресу $00451F71 лежит команда E8D2FBFFFF E8 – это CALL (относительный ближнийвызов). Запускаем калькулятор, переключаем в HEX-режим (4 байта) и считаем:$451F71 + $5 + $FFFFFBD2 = $451B48;5 - длина команды CALL; FFFFFBD2 - смещение (не забывай, что младшие байты числа лежатпо младшим адресам). Получаем $451B48 и переходим по этому адресу (CTRL-G):http://www.stranger.nextmail.ru/del5.jpgПопали как раз в начало функции ShowMessage! Надеюсь я понятно объяснил относительнуюадресацию.Теперь представь, что мы переместили код, содержащий вызов функции ShowMessage, куда-нибудь в кучу. И оказался ее вызов где-нибудь по адресу $00860828. Если снова посчитатьсмещения перехода, то получится адрес $008603FF, естественно здесь никакого вызоваShowMessage не будет, а будет ошибка доступа.Таким образом, чтобы создать перемещаемый код, надо научиться вызывать функции не поотносительному смещению, а по абсолютному адресу. Что тебе это даст? Ты сможешьпереместить код своей функции в другое место, например в кучу, стек, в адресное пространствокакой-нибудь dll’ки, и спрячешь ее вызов от отладчика.В общем идея состоит в том, чтобы вызывать функцию по указателю. А для того, чтобынормально использовать параметры, этот указатель будем приводить к прототипу этой самойфункции. Полное извращение. :) Смотри сам: program Project1; uses Dialogs; // Прототип функции ShowMessage type _ShowMessage = procedure (const Msg: string); begin // Вызов функции по указателю _ShowMessage(@ShowMessage)('Test'); end. Снова запускаем пример и смотрим, во что превратился вызов ShowMessage:http://www.stranger.nextmail.ru/del6.jpgЗдесь в регистр EBX непосредственно заносится уже знакомый тебе адрес $451B48, покоторому расположена функция ShowMessage. Потом происходит CALL на это содержимоерегистра EBX. Никаких относительных смещений нет и этот код будет работать в любом местеадресного пространства твоей программы!Теперь настало время объяснить, почему я назвал этот код полуперемещаемым. Дело в том, чтоэтот код будет работать только в адресном пространстве твоей программы. Если тыпопробуешь внедрить его в другую прогу, то снова будет ошибка доступа. Почему? Да потому,что абсолютный адрес $451B48 имеет значение только для твоей проги, а в другой по этомуадресу будет лежать что-то совсем другое, но точно не функция ShowMessage. Вот так-то.Кроме того у метода вызова функций по абсолютному адресу есть еще одно совершеннонеочевидное ограничение. С API-функциями Windows никаких проблем нет. Но вот в Delphiвсе функции делятся на настоящие и ненастоящие(встроенные). Отличить их легко и тысталкивался с этим не раз. Если навести мышку на имя функции и кликнуть по нему, удерживаяCTRL, то тебя забросит в место, где эта функция определяется, и ты сможешь посмотреть еекод. Но это относится только к настоящим функциям.Если ты сделаешь то же самое скажем с функцией AssignFile, то тебя забросит куда-то в началомодуля System. Так по крайней мере в Delphi 7. Это пример ненастоящей (встроенной)функции. На место ее вызова компилятор вставляет целый блок своего кода. Поэтому сненастоящими функциями вызов по указателю не прокатит. Если ты попробуешь сделать так: type _AssignFile = procedure (var f: File; FileName: String); var f: File; begin … _AssignFile(@AssignFile)(f,'test.txt'); … end. то компилятор просто откажется тебя понимать и нивкакую не будет компилировать код. Имейэто ввиду. Решить эту проблему можно, сделав функцию-переходник, которая будет ужевызывать встроенную функцию. В демо-проекте токой переходник сделан для функцииFreeMem.Еще одна засада поджидает тебя при использовании строк. В нашем примере в функциюShowMessage передается непосредственно указатель на строку, но так бывает далеко не всегда.Строки в Delphi это совсем непростые динамические структуры данных, для обработки которыхкомпилятор вставляет специальный код. Там происходит выделение памяти в куче, всякиеконтроли границ, обработка исключений и т.п. Лучше всего типом String не пользоваться, асоздавать строку непосредственно в функции в виде символьного массива. Примерно так: var str: array[0..4] of Char; … str[0] := 'T'; str[1] := 'e'; str[2] := 's'; str[3] := 't'; str[4] := #0; Это конечно неудобно, но зато надежно. Еще вариант – передавать указатели на строки спараметрах функции. Есть и будут другие подводные камни, например при обработкеисключительных ситуаций. Поэтому не забывай про отладчик и постоянно контролируй, чеготебе Delphi там накомпилировала.В качестве рабочего примера мы сейчас соорудим секретную функцию, которая будетпрятаться в стеке и записывать имя пользователя в файл.// Пользовательская функция освобождения памяти// Заменим этой функцией ненастоящую функцию FreeMem procedure FreeMemory(P: Pointer); begin FreeMem(P); end;

// Секретная функция, которая будет работать в стеке //Получает в параметрах указатели на строки сообщений function Secret(pTitle: PChar; pMessage: PChar): Boolean; stdcall; type // Прототипы использованных Функций _AllocMem = function(Size: Cardinal): Pointer; ........... _MessageBox = function (hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall; var FileName: array[0..5] of Char; FileHandle: Integer; UserName: PChar; NameLength: Cardinal; begin Result := False; //Резервируем память под имя пользователя. NameLength := 256; // Максимальная длина имени пользователя UserName := _AllocMem(@AllocMem)(NameLength); // Получаем имя пользователя _GetUserName(@GetUserName)(UserName,NameLength); // Создаем строку с именем файла 'х.txt' FileName[0] := 'x'; FileName[1] := '.'; // Строки надежнее всего создавать так. FileName[2] := 't'; FileName[3] := 'x'; FileName[4] := 't'; FileName[5] := #0 ; // Не забываем терминальный нуль // Создаем файл для записи. В модуле SysUtils есть функция FileCreate, но // она использует в параметрах тип String, что приводит к вставке // незапланированного кода, поэтому ее не используем. FileHandle := Integer(_CreateFile(@CreateFile)(@FileName,GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)); dec(NameLength); // Обрубаем хвостовой нуль //Пишем в файл имя пользователя if _FileWrite(@FileWrite)(FileHandle,UserName^, NameLength) = NameLength then Result := True; // Освобождаем ресурсы. _FileClose(@FileClose)(FileHandle); // Вызов ненастоящей функции FreeMem мы заменим // на нашу настоящую функцию FreeMemory _FreeMemory(@FreeMemory)(UserName); // Хвалимся успехами if Result then _MessageBox(@MessageBox)(0,pMessage,pTitle,0); end; Часть прототипов функций и различные проверки опущены для краткости (смотри их вприлагаемом демо-проекте). Имя пользователя получаем как обычно с помощью функцииGetUserName и записываем его в файл x.txt. В конце выскакивает окно сообщения с текстом,который передали функции в параметрах. Функция объявлены как stdcall, чтобы четкоопределиться, что параметры передаются через стек (а не через регистры, как в Delphi поумолчанию), чтобы удобнее было за ними следить в отладчике.Теперь остается зарезервировать память в стеке, переместить туда нашу функцию и передать нанее управление. Для резервирования памяти просто объявим локальный байтовый массив, т.к.все локальные переменные резервируются в стеке. procedure Main; var buf: array[0..511] of Byte; // Место в стеке c запасом … Ну вот. В стеке у нас есть место на полкилобайта – с запасом на всякий случай. Осталосьскопировать туда функцию Secret. Для копирования можно использовать CopyMemory илиMove.Move(Secret,buf,FuncLen);Стоп! А чему равна FuncLen - длина функции Secret? Здесь придется делать допущение, что вскомпилированном коде функции будут располагаться в том же порядке, как мы написали висходнике (и это действительно так). Поэтому если сделаем так: program Project1; ... function Secret(pTitle: PChar; pMessage: PChar): Boolean; stdcall; ... procedure Main; begin //Здесь копируем в стек функцию Secret ... end; begin Main; end. и получается, что длину функции можно будет вычислить путем вычитания адреса функцииSecret из адреса функции Main: FuncLen := Cardinal(@Main) - Cardinal(@Secret); И еще один момент. Раньше можно было без проблем выполнять код, расположенный в стеке,но теперь с этим стали жестоко бороться и запрещать исполняемый стек программными ааппаратными средствами. (если хочешь знать больше, то ищи статьи про DEP - Data ExecutionPrevention) По идее это должно привести с уменьшению количества атак типа buffer overflow(переполнение буфера). Поэтому, чтобы код работал везде, надо явно разрешить исполнениекода в странице памяти стека (куда мы копируем функцию). Станица памяти имеет размер 2 кби с помощью VirtualProtect нам нужно изменить атрибуты 2-х страниц, на тот случай, еслифункция окажется в конце первой страницы. VirtualProtect(buf, 2, PAGE_EXECUTE_READWRITE, OldPageProtection); В общем виде код функции Main выглядит так: procedure Main; var FuncLen: Cardinal; // Размер кода функции buf: array[0..511] of Byte; // Место в стеке c запасом MovedFunction: _Secret; // Указатель на функцию в стеке OldPageProtection: Cardinal;// СТарые аттрибуты страницы памяти title, ok_string: String; // Строки для всплывающего окна begin title := 'Пример выполнения кода в стэке'; ok_string := 'РАБОТА В СТЕКЕ: Файл успешно создан.'; // Загоняем функцию Secret в стэк FuncLen := Cardinal(@Main) - Cardinal(@Secret); Move(Secret,buf,FuncLen); MovedFunction := @buf; //Устанавливаем указатель на начало функции в стеке // Устанавливаем в стеке аттрибут выполнения кода if not VirtualProtect(@buf,2,PAGE_EXECUTE_READWRITE,OldPageProtection) then Exit; try // Запускаем функцию в стеке if MovedFunction(PChar(title), PChar(ok_string)) then ShowMessage('Работа в стеке прошла успешно.') else ShowMessage('Ошибка при работе в стеке.'); // Восстанавливаем аттрибуты страниц VirtualProtect(@buf,FuncLen,OldPageProtection,OldPageProtection); except ShowMessage('Hello hacker!'); end; end; Код полностью откомментирован. Вопросов быть не должно. Вызов секретной функциинаходится в блока trу..except. Это надо делать в любом случае. Мало ли что может произойти упользователя на компе. Но нам обработка исключений потребуется для совершенноконкретного случаю – ловли взломщика! JКомпилим экзешник (до сих пор использую Delphi 7) и запускаем его. Появляются подряд дваокошка, которые сообщают, что все идет нормально, а каталоге программы создается файл x.txtс именем пользователя.Теперь попробуем поймать вызов секретной функции. Мы знаем, что имя пользователяполучается с помощью функции GetUserName. Поэтому загружаем экзешник в отладчикOllyDbg и ставим точку останова на функции GetUserNameA (bpx GetUserNameA в команднойстроке). Запускаем на выполнение - вываливается сообщение «Hello hacker!», а отладчиквсплывает где-то в ntdll.dll Файл x.txt не создается. Тоже самое происходит при попыткеотладить секретную функцию с помощью стандартного отладчика Delphi. Но вот еслипоставить железный бряк (hardware on execution), то отладчик послушно всплывает посерединестека и позволяет полностью протрассировать код секретной функции.Получился довольно хитрый антиотладочный прием против начинающих взломщиков.Разберемся, как он работает? Обязательно! Когда отладчик ставит бряк, то он ставит на этоместо специальную инструкцию int 3 (опкод CC), которая вызывает отладочное исключение.Это исключение отладчик отлавливает и сразу заменяет это CC на тот байт, который стоял наэтом месте до установки бряка.А теперь смотри, что получается у нас. Вот окрестности вызова GetUserNameA в стеке. Серымподсвечена инструкция, которую заменит отладчик.http://www.stranger.nextmail.ru/del7.jpgТочка останова (инструкция int3) ставится в секции кода на вызове функции GetUserNameA, ноэтот код никогда не исполняется. Когда мы копируем функцию в стек, то вместе с остальнымкодом копируется и инструкция int3.http://www.stranger.nextmail.ru/del8.jpgНо об этой новой точке останова отладчик ничего не знает, т.к. он ее не устанавливал! Поэтомуint 3 в коде секретной функции не удаляется. Процессор ее выполняет и возникает исключение.Его-то и ловит блок try..except и приветствует неудавшегося взломщика. И кроме того посмотрикак изуродовался код после int3. В случае с железным бряком никакого изменения кода непроисходит, поэтому трассировка проходит нормально. Собственно таким образом я и получилэти скриншоты.Если тебе все еще показалось мало описанных в этой статье извращений, то никто не мешаетвзять на вооружение технику модификации кода, описанную в первой части. Изменяй кодскопированной с стек функции, как твоей душе будет угодно. Здесь я этого делать не стал,чтобы совсем не запутать. В результате идет лесом не только отладчик, но еще и дизассемблер.Можно даже прикрутить к проге простенький дизассембер и удалять из скопированного кодаточки останова. Хотя, имхо, это уже стрельба из пушки по воробьям, т.к. железный бряк сведетна нет все усилия.

 

[Уроки для хакеров и программистов](http://itblogs.org/)

---

Source: [https://yvision.kz/post/poluperemeshchaemyy-kod-318277](https://yvision.kz/post/poluperemeshchaemyy-kod-318277)