Skip to content

Instantly share code, notes, and snippets.

@erdesigns-eu
Created December 19, 2024 10:19
Show Gist options
  • Save erdesigns-eu/dba4e9a6839e56c80ba6db4484b5e1df to your computer and use it in GitHub Desktop.
Save erdesigns-eu/dba4e9a6839e56c80ba6db4484b5e1df to your computer and use it in GitHub Desktop.
Cross-Platform System Call Executor (x86, x64, ARM) in Delphi
unit SysCallExecutor;
interface
uses
Windows, SysUtils;
type
TSysCallExecutor = class
private
function GetSysCallNumber(const FunctionName: string): Cardinal;
function GetProcAddressEx(const ModuleName, FunctionName: string): Pointer;
public
function ExecuteSysCall(SysCallNumber: Cardinal; Parameters: array of Pointer): Integer;
function ExecuteFunctionByName(const FunctionName: string; Parameters: array of Pointer): Integer;
end;
implementation
function TSysCallExecutor.GetProcAddressEx(const ModuleName, FunctionName: string): Pointer;
var
ModuleHandle: HMODULE;
begin
ModuleHandle := GetModuleHandle(PChar(ModuleName));
if ModuleHandle = 0 then
raise Exception.CreateFmt('Module not found: %s', [ModuleName]);
Result := GetProcAddress(ModuleHandle, PChar(FunctionName));
if not Assigned(Result) then
raise Exception.CreateFmt('Function not found: %s in module %s', [FunctionName, ModuleName]);
end;
function TSysCallExecutor.GetSysCallNumber(const FunctionName: string): Cardinal;
var
NtFunction: Pointer;
begin
NtFunction := GetProcAddressEx('ntdll.dll', FunctionName);
if not Assigned(NtFunction) then
raise Exception.CreateFmt('Failed to locate function: %s', [FunctionName]);
Result := PCardinal(Cardinal(NtFunction) + 1)^; // Syscall number is located at +1 offset
end;
function TSysCallExecutor.ExecuteSysCall(SysCallNumber: Cardinal; Parameters: array of Pointer): Integer;
{$IFDEF WIN32}
asm
// Save state
pushad
pushfd
// Load syscall number
mov eax, SysCallNumber
// Load parameters into registers
mov ecx, Parameters
// Execute syscall
int $2E // System call interrupt for 32-bit systems
// Store result in EAX
mov @Result, eax
// Restore state
popfd
popad
end;
{$ELSEIF Defined(WIN64)}
asm
// x64 uses the syscall instruction
mov r10, rcx // First parameter in rcx
mov eax, SysCallNumber
syscall // Invoke syscall
mov @Result, eax // Return value in eax
end;
{$ELSEIF Defined(ARM)}
asm
// ARM architecture uses SVC (Supervisor Call) for syscalls
mov w8, SysCallNumber // Load syscall number into w8 (ARM convention)
svc #0 // Supervisor call
mov @Result, w0 // Store the result from x0 (ARM convention)
end;
{$ELSE}
{$ERROR Unsupported architecture}
{$ENDIF}
function TSysCallExecutor.ExecuteFunctionByName(const FunctionName: string; Parameters: array of Pointer): Integer;
var
SysCallNumber: Cardinal;
begin
SysCallNumber := GetSysCallNumber(FunctionName);
Result := ExecuteSysCall(SysCallNumber, Parameters);
end;
end.
@erdesigns-eu
Copy link
Author

// Example: Retrieve the list of running processes using NtQuerySystemInformation

uses
  SysCallExecutor, Windows, SysUtils;

procedure ListProcesses;
var
  Executor: TSysCallExecutor;
  Buffer: array[0..65535] of Byte;
  ReturnLength: Cardinal;
  ProcessInfo: Pointer;
  Offset: NativeUInt;
begin
  Executor := TSysCallExecutor.Create;
  try
    FillChar(Buffer, SizeOf(Buffer), 0);

    // Call NtQuerySystemInformation to get process information
    if Executor.ExecuteFunctionByName(
      'NtQuerySystemInformation',
      [Pointer(5), // SystemProcessInformation
       @Buffer,
       Pointer(SizeOf(Buffer)),
       @ReturnLength]) = 0 then
    begin
      Writeln('Processes:');
      ProcessInfo := @Buffer;
      Offset := 0;

      while Offset < ReturnLength do
      begin
        with PSystemProcessInformation(ProcessInfo)^ do
        begin
          Writeln('PID: ', ProcessId, ', Threads: ', HandleCount);
          if NextEntryOffset = 0 then Break;
          Inc(Offset, NextEntryOffset);
          ProcessInfo := Pointer(NativeUInt(@Buffer) + Offset);
        end;
      end;
    end
    else
      Writeln('Failed to query process information.');
  finally
    Executor.Free;
  end;
end;

begin
  ListProcesses;
end.

@erdesigns-eu
Copy link
Author

// Example: Allocate memory, write shellcode, and execute it

uses
  SysCallExecutor, Windows, SysUtils;

procedure RunShellcode;
var
  Executor: TSysCallExecutor;
  BaseAddress: Pointer := nil;
  RegionSize: NativeUInt := $1000; // 4 KB
  Shellcode: array[0..4] of Byte = ($48, $31, $C0, $C3); // x64 NOP + RET
