Разместите нашу кнопку!

Новые статьи:

Programming articles

Создание сайтов на шаблонах

Множество вариантов работы с графикой на канве

Шифруем файл с помощью другого файла

Перехват API функций - Основы

Как сделать действительно хороший сайт

Создание почтового клиента в Delphi 7

Применение паскаля для решения геометрических задач

Управление windows с помощью Delphi

Создание wap сайта

Операционная система unix, термины и понятия

SQL враг или друг

Возникновение и первая редакция ОС UNIX

Оптимизация проекта в Delphi

Ресурсы, зачем нужны ресурсы

Термины программистов 20 века

Советы по созданию собственного сайта с нуля

Шифруем файл с помощью пароля

Фракталы - геометрия природы

Crypt - Delphi программа для шифрования

Рассылка, зачем она нужна и как ее организовать?

Учебник по C++ для начинающих программистов

Уроки для изучения ассемблера

Загадочный тип PCHAR

Средства по созданию сайтов

Операторы преобразования классов is и as

Borland Developer studio 2006. Всё в одном

Создание базы данных в Delphi, без сторонних БД


Software engineering articles



Архив рассылки

    В нашей рассылке вы узнаете о новых статьях, программах, и темах на форуме. Рассылка выходит один раз в неделю, каждый четверг.
Подписаться на рассылку можно в этой форме:
Клуб программистов
Последний выпуск

27 выпуск

КЛУБ ПРОГРАММИСТОВ

в рассылке статьи, ссылки, инфо и многое другое о программировании и программистах

Новости

Лабораторные работы

Справочники

Книги

Основы Delphi

Клуб

Чат

Форум

От ведущего

Всем привет! Мы встречаемся с вами вот уже в 27 выпуске! Как же быстро летит время, а еще недавно был новый год :))))) Надеюсь, что вы вчерашний День Валентина провели также хорошо как и я, а я провел его очень хорошо! Завидуйте ))

Итак, что у нас сегодня! А сегодня в выпуске: наиполезнейшая статья Перехват API функций , самые свежие новости о Конкурсе программистов, отборные анекдоты в рубрике "Немного юмора". Желаю вам приятного времяпрепровождения за чтением выпуска.

__________________________________________________________

 

Перехват API функций. Основы .

 

    Здравствуйте, Delphi'сты и им сочувствующие. Приветствуем мою очередную статью для чайников. Сегодня я вам расскажу, как надо перехватывать API функции. Перехватывая API функции, мы увеличиваем наши возможности и граница им только наше воображение. Для прочтения данной статьи с максимальной пользой обязательны хоть начальные знания низкоуровневого программирования и знание архитектуры работы Windows.
    Сегодня я вам расскажу наиболее действенную методику перехвата API функций - сплайсинг. Сплайсинг это подмена кода функции. Конечно, есть другой метод перехвата это редактирование таблицы импорта приложения. Рассказывать буду по порядку.

Когда вы пишете в своём приложении так

Function Func1(param*:type*):restype*;stdcall;external 'libname.dll';

Вы импортируете функцию статически. Адрес функции прописывается в таблице импорта вашего приложения (допустим, что адрес нашей функции $7BC56010).
адрес      значение
……
$00405F56  7BC56010
……

А при вызове функции происходит так
Push …
push …
call dword ptr $00405F56


    Следовательно, для перехвата функции нам надо только подменить значение по адресу $00405F56 на своё, а для вызова оригинальной функции получать адрес функции через GetProcAddress. Но приложение может также получить адрес функции через GetProcAddress и вызывать перехватываемую функцию минуя, перехватчик. Данный метод бесперспективен.
    Идём дальше. Теперь я объясню технику сплайсинга. Наша функция находится по адресу $7C80B529 и допустим, что там такой код
7C80B529 8BFF mov edi, edi
7C80B52B 55 push ebp
7C80B52C 8BEC mov ebp, esp
7C80B52E 837D 08 00 cmp dword ptr ss:[ebp+8], 0


     Для перехвата функции от нас требуется только переписать начальный код функции, так чтобы он передавал управление нашему обработчику. Для передачи управления нашему обработчику достаточно всего лишь одной инструкции jmp на абсолютный адрес. Эта инструкция займёт всего лишь 5 байт - сам опкод этой инструкции () и значение для прыжка. Это значение вычисляется так

v=0-(s-d)
s - Смещение следующей команды
d - Требуемый адрес для jmp, т.е. адрес обработчика

Если немного переделать эту формулу, то она будет выглядеть так

