Ce code vous permettra de redéfinir le code d'une fonction importée statiquement par une DLL dans votre programme par une fonction quelconque définie dans votre programme. Il y a très peu à faire pour modifier le code afin qu'il fasse la même chose dans un processus extérieur. La fonction ReplaceAPI de l'unité APIRedirect.pas fait tout le travail:
-Détermination de l'adresse de base où est chargé l'exécutable.
-Exploration de la table des symboles importés jusqu'à trouver la fonction initiale à remplacer
-Recopiage de code machine à partir d'un petit morceau d'assembleur "inline" encadré par 2 nombres magiques pour retrouver le code machine correspondant
-Ce morceau de code "Launcher" est ensuite modifié pour y mettre l'adresse de la nouvelle fonction désirée
-Ajout d'une section exécutable à l'exécutable contenant le code du launcher de la nouvelle fonction
-Et ça marche!
Attention toutefois à respecter SCRUPULEUSEMENT le nombre et le format des paramètres de la nouvelle fonction à remplacer. Et ne pas oublier les conventions d'appels (stdcall, etc...) sinon ça va tout faire planter. Par exemple, si vous jugez que la fonction MessageBox de Windows ne vous convient pas, vous pouvez utiliser:
function MyMessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall;
begin
<-Code personnel pour remplacer la fonction originale de Microsoft
end;
ReplaceAPI(kernel32,'MessageBoxA',@MyMessageBox);
Et le tour est joué!
Je fourni un petit programme d'exemple pour montrer l'utilité d'une telle fonction: il se charge de remplacer l'API GetSysColor de Microsoft par une autre fonction qui permet de changer les couleurs systèmes. Tous les composants VCL non natifs (c'est à dire qui ne sont pas entièrement peints par Windows) sont affectés. Et pas besoin de changer leur couleur!
J'admet que le résultat est visuellement assez pauvre, mais le principe est là.
Source / Exemple :
unit APIRedirect;
interface
uses
SysUtils,Windows,ImageHlp,Dialogs;
function GetImageBase(
Process:THandle; // Handle to a valid process object. Current thread must have PROCESS_VM_READ | PROCESS_VM_OPERATION access to it.
Thread:THandle // Handle to a valid thread object. Current thread must have THREAD_GET_CONTEXT access to it.
):Pointer; // Return the image base of the process.
function ReplaceAPI(
DllName:string; // Name of the DLL containing the procedure or function to replace
ProcName:string; // Name of the procedure or function to replace
NewProc:Pointer; // Pointer to the new desired procedure. The declaration of this procedure *MUST* be *EXACTLY* the same as the original API.
Process:THandle=0; // Handle to a valid process object (or zero for the current process). The caller must have
// PROCESS_SUSPEND_RESUME | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION
// access to it.
Thread:THandle=0 // Handle to a valid thread object (or zero for the current thread). The caller must have
// THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION
// access to it.
):Boolean;overload; // Return True if the API was successfully patched, false otherwise (for instance, if the process doesnt use the specified
// name in his import directory, or if the API was not found in the specified DLL).
type
PPEB=^TPEB;
TPEB=packed record
UselessData:array[0..1] of Cardinal; // This is of course not the real definition, but only the ImageBaseAddress field is of interest in our case
ImageBaseAddress:POINTER;
end;
PTEB=^TTEB;
TTEB=packed record
UselessData:array[0..11] of Cardinal; // This is of course not the real definition, but only the PEB field is of interest in our case
PEB:PPEB;
end;
IMAGE_IMPORT_BY_NAME=packed record
Hint:Word;
Name:CHAR;
end;
TImageImportByName=IMAGE_IMPORT_BY_NAME;
PImageImportByName=^IMAGE_IMPORT_BY_NAME;
IMAGE_THUNK_DATA=packed record
ForwarderString: PBYTE;
Func: PDWORD;
Ordinal: DWORD;
AddressOfData:PImageImportByName;
end;
TImageThunkData=IMAGE_THUNK_DATA;
PImageThunkData=^IMAGE_THUNK_DATA;
TIIDMisc=packed record
case Integer of
0:(Characteristics:DWORD); // 0 for terminating null import descriptor
1:(OriginalFirstThunk:DWORD); // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
end;
IMAGE_IMPORT_DESCRIPTOR=packed record
Misc:TIIDMisc;
TimeDateStamp:DWORD;
ForwarderChain:DWORD;
Name:DWORD;
FirstThunk:DWORD;
end;
TImageImportDescriptor=IMAGE_IMPORT_DESCRIPTOR;
PImageImportDescriptor=^IMAGE_IMPORT_DESCRIPTOR;
const
LOADER_START_MAGIC=$FAFAFAFA; // Magic flags arbitrary defined. These flags will allow us to retrieve
LOADER_END_MAGIC=$FEFEFEFE; // code offsets in asm code. Anyway, let us hope the generated machine code doed not
LOADER_PROC_MAGIC=$ABABABAB; // contain them! (I havent checked, but the probability is highliy negligible)
implementation
procedure Launcher; // A launcher for the new procedures. We insert flags to extract only the required code, without whatever Delphi will add
// for initialisation and finalisation of the call
asm
DD LOADER_START_MAGIC; // Flag to indicate start of code
PUSH EBP // Store EBP to API caller
CALL @START
@START: POP EBP
SUB EBP, OFFSET @START // Get base EBP
MOV EAX, [EBP+@@PROC] // Calculate new proc offset
POP EBP // Restore EBP to API caller
JMP EAX // JMP to new procedure address (no need to CALL)
@@PROC: DD LOADER_PROC_MAGIC; // Flag to indicate absolute address of new proc
DD LOADER_END_MAGIC; // Flag to indicate end of code
end;
function DataPos(Start:Pointer;Data:Cardinal):Cardinal;
begin
Result:=Cardinal(Start);
while PCardinal(Result)^<>Data do
Inc(Result);
end;
procedure GetLauncherCode(var Code:Pointer;var Size:Cardinal;Proc:Pointer);
var
p:Cardinal;
begin
p:=DataPos(@Launcher,LOADER_START_MAGIC)+4; // Retrieve position of launching code inside Launcher
Size:=DataPos(@Launcher,LOADER_END_MAGIC)-p; // Retrieve end of launching code
GetMem(Code,Size);
CopyMemory(Code,Pointer(p),Size); // Duplicate launching code
p:=DataPos(Code,LOADER_PROC_MAGIC); // Replace the absolute procedure address (which contains LOADER_PROC_MAGIC)...
PPointer(p)^:=PPointer(@Proc)^; // ...with the new one
end;
function GetImageBase(Process:THandle;Thread:THandle):Pointer; // This function uses undocumented APIs. One can find their descriptions there:
var // http://undocumented.ntinternals.net/
Context:TContext; // There are other (simplier) means to obtain the image base, but this one
SelEntry:TLDTEntry; // can extract the image base of other running processes.
FSBase:Cardinal;
TEB:TTEB;
PEB:TPEB;
n:Cardinal;
begin
Context.ContextFlags:=CONTEXT_FULL or CONTEXT_DEBUG_REGISTERS;
if not GetThreadContext(Thread,Context) then // See the above link for additional informations
RaiseLastOSError;
if not GetThreadSelectorEntry(Thread,Context.SegFs,SelEntry) then
RaiseLastOSError;
with SelEntry do
FSBase:=(BaseHi shl 24) or (BaseMid shl 16) or SelEntry.BaseLow;
if not VirtualProtectEx(Process,Ptr(FSBase),SizeOf(TEB),PAGE_READWRITE,@n) then
RaiseLastOSError;
if not ReadProcessMemory(Process,Ptr(FSBase),@TEB,SizeOf(TEB),n) then
RaiseLastOSError;
if not VirtualProtectEx(Process,TEB.Peb,SizeOf(PEB),PAGE_READWRITE,@n) then
RaiseLastOSError;
if not ReadProcessMemory(Process,TEB.Peb,@PEB,SizeOf(PEB),n) then
RaiseLastOSError;
Result:=TEB.Peb.ImageBaseAddress; //That's it, we now have the address at which the process is loaded
end;
function RedirectAPI(DllName:string;InitialVOffset,NewVOffset,ImageBase:Cardinal;ImageDosHeader:PImageDosHeader;ImageNTHeader:PImageNtHeaders):Boolean;
var
ImportDirectory:Cardinal;
ImageImportDescriptor:PImageImportDescriptor;
ImageThunkData:PImageThunkData;
begin
Result:=False;
ImportDirectory:=ImageNTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if ImportDirectory=0 then // Well, I think we can find cases where there are no imported libraries...
Exit;
ImageImportDescriptor:=Ptr(ImageBase+ImportDirectory);
while ImageImportDescriptor.Name<>0 do begin // Enumerate imported DLL's
if ImageImportDescriptor.Misc.OriginalFirstThunk<>0 then
ImageThunkData:=PImageThunkData(ImageBase+ImageImportDescriptor.Misc.OriginalFirstThunk)
else
ImageThunkData:=PImageThunkData(ImageBase+ImageImportDescriptor.FirstThunk);
while ImageThunkData.AddressOfData<>nil do begin // Enumerate procs imported from the DLL
if PCardinal(ImageThunkData)^=InitialVOffset then begin // Found the imported proc!
PCardinal(ImageThunkData)^:=NewVOffset; // Replace it and leave before Bill Gates notices anything!
Result:=True;
// Exit; //Apparently, there can be more than one instance of the imported proc in the table. So let's continue just in case...
end;
Inc(ImageThunkData);
end;
Inc(ImageImportDescriptor);
end;
end;
function ReplaceAPI(DllName:string;ProcName:string;NewProc:Pointer;Process,Thread:THandle):Boolean;
var
ImageBase,OldProc,RedirectMem,LauncherCode:Pointer;
ImageDosHeader:TImageDosHeader;
ImageNTHeader:TImageNtHeaders;
n,LauncherSize:Cardinal;
Module:HModule;
begin
if Process=0 then
Process:=GetCurrentProcess;
if Thread=0 then
Thread:=GetCurrentThread
else
SuspendThread(Thread);
try
ImageBase:=GetImageBase(Process,Thread);
if not ReadProcessMemory(Process,ImageBase,@ImageDosHeader,SizeOf(ImageDosHeader),n) then // The DOS header is located at offset 0...
RaiseLastOSError;
if not ReadProcessMemory(Process,Ptr(Integer(ImageBase)+ImageDosHeader._lfanew),@ImageNTHeader,SizeOf(ImageNTHeader),n) then
RaiseLastOSError; // The NT is located at offset _lfanew...
Module:=LoadLibrary(PChar(DllName));
OldProc:=GetProcAddress(Module,PChar(ProcName)); // Get the real API address
if not Assigned(OldProc) then
Assert(False,'Wrong library or procedure name'); // TODO: use Result:=False;Exit;
GetLauncherCode(LauncherCode,LauncherSize,NewProc);
try
RedirectMem:=VirtualAllocEx(Process,nil,LauncherSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE); // Let's add some new code to the module ;-)
if not WriteProcessMemory(Process,RedirectMem,LauncherCode,LauncherSize,n) then // Write the launcher code
RaiseLastOSError;
Result:=RedirectAPI(DllName,Cardinal(OldProc),Cardinal(RedirectMem),Cardinal(ImageBase),@ImageDosHeader,@ImageNTHeader); // OK, here we go, let's modify the headers
finally
FreeMem(LauncherCode);
end;
finally
ResumeThread(Thread);
end;
end;
end.
Conclusion :
Le code est de moi, je me suis un peu inspiré de cet article, même si ma méthode est un peu différente:
http://www.codeproject.com/useritems/inject2it.asp
Je préfère aussi prévenir tout de suite: CE N'EST PAS UN VIRUS donc pas la peine que les admins le désactivent. De toute façon j'ai changé le code pour qu'on ne puisse pas injecter du code dans un autre processus.
Le code est de niveau expert, son utilisation de niveau débutant, donc en moyenne initié...
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.