Перехоплення API функцій. Основи, Різне, Програмування, статті

Здрастуй читач. Сьогодні я розповім досить таки ефективну методику перехоплення API функцій. Не слід думати, що якщо ми хочемо перехопити API функції, то ми пишемо або троян, вірус і ще якусь заразу, за допомогою перехоплення 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 байт – сам опкод цієї інструкції ($ E9) і значення для стрибка. Це значення обчислюється так


 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 фактично в цій структурі не потрібен (він просто не до чого), поле потрібно тільки для того щоб було зручніше знімати перехоплення. Назвемо цю структуру “міст” до старої функції.


Тепер напишемо функцію, яка буде встановлювати перехоплення:


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^):=$E9;


  DWORD(Pointer(DWORD(ProcAddress)+1)^):=JMPValue;


  Result:=VirtualProtect(ProcAddress,5,OldProtect,OldProtect);


end;


Ми спочатку встановлює атрибути доступу до коду функції, так щоб можна було його переписувати. Потім обчислюємо значення для стрибка. Спочатку зберігаємо початок функції в запис, потім переписуємо початок функції. В кінці встановлюємо старі атрибути доступу.
Тепер напишемо функцію, яка буде знімати перехоплення:


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;


Я думаю, що тут все зрозуміло – просто відновлюємо початок перехоплюваних функції. Адреса перехоплюваних функції беремо зі структури покажчик, на яку передається функції в якості єдиного параметра.


Тепер напишемо функцію, яка буде встановлювати перехоплення на ім’я функції.


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)


У функції тільки один параметр-це звичайний покажчик. Тепер проаналізуємо ситуацію … Думаємо … ворушити мізками … Функція 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. У прикладених до статті исходниках є функція EnableDebugPrivilege, яка включає цей привілей для поточного процесу.


Йдемо далі. Тепер у нас ми навчилися завантажувати свою DLL в чужій процес. Але для належного ефекту нам треба перехоплювати DLL у всіх процесах системи. Але як це зробити. Можна просто перераховувати процеси через ToolHelp32 і завантажувати свою DLL в кожен знайдений процес. Але не прийнятно, тому що у новостворених процесах функції не будуть перехоплені. Але можна кожну секунду перераховувати процеси, коротше це теж неприйнятно і дуже довга історія. Найпростіший метод це скористатися тим що надає нам механізм хуков. Коли ми ставимо небудь глобальний хук за допомогою функції SetWindowsHookEx то DLL, в якій знаходиться функція обробник хука, завантажується в усі процеси, які отримують повідомлення від системи через функції GetMessage і PeekMessage. Каркас DLL з перехопленням функцій буде виглядати так приблизно так


library HideDLL;


uses


  Windows,


  ExtendedAPIFunctions,


  apihooktools;


{$R *.res}


const


  MutexName=”__API_HOOK”;


function MsgProc(code:DWORD;wParam,lparam:DWORD):DWORD;stdcall;


begin


  Result:=CallNextHookEx(SH,code,wParam,lparam);


end;


procedure SetWindowsHook(e:Boolean); stdcall;


var


  M:THandle;


begin


  if e then


   begin


    M:=CreateMutex(0,false,MutexName);


    if GetLastErrorERROR_ALREADY_EXISTS then


     begin


     SH:=SetWindowsHookEx(WH_GETMESSAGE,@MsgProc,HInstance,0);


     MutexHandle:=M;


    end                                   else


    CloseHandle(M);


   end


       else


   begin


    UnhookWindowsHookEx(SH);


    CloseHandle(MutexHandle);


   end;


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);


      UnHookCodeHook(@SystemFunctionBridge);


      SetWindowsHook(false);


      //ResumeProcess(GetCurrentProcessId);


     end;


   end;


end;


begin


   DllProc:= @DLLEntryPoint;


   DLLEntryPoint(DLL_PROCESS_ATTACH);


end.


Щоб встановити перехоплення на API функції у всіх процесах (у всіх GUI процесах) досить просто завантажити нашу DLL. Достатньо написати ось такий код:


LoadLibrary(pchar(ExtractFileDir(Application.ExeName)+””+”DLL.dll”));


Також слід пам’ятати одну міру обережності при установці і знятті перехоплення на функції: треба зупинити всі інші потоки поточного процесу, так як під час установки перехоплення інший потік може викликати потрібну функцію і це призведе до непередбачуваних наслідків.
В архіві з исходниками є заголовний файл для Delphi (apihooktools.pas), в якому описані всі функції, які я сьогодні використовував і описував.
Ось, мабуть, і все на сьогодні. В архіві так само є приклад приховування процесу lsass.exe.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*