v=d-FunctionAddress-5

     Теперь при каждом вызове целевой функции, всегда будет передаваться управление нашему обработчику. А как теперь вызвать оригинальную функцию. При установке перехвата нам надо сохранять первые 5 байт функции. Для вызова оригинала надо восстанавливать надо функции и вызывать ее, потом снова устанавливать перехват. Объявим структуру в которой будем сохранять первые 5 байт функции:

  
PFunctionRestoreData = ^ TFunctionRestoreData;
  TFunctionRestoreData = packed record
     Address:Pointer;
      val1:Byte;
      val2:DWORD;
   end;

    Поле Address фактически в этой структуре не нужен (он просто не к чему), но, тем не менее, так удобнее снимать перехват, потом сами поймете, зачем это поле нужно. Назовём эту структуру "мост" к старой функции.
    Теперь напишем функцию, которая будет устанавливать перехват:

{
  ProcAddress, ????????? ?? ??????? ???????
  NewProcAddress:pointer; ????????? ?? ??????? ???????????
  RestoreDATA:PFunctionRestoreData ????????? ?? ???? ? ?????? ???????
  
  Result: boolean ??????????
}

function SetCodeHook(ProcAddress, NewProcAddress: pointer; RestoreDATA:PFunctionRestoreData):boolean;
var
  OldProtect, JMPValue:DWORD;
begin
  Result:=False;
  if not VirtualProtect(ProcAddress,5,PAGE_EXECUTE_READWRITE,OldProtect) then exit;
  JMPValue := DWORD (NewProcAddress) - DWORD (ProcAddress) - 5;
  RestoreDATA^.val1:= Byte(ProcAddress^);
  RestoreDATA^.val2:= DWORD(Pointer(DWORD(ProcAddress)+1)^);
  RestoreDATA^.Address:=ProcAddress;
  byte(ProcAddress^):=;
  DWORD(Pointer(DWORD(ProcAddress)+1)^):=JMPValue;
  Result:=VirtualProtect(ProcAddress,5,OldProtect,OldProtect);
end;

   Мы сначала устанавливает атрибуты доступа к коду функции, так чтобы можно было его переписывать. Потом вычисляем значение для прыжка. Сначала сохраняем начало функции в запись, потом переписываем начало функции. В конце устанавливаем старые атрибуты доступа. Теперь напишем функцию которая будет снимать перехват:
{
  Procedure: UnHookCodeHook
  
  RestoreDATA:PFunctionRestoreData ????? ????? ? ?????? ???????
}


 
 function UnHookCodeHook(RestoreDATA:PFunctionRestoreData):Boolean;
var
  ProcAddress:Pointer;
  OldProtect,JMPValue:DWORD;
begin
  Result:=False;
  ProcAddress:=RestoreDATA^.Address;
  if not VirtualProtect(ProcAddress,5,PAGE_EXECUTE_READWRITE,OldProtect) then exit;
  Byte(ProcAddress^):=RestoreDATA^.val1;
  DWORD(Pointer(DWORD(ProcAddress)+1)^):=RestoreDATA^.val2;
  Result:=VirtualProtect(ProcAddress,5,OldProtect,OldProtect);
end;

     Я думаю, что здесь всё понятно - просто восстанавливаем начало перехватываемой функции. Адрес перехватываемой функции берём из структуры указатель, на которую передаётся функции в качестве единственного параметра.

     Теперь напишем функцию, которая будет устанавливать перехват по имени функции. Я думаю что она тоже не слишком сложная.
{
  ModuleHandle:HMODULE; Хендл модуля, в котором находится целевая функция
  ProcedureName:PChar; Имя процедуры
  NewProcedureAddress:Pointer; Указатель на процедуру перехватчик
  RestoreDATA:PFunctionRestoreData Указатель на мост к старой функции
  
  Result: Boolean Успешность установки перехвата
}

function SetProcedureHook(ModuleHandle:HMODULE;ProcedureName:PChar;NewProcedureAddress:Pointer;
  RestoreDATA:PFunctionRestoreData):Boolean;
var
   ProcAddress:Pointer;
begin
   ProcAddress:=GetProcAddress(ModuleHandle,ProcedureName);
   Result:=SetCodeHook(ProcAddress,NewProcedureAddress,RestoreDATA);
end;
     Идём дальше. Описанные выше функции могут перехватывать функции только в текущем процессе. А как нам перехватывать функции в других процессах. Наиболее простой метод это засунуть перехватчик функции в DLL и в коде библиотечной функции устанавливать перехват, если DLL загружается в процесс и снимать перехват, если она выгружается. Тут ещё одна проблема: как заставить другой процесс загрузить нашу DLL'ку. Наиболее простое решение это создание удалённых потоков. Теперь всё по порядку.
  Удалённый поток создаётся функцией CreateRemoteThread.
