SttUnhooker - Unhook Apis for Local and Remote Processes

posted 11 Jul 2010, 15:06 by Delphi Basics   [ updated 4 Sep 2010, 20:06 ]
This unit by StTwister allows you to unhook apis for local and remote processes. It also contains functions which can be used as an alternative to the GetProcAddress Api.

StTwister on experts exchange:

The unit is well-commented for your understanding. As you can read in the comments, this unit was written after Aphex's afxcodehook and Leak Test (soon to be published here).

{*****************************************************************}
{                                                                 }
{       SttUnhooker unit by StTwister                             }
{       http://gateofgod.com                                      }
{       StTwister2003@yahoo.co.uk                                 }
{                                                                 }
{       Unhooks APIs for both local and remote processes          }
{                                                                 }
{*****************************************************************}
unit untSttUnhooker;

// if the DISPLAY_ERRORS flag is enabled, any error/warning will show up
{$DEFINE DISPLAY_ERRORS}

interface

uses
  Windows;

function UnHookAPI(strModuleName, strFuncName: string): boolean;
function UnHookAPIEx(hProcess: THandle; strModuleName, strFuncName: string): boolean;
function GetRealProcAddress(strModuleName, strFuncName: string): Pointer;
function GetRealProcAddressEx(hProcess: THandle; strModuleName, strFuncName: string): Pointer;

var
  pCreateRemoteThread: function(hProcess: THandle; lpThreadAttributes: Pointer; dwStackSize: DWORD; lpStartAddress: TFNThreadStartRoutine; lpParameter: Pointer; dwCreationFlags: DWORD; var lpThreadId: DWORD): THandle; stdcall;
  pVirtualAllocEx: function(hProcess: THandle; lpAddress: Pointer; dwSize, flAllocationType: DWORD; flProtect: DWORD): Pointer; stdcall;
  pWriteProcessMemory: function(hProcess: THandle; const lpBaseAddress: Pointer; lpBuffer: Pointer; nSize: DWORD; var lpNumberOfBytesWritten: DWORD): BOOL; stdcall;

implementation

type
  TSmallArray = array[1..20] of byte;

  PRemoteInfo = ^TRemoteInfo;
  TRemoteInfo = record
    pGetModuleHandle: function(lpModuleName: PAnsiChar): cardinal; stdcall;
    pGetProcAddress: function(hModule: cardinal; lpProcName: PAnsiChar): Pointer; stdcall;
    pGetModuleFileName: function(hModule: cardinal; lpFilename: PAnsiChar; nSize: cardinal): cardinal; stdcall;
    lpModuleName, lpFuncName, lpFilename: PChar;
    lpFuncAddress: Pointer;
    dwLength: DWORD;
  end;


const
  Opcodes1: array [0..255] of word =
  (
    (16913),(17124),(8209),(8420),(33793),(35906),(0),(0),(16913),(17124),(8209),(8420),(33793),(35906),(0),(0),(16913),
    (17124),(8209),(8420),(33793),(35906),(0),(0),(16913),(17124),(8209),(8420),(33793),(35906),(0),(0),(16913),
    (17124),(8209),(8420),(33793),(35906),(0),(32768),(16913),(17124),(8209),(8420),(33793),(35906),(0),(32768),(16913),
    (17124),(8209),(8420),(33793),(35906),(0),(32768),(529),(740),(17),(228),(1025),(3138),(0),(32768),(24645),
    (24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(69),
    (69),(69),(69),(69),(69),(69),(69),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(24645),(0),
    (32768),(228),(16922),(0),(0),(0),(0),(3072),(11492),(1024),(9444),(0),(0),(0),(0),(5120),
    (5120),(5120),(5120),(5120),(5120),(5120),(5120),(5120),(5120),(5120),(5120),(5120),(5120),(5120),(5120),(1296),
    (3488),(1296),(1440),(529),(740),(41489),(41700),(16913),(17124),(8209),(8420),(17123),(8420),(227),(416),(0),
    (57414),(57414),(57414),(57414),(57414),(57414),(57414),(32768),(0),(0),(0),(0),(0),(0),(32768),(33025),
    (33090),(769),(834),(0),(0),(0),(0),(1025),(3138),(0),(0),(32768),(32768),(0),(0),(25604),
    (25604),(25604),(25604),(25604),(25604),(25604),(25604),(27717),(27717),(27717),(27717),(27717),(27717),(27717),(27717),(17680),
    (17824),(2048),(0),(8420),(8420),(17680),(19872),(0),(0),(2048),(0),(0),(1024),(0),(0),(16656),
    (16800),(16656),(16800),(33792),(33792),(0),(32768),(8),(8),(8),(8),(8),(8),(8),(8),(5120),
    (5120),(5120),(5120),(33793),(33858),(1537),(1602),(7168),(7168),(0),(5120),(32775),(32839),(519),(583),(0),
    (0),(0),(0),(0),(0),(8),(8),(0),(0),(0),(0),(0),(0),(16656),(416)
  );

  Opcodes2: array [0..255] of word =
  (
    (280),(288),(8420),(8420),(65535),(0),(0),(0),(0),(0),(65535),(65535),(65535),(272),(0),(1325),(63),
    (575),(63),(575),(63),(63),(63),(575),(272),(65535),(65535),(65535),(65535),(65535),(65535),(65535),(16419),
    (16419),(547),(547),(65535),(65535),(65535),(65535),(63),(575),(47),(575),(61),(61),(63),(63),(0),
    (32768),(32768),(32768),(0),(0),(65535),(65535),(65535),(65535),(65535),(65535),(65535),(65535),(65535),(65535),(8420),
    (8420),(8420),(8420),(8420),(8420),(8420),(8420),(8420),(8420),(8420),(8420),(8420),(8420),(8420),(8420),(16935),
    (63),(63),(63),(63),(63),(63),(63),(63),(63),(63),(63),(63),(63),(63),(63),(237),
    (237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(101),(237),(1261),
    (1192),(1192),(1192),(237),(237),(237),(0),(65535),(65535),(65535),(65535),(65535),(65535),(613),(749),(7168),
    (7168),(7168),(7168),(7168),(7168),(7168),(7168),(7168),(7168),(7168),(7168),(7168),(7168),(7168),(7168),(16656),
    (16656),(16656),(16656),(16656),(16656),(16656),(16656),(16656),(16656),(16656),(16656),(16656),(16656),(16656),(16656),(0),
    (0),(32768),(740),(18404),(17380),(49681),(49892),(0),(0),(0),(17124),(18404),(17380),(32),(8420),(49681),
    (49892),(8420),(17124),(8420),(8932),(8532),(8476),(65535),(65535),(1440),(17124),(8420),(8420),(8532),(8476),(41489),
    (41700),(1087),(548),(1125),(9388),(1087),(33064),(24581),(24581),(24581),(24581),(24581),(24581),(24581),(24581),(65535),
    (237),(237),(237),(237),(237),(749),(8364),(237),(237),(237),(237),(237),(237),(237),(237),(237),
    (237),(237),(237),(237),(237),(63),(749),(237),(237),(237),(237),(237),(237),(237),(237),(65535),
    (237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(237),(0)
  );

  Opcodes3: array [0..9] of array [0..15] of word =
  (
    ((1296),(65535),(16656),(16656),(33040),(33040),(33040),(33040),(1296),(65535),(16656),(16656),(33040),(33040),(33040),(33040)),
    ((3488),(65535),(16800),(16800),(33184),(33184),(33184),(33184),(3488),(65535),(16800),(16800),(33184),(33184),(33184),(33184)),
    ((288),(288),(288),(288),(288),(288),(288),(288),(54),(54),(48),(48),(54),(54),(54),(54)),
    ((288),(65535),(288),(288),(272),(280),(272),(280),(48),(48),(0),(48),(0),(0),(0),(0)),
    ((288),(288),(288),(288),(288),(288),(288),(288),(54),(54),(54),(54),(65535),(0),(65535),(65535)),
    ((288),(65535),(288),(288),(65535),(304),(65535),(304),(54),(54),(54),(54),(0),(54),(54),(0)),
    ((296),(296),(296),(296),(296),(296),(296),(296),(566),(566),(48),(48),(566),(566),(566),(566)),
    ((296),(65535),(296),(296),(272),(65535),(272),(280),(48),(48),(48),(48),(48),(48),(65535),(65535)),
    ((280),(280),(280),(280),(280),(280),(280),(280),(566),(566),(48),(566),(566),(566),(566),(566)),
    ((280),(65535),(280),(280),(304),(296),(304),(296),(48),(48),(48),(48),(0),(54),(54),(65535))
  );

function LowerCase(const S: string): string;
var
  Ch: Char;
  i: Integer;
  Source, Dest: PChar;
begin
  i := Length(S);
  SetLength(Result, i);
  Source := PChar(S);
  Dest := PChar(Result);
  while i <> 0 do
  begin
    Ch := Source^;
    if (Ch >= 'A') and (Ch <= 'Z') then
      Inc(Ch, 32);
    Dest^ := Ch;
    Inc(Source);
    Inc(Dest);
    Dec(i);
  end;
end;


// displays an error message if the DISPLAY_ERRORS flag is enabled
procedure Error(strError: string);
begin
{$IFDEF DISPLAY_ERRORS}
  // change this line if you want the error to appear in a different way than a messagebox.
  // For example, you could use Write() in console apps or append to a log file
  MessageBox(0, PChar(strError), 'Error', MB_ICONERROR);
{$ENDIF}
end;

// as small LDE to calculate the length of CPU instructions
// taken from Aphex's afxCodeHook unit (http://www.iamaphex.com)
function SizeOfCode(Code: pointer): longword;
var
  Opcode: word;
  Modrm: byte;
  Fixed, AddressOveride: boolean;
  Last, OperandOveride, Flags, Rm, Size, Extend: longword;
begin
  try
    Last := longword(Code);
    if Code <> nil then
    begin
      AddressOveride := False;
      Fixed := False;
      OperandOveride := 4;
      Extend := 0;
      repeat
        Opcode := byte(Code^);
        Code := pointer(longword(Code) + 1);
        if Opcode = $66 then
        begin
          OperandOveride := 2;
        end
        else if Opcode = $67 then
        begin
          AddressOveride := True;
        end
        else
        begin
          if not ((Opcode and $E7) = $26) then
          begin
            if not (Opcode in [$64..$65]) then
            begin
              Fixed := True;
            end;
          end;
        end;
      until Fixed;
      if Opcode = $0f then
      begin
        Opcode := byte(Code^);
        Flags := Opcodes2[Opcode];
        Opcode := Opcode + $0f00;
        Code := pointer(longword(Code) + 1);
      end
      else
      begin
        Flags := Opcodes1[Opcode];
      end;
      if ((Flags and $0038) <> 0) then
      begin
        Modrm := byte(Code^);
        Rm := Modrm and $7;
        Code := pointer(longword(Code) + 1);
        case (Modrm and $c0) of
          $40: Size := 1;
          $80:
            begin
              if AddressOveride then
              begin
                Size := 2;
              end
              else
                Size := 4;
              end;
          else
          begin
            Size := 0;
          end;
        end;
        if not (((Modrm and $c0) <> $c0) and AddressOveride) then
        begin
          if (Rm = 4) and ((Modrm and $c0) <> $c0) then
          begin
            Rm := byte(Code^) and $7;
          end;
          if ((Modrm and $c0 = 0) and (Rm = 5)) then
          begin
            Size := 4;
          end;
          Code := pointer(longword(Code) + Size);
        end;
        if ((Flags and $0038) = $0008) then
        begin
          case Opcode of
            $f6: Extend := 0;
            $f7: Extend := 1;
            $d8: Extend := 2;
            $d9: Extend := 3;
            $da: Extend := 4;
            $db: Extend := 5;
            $dc: Extend := 6;
            $dd: Extend := 7;
            $de: Extend := 8;
            $df: Extend := 9;
          end;
          if ((Modrm and $c0) <> $c0) then
          begin
            Flags := Opcodes3[Extend][(Modrm shr 3) and $7];
          end
          else
          begin
            Flags := Opcodes3[Extend][((Modrm shr 3) and $7) + 8];
          end;
        end;
      end;
      case (Flags and $0C00) of
        $0400: Code := pointer(longword(Code) + 1);
        $0800: Code := pointer(longword(Code) + 2);
        $0C00: Code := pointer(longword(Code) + OperandOveride);
        else
        begin
          case Opcode of
            $9a, $ea: Code := pointer(longword(Code) + OperandOveride + 2);
            $c8: Code := pointer(longword(Code) + 3);
            $a0..$a3:
              begin
                if AddressOveride then
                begin
                  Code := pointer(longword(Code) + 2)
                end
                else
                begin
                  Code := pointer(longword(Code) + 4);
                end;
              end;
          end;
        end;
      end;
    end;
    Result := longword(Code) - Last;
  except
    Result := 0;
  end;
end;

// unhooks some APIs used for remote unhooking, so FWs don't catch it
procedure InitInjectionAPIs;
begin
  pVirtualAllocEx := GetRealProcAddress('kernel32', 'VirtualAllocEx');
  pWriteProcessMemory := GetRealProcAddress('kernel32', 'WriteProcessMemory');
  pCreateRemoteThread := GetRealProcAddress('kernel32', 'CreateRemoteThread');
end;

// this is a function that will be injected in the remote thread to get the function address
// and full path to library file
procedure RemoteThread(Param: Pointer); stdcall;
begin
  With TRemoteInfo(Param^) do
  begin
    lpFuncAddress := pGetProcAddress(pGetModuleHandle(lpModuleName), lpFuncName);
    dwLength := pGetModuleFileName(pGetModuleHandle(lpModuleName), lpFileName, 4096);
  end;
end;

// null function used to calculate the size of the RemoteThread function
procedure RemoteThreadEnd; stdcall;
begin
end;

function GetRemoteProcAddress(hProcess: THandle; strModuleName, strFuncName: string;
  var strFileName: string): pointer;
var
  RemoteInfo: TRemoteInfo;
  dwBytesWritten, dwSize: DWORD;
  lpRemoteInfo, lpFunc: Pointer;
  TID: cardinal;
begin
  // if we are unhooking local process, we don't need to inject a function
  if hProcess = GetCurrentProcess then
  begin
    Result := GetProcAddress(GetModuleHandle(Pchar(strModuleName)), PChar(strFuncName));
    SetLength(strFileName, 4096);
    dwBytesWritten := GetModuleFileName(GetModuleHandle(PChar(strModuleName)), PChar(strFileName), 4096);
    SetLength(strFileName, dwBytesWritten);
    exit;
  end;

  // fill API addresses into RemoteInfo
  RemoteInfo.pGetModuleHandle := GetProcAddress(GetModuleHandle('kernel32'), 'GetModuleHandleA');
  RemoteInfo.pGetProcAddress := GetProcAddress(GetModuleHandle('kernel32'), 'GetProcAddress');
  RemoteInfo.pGetModuleFileName := GetProcAddress(GetModuleHandle('kernel32'), 'GetModuleFileNameA');

  // allocate memory for the strings in the remote process
  RemoteInfo.lpModuleName := pVirtualAllocEx(hProcess, nil, Length(strModuleName)+1, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RemoteInfo.lpFuncName := pVirtualAllocEx(hProcess, nil, Length(strFuncName)+1, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RemoteInfo.lpFileName := pVirtualAllocEx(hProcess, nil, 4096, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);

  // write strings to remote process
  pWriteProcessMemory(hProcess, RemoteInfo.lpModuleName, PChar(strModuleName), Length(strModuleName) + 1, dwBytesWritten);
  pWriteProcessMemory(hProcess, RemoteInfo.lpFuncName, PChar(strFuncName), Length(strFuncName), dwBytesWritten);

  // allocate memory for the remote info structure in the remote process
  lpRemoteInfo := pVirtualAllocEx(hProcess, nil, SizeOf(TRemoteInfo), MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);

  // write remote info structure to the remote process
  pWriteProcessMemory(hProcess, lpRemoteInfo, @RemoteInfo, SizeOf(RemoteInfo), dwBytesWritten);

  // calculate the size of the remote function
  dwSize := DWORD(@RemoteThreadEnd) - DWORD(@RemoteThread);

  // allocate meory for the remote function in the remote process
  lpFunc := pVirtualAllocEx(hProcess, nil, dwSize, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);

  // write remote function to the remote process
  pWriteProcessMemory(hProcess, lpFunc, @RemoteThread, dwSize, dwBytesWritten);

  // execute the remote function
  TID := pCreateRemoteThread(hProcess, nil, 0, lpFunc, lpRemoteInfo, 0, TID);

  // wait for the remote thread to terminate
  WaitForSingleObject(TID, INFINITE);

  // get back the results
  ReadProcessMemory(hProcess, lpRemoteInfo, @RemoteInfo, SizeOf(RemoteInfo), dwBytesWritten);

  Result := RemoteInfo.lpFuncAddress;

  // get the module path
  SetLength(strFileName, RemoteInfo.dwLength);
  ReadProcessMemory(hProcess, RemoteInfo.lpFilename, PChar(strFileName), RemoteInfo.dwLength, dwBytesWritten);

  // clean up
  VirtualFreeEx(hProcess, RemoteInfo.lpModuleName, Length(strModuleName)+1, MEM_RELEASE);
  VirtualFreeEx(hProcess, RemoteInfo.lpFuncName, Length(strFuncName)+1, MEM_RELEASE);
  VirtualFreeEx(hProcess, RemoteInfo.lpFileName, 4096, MEM_RELEASE);
  VirtualFreeEx(hProcess, lpRemoteInfo, SizeOf(TRemoteInfo), MEM_RELEASE);
  VirtualFreeEx(hProcess, lpFunc, dwSize, MEM_RELEASE);
end;

// read the first bytes of the function to see if it's hooked or not
function ReadFunctionBytes(hProcess: Thandle; strModuleName, strFuncName: string;
  var SmallArray: TSmallArray; var lpFuncAddress: Pointer; var strFileName: string): boolean;
var
  dwBytesRead: dword;
begin
  Result := False;

  // if module is kernel32, we don't need to inject a routine to get the function
  // address since kernel32 APIs have the same address in all processes
  if (LowerCase(strModuleName) = 'kernel32') or (LowerCase(strModuleName) = 'kernel32.dll') then
  begin
    lpFuncAddress := GetProcAddress(GetModuleHandle(PChar(strModuleName)), PChar(strFuncName));
    SetLength(strFileName, 4096);
    dwBytesRead := GetModuleFileName(GetModuleHandle(Pchar(strModuleName)), PChar(strFileName), 4096);
    SetLength(strFileName, 4096);
  end
  else
  begin
    lpFuncAddress := GetRemoteProcAddress(hProcess, strModuleName, strFuncName, strFileName);
  end;

  if lpFuncAddress = nil then
  begin
    Error('Cannot get the address of function '+strFuncName+' function!');
    exit;
  end;

  // read the first 20 bytes of the function to detect if function is hooked and to calculate
  // how many bytes need to be rewritten
  ReadProcessMemory(hProcess, lpFuncAddress, @SmallArray, SizeOf(SmallArray), dwBytesRead);
  if dwBytesRead <> SizeOf(SmallArray) then
  begin
    Error('Cannot read from remote function address!');
    exit;
  end;

  Result := True;
end;

// reads the original start bytes of an API directly from the library file
function ReadOriginalFunctionBytes(strModuleName, strFuncName, strFileName: string;
  var SmallArray: TSmallArray; intNops: Integer): boolean;

  function GetFieldOffset(const Struct; const Field): Cardinal;
  begin
    Result := Cardinal(@Field) - Cardinal(@Struct);
  end;

  // replacement of IMAGE_FIRST_SECTION macro
  function GetImageFirstSection(NtHeader: PImageNtHeaders): PImageSectionHeader;
  begin
    Result := PImageSectionHeader(Cardinal(NtHeader) +
      GetFieldOffset(NtHeader^, NtHeader^.OptionalHeader) +
      NtHeader^.FileHeader.SizeOfOptionalHeader);
  end;

var
  hFile: THandle;
  lpstrFuncName: PChar;
  lpData, lpFunc: Pointer;
  dwSize,dwBytesRead, dwVirtualOffset, dwPhysicalOffset, dwFuncOrdinal: DWORD;
  i: Integer;
  bFound: Boolean;
  DosHeader: PImageDosHeader;
  NTHeader: PImageNtHeaders;
  ExportDir: PImageExportDirectory;
  Directory: PImageDataDirectory;
  SectionHeader: PImageSectionHeader;
begin
  Result := False;

  // open the file in read mode
  hFile := CreateFile(PChar(strFileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
  if hFile = INVALID_HANDLE_VALUE then
  begin
    Error('Cannot open file '+strModuleName+'!');
    CloseHandle(hFile);
    exit;
  end;

  // copy to memory
  dwSize := GetFileSize(hFile, nil);
  lpData := GetMemory(dwSize);
  ReadFile(hFile, lpData^, dwSize, dwBytesRead, nil);
  CloseHandle(hFile);
  if dwBytesRead <> dwSize then
  begin
    Error('Cannot read from file '+strModuleName+'!');
    FreeMem(lpData, dwSize);
    exit;
  end;

  // load the MZ and PE headers
  DosHeader := lpData;
  if DosHeader.e_magic <> IMAGE_DOS_SIGNATURE then
  begin
    Error('Invalid MZ header in file '+strModuleName+'!');
    FreeMem(lpData, dwSize);
    exit;
  end;
  NTHeader := Pointer(Integer(lpData) + DosHeader._lfanew);
  if NTHeader.Signature <> IMAGE_NT_SIGNATURE then
  begin
    Error('Invalid PE header in file '+strModuleName+'!');
    FreeMem(lpData, dwSize);
    exit;
  end;

  // get the export table virtual address
  Directory := @NTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
  if Directory.Size = 0 then
  begin
    Error('No export table found in file '+strModuleName+'!');
    FreeMem(lpData, dwSize);
    exit;
  end;
  dwVirtualOffset := Directory.VirtualAddress;

  // find the section where the export table is located
  // dwVirtualOffset = offset where the export table would normally reside in memory
  // dwPhysicalOffset = offset where export table is located in current app memory (as if it was a file)
  dwPhysicalOffset := 0;
  SectionHeader := GetImageFirstSection(NtHeader);
  for i := 1 to NTHeader.FileHeader.NumberOfSections do
  begin
    if (dwVirtualOffset >= SectionHeader.VirtualAddress) and (dwVirtualOffset < SectionHeader.VirtualAddress + SectionHeader.SizeOfRawData) then
    begin
      dwPhysicalOffset := SectionHeader.PointerToRawData + (dwVirtualOffset - SectionHeader.VirtualAddress);
      break;
    end;
    SectionHeader := Pointer(DWORD(SectionHeader) + SizeOf(TImageSectionHeader));
  end;

  if dwPhysicalOffset = 0 then
  begin
    Error('Cannot find section where export table is located in file '+strModuleName+'!');
    FreeMem(lpData, dwSize);
    exit;
  end;

  ExportDir := Pointer(DWORD(lpData) + dwPhysicalOffset);

  // loop through all functions to find right function (with the name strFuncName)
  bFound := False;
  lpFunc := Pointer(DWORD(ExportDir) + (DWORD(ExportDir.AddressOfNames) - dwVirtualOffset));
  for i := 1 to ExportDir.NumberOfNames do
  begin
    lpstrFuncName := Pointer(DWORD(ExportDir) + (DWORD(lpFunc^) - dwVirtualOffset));
    if lpstrFuncName = strFuncName then
    begin
      bFound := True;
      break;
    end;
    lpFunc := Pointer(DWORD(lpFunc) + SizeOf(DWORD));
  end;

  if not bFound then
  begin
    Error('Function '+strFuncName+' not found in the export table of the file '+strModuleName+'!');
    FreeMem(lpData, dwSize);
    exit;
  end;

  // find the function ordinal associated to the function name
  lpFunc := Pointer(DWORD(ExportDir) + (DWORD(ExportDir.AddressOfNameOrdinals) - dwVirtualOffset));
  lpFunc := Pointer(Integer(lpFunc) + (i - 1) * SizeOf(WORD));
  dwFuncOrdinal := WORD(lpFunc^) + ExportDir.Base;
  if (dwFuncOrdinal < ExportDir.Base) or (dwFuncOrdinal > ExportDir.Base + ExportDir.NumberOfFunctions - 1) then
  begin
    Error('No function ordinal found for function '+strFuncName+' in the file '+strModuleName+'!');
    FreeMem(lpData, dwSize);
    exit;
  end;

  // get the function entry address using the function ordinal
  lpFunc := Pointer(DWORD(ExportDir) + (DWORD(ExportDir.AddressOfFunctions) - dwVirtualOffset + (dwFuncOrdinal - ExportDir.Base) * SizeOf(DWORD)));

  // finally we got the function address. Now we must find the coresponding section and copy
  // the function code
  dwPhysicalOffset := 0;
  SectionHeader := GetImageFirstSection(NtHeader);
  for i := 1 to NTHeader.FileHeader.NumberOfSections do
  begin
    if (DWORD(lpFunc^) >= SectionHeader.VirtualAddress) and (DWORD(lpFunc^) < SectionHeader.VirtualAddress + SectionHeader.SizeOfRawData) then
    begin
      dwPhysicalOffset := SectionHeader.PointerToRawData + (DWORD(lpFunc^) - SectionHeader.VirtualAddress);
      break;
    end;
    SectionHeader := Pointer(DWORD(SectionHeader) + SizeOf(TImageSectionHeader));
  end;

  if dwPhysicalOffset = 0 then
  begin
    Error('Cannot find function '+strFuncName+' code in the file '+strModuleName+'!');
    FreeMem(lpData, dwSize);
    exit;
  end;

  // finally we can copy our needed data into SmallArray
  CopyMemory(@SmallArray, Pointer(DWORD(lpData) + dwPhysicalOffset), intNops);

  // free the loaded file
  FreeMem(lpData, dwSize);
  Result := True;

end;

// unhooks an API, given the module name and function name
// only unhooks overwriting/extended overwriting hooks
function UnHookAPI(strModuleName, strFuncName: string): boolean;
begin
  Result := UnHookAPIEx(GetCurrentProcess, strModuleName, strFuncName);
end;

// unhooks an API of a remote process, given the modeule name and function name
// only unhooks overwriting/extended overwriting hooks
function UnHookAPIEx(hProcess: THandle; strModuleName, strFuncName: string): boolean;
var
  SmallArray: TSmallArray;
  intNops: Integer;
  dwBytesWritten: DWORD;
  lpFuncAddress: Pointer;
  strFileName: string;
begin
  Result := False;

  // read the first 20 bytes of the function
  // also gets the address of the function in the remote process and the full path to library
  if not ReadFunctionBytes(hProcess, strModuleName, strFuncName, SmallArray, lpFuncAddress, strFileName) then
    exit;

  // if the function is hooked, it contains a JMP as the first operations
  // therefore, if the first byte is not $E9, then we know the function is not hooked
  if SmallArray[1] <> $E9 then
  begin
    Result := True;
    exit;
  end;

  // read how many NOPs exist after the JMP so we can know how many bytes need to be rewritten
  intNops := 0;
  while SmallArray[6 + intNops] = $90 do
    inc(intNops);
  // intNops + 5 = total number of bytes that need to be rewritten
  intNops := intNops + 5;

  if not ReadOriginalFunctionBytes(strModuleName, strFuncName, strFileName, SmallArray, intNops) then
    exit;

  pWriteProcessMemory(hProcess, lpFuncAddress, @SmallArray, intNops, dwBytesWritten);
  if dwBytesWritten <> DWORD(intNops) then
  begin
    Error('Cannot write data to remote API location');
    exit;
  end;

  Result := True;
end;

// Creates a new function that behaves exactly like the original one, given the module name
// and function name, but without being hooked.
// Use this function rather than UnHookAPI if u want to have access to both hooked and unhooked
// functions or if u don;t want the function to be rehooked
function GetRealProcAddress(strModuleName, strFuncName: string): Pointer;
begin
  Result := GetRealProcAddressEx(GetCurrentProcess, strModuleName, strFuncName);
end;

function GetRealProcAddressEx(hProcess: THandle; strModuleName, strFuncName: string): Pointer;
var
  SmallArray: TSmallArray;
  dwBytesWritten, dwLength, dwLen: DWORD;
  lpFuncAddress, lpNewFuncAddress, lpAddr: Pointer;
  strFileName: string;
begin
  Result := nil;

  // read the first 20 bytes of the function
  // also gets the address of the function in the remote process and the full path to library
  if not ReadFunctionBytes(hProcess, strModuleName, strFuncName, SmallArray, lpFuncAddress, strFileName) then
    exit;

  if not ReadOriginalFunctionBytes(strModuleName, strFuncName, strFileName, SmallArray, 20) then
    exit;

  // use a LDE to find the length of the instructions that have benn overwirtten by the JMP
  lpAddr := @SmallArray;
  dwLength := 0;
  While dwLength < 5 do
  begin
    dwLen := SizeOfCode(lpAddr);
    if dwLen = 0 then
      exit;
    dwLength := dwLength + dwLen;
    lpAddr := Pointer(DWORD(lpAddr) + dwLen);
  end;

  // allocate memory for the new function
  if @pVirtualAllocEx <> nil then
    lpNewFuncAddress := pVirtualAllocEx(hProcess, nil, dwLength + 5, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE)
  else
    lpNewFuncAddress := VirtualAllocEx(hProcess, nil, dwLength + 5, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  if lpNewFuncAddress = nil then
  begin
    Error('Cannot allocate memory for the new function');
    exit;
  end;

  // after we've got the bytes that were modified, we need to link them back to the original
  // function with a JMP ($E9)
  SmallArray[dwLength + 1] := $E9;
  DWORD(Pointer(DWORD(@SmallArray) + dwLength + 1)^) := DWORD(lpFuncAddress) - DWORD(lpNewFuncAddress) - 5;

  // finally write the created function to the remote process
  if @pWriteProcessMemory <> nil then
    pWriteProcessMemory(hProcess, lpNewFuncAddress, @SmallArray, dwLength + 5, dwBytesWritten)
  else
    WriteProcessMemory(hProcess, lpNewFuncAddress, @SmallArray, dwLength + 5, dwBytesWritten);
  if dwBytesWritten <> DWORD(dwLength + 5) then
  begin
    Error('Cannot write data to remote API location');
    exit;
  end;

  Result := lpNewFuncAddress;
end;

initialization
  InitInjectionAPIs;

end.
Comments