Add Imports to PE File

posted 16 Mar 2010, 08:52 by Delphi Basics   [ updated 4 Jan 2011, 15:09 ]
{
   Unit: uAddImport
   Author: steve10120
   Description: Add imports to a PE file.
   Website: ic0de.org
   History: First try
              Added multiple import support.
}

unit uAddImport;
//steve10120
interface

uses Windows;

type
  TFuncArray = array of string;
  TImports = packed record
    szLibName:  string;
    Funcs:      TFuncArray;
  end;

procedure AddImport(szFilePath:string; Imports:TImports);

type
  PImageImportDescriptor = ^TImageImportDescriptor;
  TImageImportDescriptor = packed record
    OriginalFirstThunk: DWORD;
    TimeDateStamp: DWORD;
    ForwarderChain: DWORD;
    Name: DWORD;
    FirstThunk: DWORD;
  end;

type
  PImportByName = ^TImportByName;
  TImportByName = packed record
    Name1:        DWORD;
  end;

type
  TByteArray = array of Byte;

implementation

function FileToBytes(sPath:string; var bFile:TByteArray):Boolean;
var
hFile:    THandle;
dSize:    DWORD;
dRead:    DWORD;
begin
  Result := FALSE;
  hFile := CreateFile(PChar(sPath), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
  if hFile <> 0 then
  begin
    dSize := GetFileSize(hFile, nil);
    SetFilePointer(hFile, 0, nil, FILE_BEGIN);
    SetLength(bFile, dSize);
    if ReadFile(hFile, bFile[0], dSize, dRead, nil) then
      Result := TRUE;
    CloseHandle(hFile);
  end;
end;

procedure BytesToFile(bData:TByteArray; sPath:string);
var
hFile:    THandle;
dWritten: DWORD;
begin
  hFile := CreateFile(PChar(sPath), GENERIC_WRITE, FILE_SHARE_WRITE, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  if hFile <> 0 then
  begin
    SetFilePointer(hFile, 0, nil, FILE_BEGIN);
    WriteFile(hFile, bData[0],Length(bData), dWritten, nil);
    CloseHandle(hFile);
  end;
end;

function OffsetToRVA(dwOffset:DWORD; dwVA:DWORD; dwRaw:DWORD):DWORD;
begin
  Result := dwOffset - dwRaw + dwVA;
end;

function RVAToOffset(dwRVA:DWORD; dwVA:DWORD; dwRaw:DWORD):DWORD;
begin
  Result := dwRVA - dwVA + dwRaw;
end;

function Align(dwValue:DWORD; dwAlign:DWORD):DWORD;
begin
  if dwAlign <> 0 then
  begin
    if dwValue mod dwAlign <> 0 then
    begin
      Result := (dwValue + dwAlign) - (dwValue mod dwAlign);
      Exit;
    end;
  end;
  Result := dwValue;
end;

procedure AddImport(szFilePath:string; Imports:TImports);
var
  bFile:    TByteArray;
  IDH:      PImageDosHeader;
  INH:      PImageNtHeaders;
  ISH:      PImageSectionHeader;
  IID:      PImageImportDescriptor;
  IBN:      array of TImportByName;
  i:        DWORD;
  dwIATPos: DWORD;
  dwPos:    DWORD;
  bBuff:    TByteArray;
  dwSize:   DWORD;
begin
  if FileToBytes(szFilePath, bFile) then
  begin
    IDH := @bFile[0];
    if IDH.e_magic = IMAGE_DOS_SIGNATURE then
    begin
      INH := @bFile[IDH._lfanew];
      if INH.Signature = IMAGE_NT_SIGNATURE then
      begin
        for i := 0 to INH.FileHeader.NumberOfSections - 1 do
        begin
          ISH := @bFile[IDH._lfanew + 248 + i * 40];
          if (INH.OptionalHeader.DataDirectory[1].VirtualAddress >= ISH.VirtualAddress) and (INH.OptionalHeader.DataDirectory[1].VirtualAddress <= (ISH.VirtualAddress + ISH.Misc.VirtualSize)) then
          begin
            dwIATPos := RVAToOffset(INH.OptionalHeader.DataDirectory[1].VirtualAddress, ISH.VirtualAddress, ISH.PointerToRawData);
            Break;
          end;
        end;
        IID := @bFile[dwIATPos];
        SetLength(bBuff, 20);
        dwPos := 0;
        repeat
          CopyMemory(@bBuff[dwPos], IID, 20);
          Inc(IID);
          Inc(dwPos, 20);
          SetLength(bBuff, dwPos + 20);
        until IID.Name = 0;
        dwPos := Length(bFile);
        dwSize := Align(Length(bBuff) + 20, INH.OptionalHeader.FileAlignment);
        SetLength(bFile, Length(bFile) + dwSize);
        ISH := @bFile[IDH._lfanew + 248 + (INH.FileHeader.NumberOfSections - 1) * 40];
        Imports.szLibName := Imports.szLibName + #0;
        CopyMemory(@bFile[dwPos], @Imports.szLibName[1], Length(Imports.szLibName));
        IID.Name := OffsetToRVA(dwPos, ISH.VirtualAddress, ISH.PointerToRawData);
        Inc(dwPos, Length(Imports.szLibName));
        SetLength(IBN, Length(Imports.Funcs));
        for i := 0 to Length(Imports.Funcs) - 1 do
        begin
          Imports.Funcs[i] := #0#0 + Imports.Funcs[i] + #0;
          CopyMemory(@bFile[dwPos], @Imports.Funcs[i][1], Length(Imports.Funcs[i]));
          IBN[i].Name1 := OffsetToRVA(dwPos, ISH.VirtualAddress, ISH.PointerToRawData);
          Inc(dwPos, Length(Imports.Funcs[i]));
        end;
        IID.FirstThunk := OffsetToRVA(dwPos + Length(bBuff) + 20, ISH.VirtualAddress, ISH.PointerToRawData);
        CopyMemory(@bBuff[Length(bBuff) - 20], IID, 20);
        SetLength(bBuff, Length(bBuff) + 20);
        CopyMemory(@bFile[dwPos], @bBuff[0], Length(bBuff));
        INH.OptionalHeader.DataDirectory[1].VirtualAddress := OffsetToRVA(dwPos, ISH.VirtualAddress, ISH.PointerToRawData);
        INH.OptionalHeader.DataDirectory[1].Size := Length(bBuff);
        Inc(dwPos, Length(bBuff));
        for i := 0 to Length(IBN) - 1 do
        begin
          CopyMemory(@bFile[dwPos], @IBN[i], 4);
          Inc(dwPos, 4);
        end;
        Inc(ISH.SizeOfRawData, dwSize);
        Inc(ISH.Misc.VirtualSize, dwSize);
        CopyMemory(@bFile[IDH._lfanew + 248 + (INH.FileHeader.NumberOfSections - 1) * 40], ISH, 40);
        INH.OptionalHeader.SizeOfImage := ISH.VirtualAddress + ISH.Misc.VirtualSize;
        CopyMemory(@bFile[IDH._lfanew], INH, 248);
        BytesToFile(bFile, szFilePath);
      end;
    end;
  end;
end;

end.


Usage:
var
  Imports:  TImports;

begin
  SetLength(Imports.Funcs, 2);
  Imports.szLibName := 'urlmon.dll';
  Imports.Funcs[0] := 'URLDownloadToFileA';
  Imports.Funcs[1] := 'URLDownloadToFileW';
  AddImport('Hello.exe', Imports);
end.

Comments