HANDLE CreateRemoteThread(
HANDLE hProcess, // хендл потока в котором создаётся поток
LPSECURITY_ATTRIBUTES lpThreadAttributes,//атрибуты безопасности
DWORD dwStackSize, //размер стека
LPTHREAD_START_ROUTINE lpStartAddress,//адрес функции потока т.е. адрес первой инструкции потока
LPVOID lpParameter, // указатель на параметр для функции
DWORD dwCreationFlags, // флаги создания
LPDWORD lpThreadId // указатель на переменную, в которой будет сохранён ID потока
);
Функция потока должна иметь следующие атрибуты

DWORD WINAPI ThreadFunc(PVOID pvPararn)

    По ходу наверно WINAPI обозначает stdcall это не важно, что она обозначает, всё равно она должна быть объявлена как stdcall. У функции только один параметр -это обычный указатель. Теперь проанализируем ситуацию… Думаем…Шевелим мозгами…Функция LoadLibraryA имеет такие же атрибуты. Она принимает указатель на первый символ имени файла DLL(строка должна кончаться символом #0). Следовательно, функция LoadLibraryA полностью подходит для того, что бы она могла выступать в качестве функции потока. Так как она принимает указатель на строку в своём процессе, нам надо будет записать в память чужого процесса нашу строку и именем файла DLL. Это делается функцией WriteProcessMemory. Вот её описание

BOOL WriteProcessMemory(
HANDLE hProcess, // хендл процесса
LPVOID lpBaseAddress,// адрес по которому надо писать
LPVOID lpBuffer, // указатель на буфер
DWORD nSize, // количество байт для записи
LPDWORD lpNumberOfBytesWritten //количество реально записанных байт
);
  Адрес функции LoadLibraryA мы получаем с помощью функции GetProcAddress так библиотека kernel32.dll и ntdll.dll грузятся во все процессы всегда по одним и тем же адресам, следовательно, то адрес, полученный в адресном пространстве нашего процесса, будет действителен и адресном пространстве любого другого процесса. Теперь напишем функцию, которая загружает вашу DLL в адресное пространство чужого процесса.

function LoadLibrary_Ex(ProcessID:DWORD;LibName:PChar):boolean;
var
  pLL,pDLLPath:Pointer;
  hProcess,hThr:THandle;
  LibPathLen,_WR,ThrID:DWORD;
begin
  Result:=False;
  LibPathLen:=Length(string(LibName));
  hProcess:=OpenProcess(PROCESS_ALL_ACCESS,false,ProcessID);
  if hProcess=0 then exit;
  pDLLPath:=VirtualAllocEx(hProcess,0,LibPathLen+1,MEM_COMMIT,PAGE_READWRITE);
  if DWORD(pDLLPath)=0 then exit;
  pLL:=GetProcAddress(GetModuleHandle(kernel32),'LoadLibraryA');
  WriteProcessMemory(hProcess,pDLLPath,LibName,LibPathLen+1,_WR);
  hThr:=CreateRemoteThread(hProcess,0,0,pLL,pDLLPath,0,ThrID);
  if hThr=0 then exit;
  Result:=CloseHandle(hProcess);
end;

   Таким образом, мы загрузили свою DLL в чужой процесс. Вообще - то внедрение своего кода это совсем другая история и требует написания отдельной статьи. Иногда процессу не разрешается изменение памяти системных процессов, таких как winlogon.exe, lsass.exe, smss.exe, csrss.exe и др. для этого нужна привилегия SeDebugPrivilege в исходник модуля для перехвата API функций есть функция EnableDebugPrivilege которая включает эту привилегию для текущего процесса.
  Идём дальше. Теперь у нас мы научились загружать свою DLL в чужой процесс. Но для должного эффекта нам надо перехватывать DLL во всех процессах системы. Но как это сделать. Можно просто перечислять процессы через ToolHelp32 и загружать свою DLL в каждый найденный процесс. Но не приемлемо, так как во вновь созданных процессах функции не будут перехвачены. Но можно каждую секунду перечислять процессы, короче это тоже неприемлемо и очень долгая история. Самый простой метод это воспользоваться техникой так называемых хуков. Когда мы ставим глобальный хук на сообщения окон с помощью функции SetWindowsHookEx этой функции мы передаёт адрес функции обработчика и хендл DLL в которой находится функция обработчик. После того как какое-либо приложение хочет отправить какому-нибудь окну, то проверяется, установлена ли ловушка на сообщение, которое он хочет послать окну. Если да то загружается DLL, в которой находится обработчик сообщений, вызывается обработчик и т.д. Следовательно, после установки хука на сообщения наша DLL будет находиться во всех процессах, которые хотя бы один раз отправили сообщения окнам, т.е. почти во всех GUI процессах. Каркас DLL с перехватом функций будет выглядеть так примерно так

library HideDLL;


const
   MutexName='__API_HOOK';
……………
    //обработчик сообщений
function MsgProc(code:DWORD;wParam,lparam:DWORD):DWORD;stdcall;
begin
   CallNextHookEx(SH,code,wParam,lparam);
end;

     //внедрение нашей DLL во все GUI процессы
procedure SetWindowsHook(e:Boolean); stdcall;
var
   M:THandle;
begin
   if e then
     begin
      m:=CreateMutex(0,false,MutexName);
      if m=0 then exit;
      SH:=SetWindowsHookEx(WH_GETMESSAGE,@MsgProc,HInstance,0);
    end
        else
    UnhookWindowsHookEx(sh);
end;

    //главный библиотечный обработчик
procedure DLLEntryPoint(dwReason:DWord);
begin
  case dwReason of
    DLL_PROCESS_ATTACH:
    begin
    // StopProcess(GetCurrentProcessId);
       SetWindowsHook(true);
       SetProcedureHook(GetModuleHandle('ntdll.dll'),'ZwQuerySystemInformation',@NewSystemFunction,@SystemFunctionBridge);
    // ResumeProcess(GetCurrentProcessId);
    end;
    DLL_PROCESS_DETACH:
    begin
    // StopProcess(GetCurrentProcessId);
        SetWindowsHook(false);
        UnHookCodeHook(@SystemFunctionBridge);
    //ResumeProcess(GetCurrentProcessId);
    end;
  end;
end;

begin
  DllProc:= @DLLEntryPoint; //устанавливаеем библиотечный обработчик
  DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

     Чтобы установить перехват на API функции во всех процессах (во всех GUI процессах) достаточно просто загрузить нашу DLL. Достаточно написать вот такой код:

LoadLibrary(pchar(ExtractFileDir(Application.ExeName)+'\'+'HideDLL.dll'));

    В архиве с исходниками есть модуль с функциями перехвата API там есть все функции, которые я сегодня использовал и описывал.

    Вот, пожалуй, и всё на сегодня. В архиве так же есть пример скрытия процесса lsass.exe. Скачиваем архив здесь
.

Автор статьи: Руслан Аблязов, почетный участник Клуба программистов

__________________________________________________________

 

Новости Конкурса программистов

Вот уже который раз говорим о конкурсе. Но сегодня я не буду томить вас своими глупыми "речами" и лучше сразу скажу что надо. Как мы все знаем, первый этап конкурса закончился еще 2 недели назад. Сейчас уже время подводить итоги второго этапа и всего конкурса в общем.

Внимание! Участники прошедшие во второй этап! Срочно шлите окончательные версии работы!

20 числа последний день конкурса! В этот день буду объявлены победители и разосланы призы, так что не пропустите )))

Приглашаем всех на " Форум программистов "

__________________________________________________________

 

Немного юмора

Как распознать кто есть кто в психиатрической лечебнице?
Нужно подойти к первому попавшемуся и плюнуть ему в лицо.
Если первый попавшийся начал плакать - это больной;
Если ругаться - посетитель;
Если дал в морду - санитар;
Если плюнул в ответ - лечащий врач.

***

Наконец-то закончился последний день рабочей недели...
Одеваю куртку, сажусь за комп, жму кнопку выключить...
И тут в кабинет заходит шеф... Успеваю краем глаза на мониторе еще не выключившегося компа глянуть время... Мол не рано ли собрался домой?
И вижу: понедельник, 8:31
Типа пошла первая минута новой трудовой недели! )))
Вот это глюк!!!

***

- Почему компьютерщику трудно устроится на работу?
- Потому что он не умеет писать. Все на клавиатуре да на клавиатуре.

***

Встречаются двое юзеров. Один говорит:
- Я тут свежий антивирус достал, не хочешь себе установить?
- Нет, мне это не нужно.
- Почему?
- Да мой комп так глючит, что на нем ни один вирус не запустится.

***

Новая рекламная акция от Майкрософт:
"Купите операционные системы Windows ME, Windows CE для КПК и мобильных телефонов и Windows NT. Установите это всё в одну папку и получите супер-Windows CEMENT"!

Юмор от членов клуба программистов здесь .

__________________________________________________________

 

Программистам на заметку

Если у вас есть желание поделиться своим опытом с читателями, вы являетесь обладателем ценных, интересных статей или авторских программ, а может вы просто хотите высказать свое мнение или замечание о рассылке, просто напишите мне . Я постараюсь уделить внимание каждому. А " Клуб программистов " всегда рад приветствовать в своих рядах новых пользователей.

 

© 2006, Кочари Арнольд и "Клуб программистов"