2

My application is an x64 Windows console app that needs to inject itself into another running process. When executed from the command line, you enter the PID of the process you want to inject into as a command line parameter.

Within the context of a thread running under a parent process, I'm able to work with String variables, but I'm having difficulty trying to figure out how to convert an Integer to a String in Delphi. Everything I've tried to convert from Integer to String has the effect of crashing the parent process. I understand that standard Delphi RTL commands are not going to work, and that I need to use WINAPI functions.

Here is a list of some of the commands I've tried:

a. IntToStr(int) crashes parent process;

b. itoa(src, dst, radix) crashes parent process;

c. strcpy(dst, src) crashes parent process;

I've included a working snippet of code that compiles in Delphi RAD Studio RIO 10.3.2. Be sure to set the Target Platform as a Windows-64-bit. As-is, the program simply injects into a process and displays a MessageBox. I've included and commented out the commands that crash the parent process.

In this sample program, I'm attempting to display the PID of the running process which was determined using GetCurrentProcessId(), which returns the PID as an Integer. The challenge is trying to convert the variable 'x' to a string variable 's'. I've also tried converting 'x' into a PAnsiChar variable using itoa(), which fails.

I anticipate that my issue is likely that I'm not loading the correct Windows library, or that I'm not defining the WINAPI function that I'm trying to use.

Any assistance would be greatly appreciated, as I'm stuck and can't move forward until I get past this hurdle.

program Inject;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Variants,
  System.Classes,
  Winapi.Windows,
  Winapi.Messages,
  ShellAPI,
  System.Win.crtl,
  shlobj;

var
  ClassName: string;
  ProcessHandle: Thandle;
  Active : Integer;
  PID : integer;
  Module, NewModule: Pointer;
  Size: SIZE_T;
  BytesWritten: SIZE_T;
  TID: DWORD;

procedure Main;
var
  x : Integer;
  s : string;
  p : PAnsiChar;
begin
  LoadLibrary('kernel32.dll');
  LoadLibrary('user32.dll');
  LoadLibrary('msvcrt.dll');
  LoadLibrary('win32.dll');

  x := GetCurrentProcessId;

  { This command crashes the parent process }
  // s := IntToStr(x);

  { This command crashes the parent process }
  // itoa(x, p, 10);

  { This command crashes the parent process }
  strcpy(P, PAnsiChar(IntToStr(x)));

  { A standard Message Box works }
  MessageBox(0, 'This Message Box produced by Thread running under Parent Process', 'Process ID', 0);

  { This Message Box crashes the parent process }
  // MessageBox(0, PWideChar(IntToStr(x)), 'Process ID', 0);

  ExitThread(0);
end;

begin
try
   if (ParamCount() > 0) then
   begin
      PID := StrToInt(ParamStr(1));
      ProcessHandle := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
      Module := Pointer(GetModuleHandle(nil));
      Size := PImageOptionalHeader64(Pointer(integer(Module) + PImageDosHeader(Module)._lfanew + SizeOf(dword) + SizeOf(TImageFileHeader))).SizeOfImage;
      VirtualFreeEx(ProcessHandle, Module, 0, MEM_RELEASE);
      NewModule := VirtualAllocEx(ProcessHandle, Module, Size, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
      WriteProcessMemory(ProcessHandle, NewModule, Module, Size, BytesWritten);
      CreateRemoteThread(ProcessHandle, nil, 0, @Main, Module, 0, TID);
      WaitForSingleObject(ProcessHandle, 1000);
  end
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Scooter
  • 41
  • 1
  • 7

1 Answers1

5

In your Main() procedure, when calling itoa() or strcpy(), your p variable is an uninitialized pointer, it doesn't point to any valid memory. You need to allocate memory to write to, eg:

procedure Main;
var
  x : Integer;
  str : array[0..12] of AnsiChar;
begin
  LoadLibrary('kernel32.dll');
  LoadLibrary('user32.dll');
  LoadLibrary('msvcrt.dll');
  LoadLibrary('win32.dll');

  x := GetCurrentProcessId;
  itoa(x, str, 10);

  MessageBoxA(0, str, 'Process ID', 0);
  ExitThread(0);
end;

In the case of strcpy(), you have the additional problem that IntToStr() in Delphi 2009+ returns a UnicodeString, not an AnsiString, so your typecast to PAnsiChar is wrong anyway.

Alternatively, look at the Win32 wsprintfA() function in user32.dll:

procedure Main;
var
  x : Integer;
  str : array[0..12] of AnsiChar;
begin
  LoadLibrary('kernel32.dll');
  LoadLibrary('user32.dll');
  LoadLibrary('msvcrt.dll');
  LoadLibrary('win32.dll');

  x := GetCurrentProcessId;
  wsprintfA(str, '%d', x);

  MessageBoxA(0, str, 'Process ID', 0);
  ExitThread(0);
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you - itoa() no longer crashes the parent process now that you advised me to initialize it using the array of AnsiChar. However, the MessageBox is throwing an error now, it that PWideChar is incompatible with array of AnsiChar. I tried to typecast it, and the result is that the message box is empty when it displays. I'll post code shortly. – Scooter Dec 21 '19 at 18:28
  • So close... itoa() no longer crashes the Parent Process so this is a step in the right direction. This states incompatible types: PWideChar and Arrary of AnsiChar `MessageBox(0, str, 'Process ID', 0);` This still yields an invalid typecast, but does not crash the parent process: `MessageBox(0,PWideChar(str), 'Process ID', 0);` This crashes the Parent Process: `MessageBox(0,PWideChar(AnsiString(str)), 'Process ID', 0);` – Scooter Dec 21 '19 at 18:36
  • I'm trying to format my comment properly... but line-breaks are not working. – Scooter Dec 21 '19 at 18:41
  • This worked. `MessageBox(0, PWideChar(WideString(str)), 'Process ID', 0);` – Scooter Dec 21 '19 at 18:45
  • @Scooter look closer at my answer, notice that I had changed `MessageBox` to `MessageBoxA`, that is an important detail. You are using a Unicode version of Delphi, so `MessageBox` actually calls `MessageBoxW` instead, that is why you needed `PWideChar`. But since your data is `AnsiChar`, use `MessageBoxA` instead to avoid data conversions. Otherwise, change the data to `WideChar` instead (`_itow`, etc) – Remy Lebeau Dec 21 '19 at 21:54
  • You're right, I did not notice that you changed MessageBox to MessageBoxA. Thank you for following up. Clearly, I need to better educate myself with respect to the various variable types, otherwise I can see myself running into more issues down the road. – Scooter Dec 21 '19 at 23:40