begin
  Executor := TSysCallExecutor.Create;
  try
    // Allocate executable memory
    if Executor.ExecuteFunctionByName(
      'NtAllocateVirtualMemory',
      [GetCurrentProcess(),
       @BaseAddress,
       Pointer(0),
       @RegionSize,
       Pointer($3000), // MEM_COMMIT | MEM_RESERVE
       Pointer($40)    // PAGE_EXECUTE_READWRITE
      ]) = 0 then
    begin
      Writeln('Memory allocated at: ', IntToHex(NativeUInt(BaseAddress), 8));

      // Copy shellcode to allocated memory
      Move(Shellcode, BaseAddress^, Length(Shellcode));

      // Execute the shellcode
      TProcedure(BaseAddress)();
      Writeln('Shellcode executed.');
    end
    else
      Writeln('Failed to allocate memory.');
  finally
    Executor.Free;
  end;
end;

begin
  RunShellcode;
end.

@erdesigns-eu
Copy link
Author

program LowLevelHttp;

{$APPTYPE CONSOLE}

uses
  SysCallExecutor, Windows, SysUtils;

procedure PerformHttpPostAndGet;
var
  Executor: TSysCallExecutor;
  SocketHandle: THandle;
  ServerAddr: TSockAddrIn;
  IoStatusBlock: IO_STATUS_BLOCK;
  Request: string;
  Response: array[0..4095] of AnsiChar;
  Result: Integer;
  BytesReceived: Cardinal;
begin
  Executor := TSysCallExecutor.Create;
  try
    // Step 1: Create a socket using NtCreateFile
    Result := Executor.ExecuteFunctionByName(
      'NtCreateFile',
      [@SocketHandle,
       Pointer($80100000), // FILE_GENERIC_WRITE | FILE_GENERIC_READ
       nil,                // ObjectAttributes
       @IoStatusBlock,
       nil,                // Allocation size
       Pointer(0),         // File attributes
       Pointer(3),         // Share access: FILE_SHARE_READ
       Pointer(1),         // Create disposition: FILE_OPEN
       Pointer(0),         // Create options
       nil,                // EaBuffer
       Pointer(0)          // EaLength
      ]);
    if Result <> 0 then
      raise Exception.CreateFmt('Failed to create socket: %d', [Result]);

    Writeln('Socket created.');

    // Step 2: Connect to the server
    FillChar(ServerAddr, SizeOf(ServerAddr), 0);
    ServerAddr.sin_family := AF_INET;
    ServerAddr.sin_port := htons(80); // HTTP port
    ServerAddr.sin_addr.S_addr := inet_addr('93.184.216.34'); // example.com

    Result := Executor.ExecuteFunctionByName(
      'NtDeviceIoControlFile',
      [SocketHandle,
       nil,                // Event
       nil,                // ApcRoutine
       nil,                // ApcContext
       @IoStatusBlock,
       Pointer(IOCTL_CONNECT), // IOCTL_CONNECT placeholder
       @ServerAddr,
       Pointer(SizeOf(ServerAddr)),
       nil,
       Pointer(0)
      ]);
    if Result <> 0 then
      raise Exception.CreateFmt('Failed to connect: %d', [Result]);

    Writeln('Connected to server.');

    // Step 3: Perform HTTP POST to upload data
    Request :=
      'POST /upload HTTP/1.1' + #13#10 +
      'Host: example.com' + #13#10 +
      'Content-Type: text/plain' + #13#10 +
      'Content-Length: 11' + #13#10 +
      #13#10 +
      'Hello World';

    Result := Executor.ExecuteFunctionByName(
      'NtDeviceIoControlFile',
      [SocketHandle,
       nil,                // Event
       nil,                // ApcRoutine
       nil,                // ApcContext
       @IoStatusBlock,
       Pointer(IOCTL_SEND), // IOCTL_SEND placeholder
       @Request[1],
       Pointer(Length(Request)),
       nil,
       Pointer(0)
      ]);
    if Result <> 0 then
      raise Exception.CreateFmt('Failed to send POST request: %d', [Result]);

    Writeln('POST request sent.');

    // Step 4: Perform HTTP GET to retrieve uploaded data
    Request :=
      'GET /upload HTTP/1.1' + #13#10 +
      'Host: example.com' + #13#10 +
      #13#10;

    Result := Executor.ExecuteFunctionByName(
      'NtDeviceIoControlFile',
      [SocketHandle,
       nil,                // Event
       nil,                // ApcRoutine
       nil,                // ApcContext
       @IoStatusBlock,
       Pointer(IOCTL_SEND), // IOCTL_SEND placeholder
       @Request[1],
       Pointer(Length(Request)),
       nil,
       Pointer(0)
      ]);
    if Result <> 0 then
      raise Exception.CreateFmt('Failed to send GET request: %d', [Result]);

    Writeln('GET request sent.');

    // Step 5: Receive the response
    FillChar(Response, SizeOf(Response), 0);
    Result := Executor.ExecuteFunctionByName(
      'NtDeviceIoControlFile',
      [SocketHandle,
       nil,                // Event
       nil,                // ApcRoutine
       nil,                // ApcContext
       @IoStatusBlock,
       Pointer(IOCTL_RECEIVE), // IOCTL_RECEIVE placeholder
       @Response,
       Pointer(SizeOf(Response)),
       nil,
       Pointer(0)
      ]);
    if Result = 0 then
    begin
      BytesReceived := IoStatusBlock.Information;
      Writeln('Response received:');
      Writeln(Copy(Response, 1, BytesReceived));
    end
    else
      Writeln('Failed to receive response: ', Result);

  finally
    Executor.Free;
  end;
end;

begin
  PerformHttpPostAndGet;
end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment