There are times when you want to save the data after completion of the program, but did not want to have external dependencies, registry entries, etc. However you can store the data in your EXE. Unfortunately, Windows doesn't allow to write into the running EXE (i don't consider NTFS streams), and any attempt of the writing will be rejected with the ERROR_ACCESS_DENIED error. Although if the process is complete it can be performed by another process. Here is the way I decided to choose.
Firstly, you'd run cmd.exe with the suspended state. Further you'd create code that will be injected to it and will change the resources of our EXE. Then you'd run this code. This code waits for termination of our process and then rewrites the needed data (you've passed them to there). Eventually it is terminated.
In order to simplify the code (it only needs single form) i decide to make it in assembler. It is simpler and requires less code (source is included). Because the code is published especially for the review and test, it doesn't perform any synchronizations.
Code:
' Store data to EXE
' © Krivous Anatolii Anatolevich (The trick), 2014
' Writing is performed only after process termination
Option Explicit
Private Type STARTUPINFO
cb As Long
lpReserved As Long
lpDesktop As Long
lpTitle As Long
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Type ThreadData
hParent As Long
lpFileName As Long
lpRsrcName As Long
lpData As Long
dwDataCount As Long
lpWFSO As Long
lpCH As Long
lpBUR As Long
lpUR As Long
lpEUR As Long
lpEP As Long
End Type
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long) As Long
Private Declare Function CreateProcess Lib "kernel32" _
Alias "CreateProcessW" ( _
ByVal lpApplicationName As Long, _
ByVal lpCommandLine As Long, _
lpProcessAttributes As Any, _
lpThreadAttributes As Any, _
ByVal bInheritHandles As Long, _
ByVal dwCreationFlags As Long, _
lpEnvironment As Any, _
ByVal lpCurrentDirectory As Long, _
lpStartupInfo As STARTUPINFO, _
lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Function GetModuleHandle Lib "kernel32" _
Alias "GetModuleHandleA" ( _
ByVal lpModuleName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" ( _
ByVal hModule As Long, _
ByVal lpProcName As String) As Long
Private Declare Function DuplicateHandle Lib "kernel32" ( _
ByVal hSourceProcessHandle As Long, _
ByVal hSourceHandle As Long, _
ByVal hTargetProcessHandle As Long, _
lpTargetHandle As Long, _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwOptions As Long) As Long
Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
Private Declare Function VirtualAllocEx Lib "kernel32.dll" ( _
ByVal hProcess As Long, _
lpAddress As Any, _
ByVal dwSize As Long, _
ByVal flAllocationType As Long, _
ByVal flProtect As Long) As Long
Private Declare Function WriteProcessMemory Lib "kernel32" ( _
ByVal hProcess As Long, _
ByVal lpBaseAddress As Long, _
lpBuffer As Any, _
ByVal nSize As Long, _
lpNumberOfBytesWritten As Long) As Long
Private Declare Function GetMem4 Lib "msvbvm60" ( _
src As Any, _
dst As Any) As Long
Private Declare Function VirtualFreeEx Lib "kernel32.dll" ( _
ByVal hProcess As Long, _
lpAddress As Any, _
ByVal dwSize As Long, _
ByVal dwFreeType As Long) As Long
Private Declare Function CreateRemoteThread Lib "kernel32" ( _
ByVal hProcess As Long, _
lpThreadAttributes As Any, _
ByVal dwStackSize As Long, _
ByVal lpStartAddress As Long, _
lpParameter As Any, _
ByVal dwCreationFlags As Long, _
lpThreadId As Long) As Long
Private Declare Function FindResource Lib "kernel32" _
Alias "FindResourceW" ( _
ByVal hInstance As Long, _
ByVal lpName As Long, _
ByVal lpType As Long) As Long
Private Declare Function LoadResource Lib "kernel32" ( _
ByVal hInstance As Long, _
ByVal hResInfo As Long) As Long
Private Declare Function LockResource Lib "kernel32" ( _
ByVal hResData As Long) As Long
Private Declare Function SizeofResource Lib "kernel32" ( _
ByVal hInstance As Long, _
ByVal hResInfo As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" ( _
Destination As Any, _
Source As Any, _
ByVal Length As Long)
Private Const STARTF_USESHOWWINDOW As Long = &H1
Private Const SW_HIDE As Long = 0
Private Const MEM_COMMIT As Long = &H1000&
Private Const MEM_RESERVE As Long = &H2000&
Private Const MEM_RELEASE As Long = &H8000&
Private Const PAGE_EXECUTE_READWRITE As Long = &H40&
Private Const INFINITE As Long = -1&
Private Const MAX_PATH As Long = 260
Private Const RT_RCDATA As Long = 10&
Private Const CREATE_SUSPENDED As Long = &H4
Private Const DUPLICATE_SAME_ACCESS As Long = &H2
Private Const ResName As String = "TRICKRESOURCE" & vbNullChar ' Only capital letters
' // Procedure load data from EXE
Private Sub LoadFromEXE()
Dim hRes As Long, hMem As Long, ptr As Long, l As Long, Msg As String
hRes = FindResource(0, StrPtr(ResName), RT_RCDATA)
If hRes Then
hMem = LoadResource(0, hRes)
If hMem Then
l = SizeofResource(0, hRes)
If l Then
ptr = LockResource(hMem)
GetMem4 ByVal ptr, l
Msg = Space(l \ 2)
CopyMemory ByVal StrPtr(Msg), ByVal ptr + 4, l
txtData.Text = Msg
End If
End If
End If
End Sub
' // Procedure store data to EXE
Private Sub StoreToExe()
Dim hLib As Long
Dim td As ThreadData, ts As Long, path As String, pi As PROCESS_INFORMATION, si As STARTUPINFO, hProc As Long, lpDat As Long, pt As Long
Dim Code() As Byte, Data() As Byte, ret As Long, thr As Long, otd As Long
' // Get the Kernel32 handle
hLib = GetModuleHandle("kernel32")
If hLib = 0 Then MsgBox "Error": Exit Sub
' // Get the functions addresses
td.lpWFSO = GetProcAddress(hLib, "WaitForSingleObject")
td.lpCH = GetProcAddress(hLib, "CloseHandle")
td.lpBUR = GetProcAddress(hLib, "BeginUpdateResourceW")
td.lpUR = GetProcAddress(hLib, "UpdateResourceW")
td.lpEUR = GetProcAddress(hLib, "EndUpdateResourceW")
td.lpEP = GetProcAddress(hLib, "ExitProcess")
path = App.path & "\" & App.EXEName & ".exe" & vbNullChar
' // Create the machine code
CreateCode Code
' // Calculate size of the needed memory
ts = LenB(path) + LenB(ResName) + (UBound(Code) + 1) + LenB(txtData.Text) + Len(td) + 4
si.cb = Len(si)
si.dwFlags = STARTF_USESHOWWINDOW
si.wShowWindow = SW_HIDE
' // Launch "victim" (CMD.EXE)
If CreateProcess(StrPtr(Environ("ComSpec")), 0, ByVal 0&, ByVal 0&, False, CREATE_SUSPENDED, ByVal 0, 0, si, pi) = 0 Then
MsgBox "error": Exit Sub
End If
' // Get handle of the our process for CMD process
hProc = GetCurrentProcess()
DuplicateHandle hProc, hProc, pi.hProcess, td.hParent, 0, False, DUPLICATE_SAME_ACCESS
td.dwDataCount = LenB(txtData.Text) + 4 ' Размер данных
' // Allocate memory in the CMD
lpDat = VirtualAllocEx(pi.hProcess, ByVal 0, ts, MEM_COMMIT Or MEM_RESERVE, PAGE_EXECUTE_READWRITE)
If lpDat = 0 Then
MsgBox "Error": CloseHandle pi.hThread: CloseHandle pi.hProcess
VirtualFreeEx pi.hProcess, ByVal lpDat, 0, MEM_RELEASE
Exit Sub
End If
' // Ok, all is ready for the writing to cmd
' // Create buffer with data
ReDim Data(ts - 1)
' // Copy the file name of our process
CopyMemory Data(pt), ByVal StrPtr(path), LenB(path)
td.lpFileName = lpDat + pt: pt = pt + LenB(path)
' // Copy the name of the resource
CopyMemory Data(pt), ByVal StrPtr(ResName), LenB(ResName)
td.lpRsrcName = lpDat + pt: pt = pt + LenB(ResName)
' // Copy the data of the resource
GetMem4 LenB(txtData.Text), Data(pt) ' Размер
CopyMemory Data(pt + 4), ByVal StrPtr(txtData.Text), LenB(txtData.Text)
td.lpData = lpDat + pt: pt = pt + LenB(txtData.Text) + 4
' // Copy the structure to buffer
CopyMemory Data(pt), td, Len(td): otd = pt: pt = pt + Len(td)
' // Copy the code
CopyMemory Data(pt), Code(0), UBound(Code) + 1
' // Buffer is ready, inject it to cmd
If WriteProcessMemory(pi.hProcess, lpDat, Data(0), ts, ret) Then
If ret <> ts Then
MsgBox "Error": CloseHandle pi.hThread: CloseHandle pi.hProcess
VirtualFreeEx pi.hProcess, ByVal lpDat, 0, MEM_RELEASE
Exit Sub
End If
' // Launch the injected code
thr = CreateRemoteThread(pi.hProcess, ByVal 0, 0, lpDat + pt, ByVal lpDat + otd, 0, 0)
If thr = 0 Then
MsgBox "Error": CloseHandle pi.hThread: CloseHandle pi.hProcess
VirtualFreeEx pi.hProcess, ByVal lpDat, 0, MEM_RELEASE
Exit Sub
End If
End If
' // Close handles
CloseHandle thr
CloseHandle pi.hThread
CloseHandle pi.hProcess
End Sub
Private Sub CreateCode(Code() As Byte)
ReDim Code(63)
Code(0) = &H8B: Code(1) = &H74: Code(2) = &H24: Code(3) = &H4: Code(4) = &H31: Code(5) = &HDB: Code(6) = &H53: Code(7) = &H6A
Code(8) = &HFF: Code(9) = &HFF: Code(10) = &H36: Code(11) = &HFF: Code(12) = &H56: Code(13) = &H14: Code(14) = &HFF: Code(15) = &H36
Code(16) = &HFF: Code(17) = &H56: Code(18) = &H18: Code(19) = &H53: Code(20) = &HFF: Code(21) = &H76: Code(22) = &H4: Code(23) = &HFF
Code(24) = &H56: Code(25) = &H1C: Code(26) = &H89: Code(27) = &H4: Code(28) = &H24: Code(29) = &H85: Code(30) = &HC0: Code(31) = &H74
Code(32) = &H1B: Code(33) = &HFF: Code(34) = &H76: Code(35) = &H10: Code(36) = &HFF: Code(37) = &H76: Code(38) = &HC: Code(39) = &H53
Code(40) = &HFF: Code(41) = &H76: Code(42) = &H8: Code(43) = &H6A: Code(44) = &HA: Code(45) = &HFF: Code(46) = &H74: Code(47) = &H24
Code(48) = &H14: Code(49) = &HFF: Code(50) = &H56: Code(51) = &H20: Code(52) = &H53: Code(53) = &HFF: Code(54) = &H74: Code(55) = &H24
Code(56) = &H4: Code(57) = &HFF: Code(58) = &H56: Code(59) = &H24: Code(60) = &H53: Code(61) = &HFF: Code(62) = &H56: Code(63) = &H28
End Sub
Private Sub Form_Load()
LoadFromEXE
End Sub
Private Sub Form_Unload(Cancel As Integer)
StoreToExe
End Sub
'\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ This procedure is running in other process \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
' Similar code in VB6
'Private Sub ThreadProc(dat As ThreadData)
' Dim hRes As Long
' ' Wait for the termination of the main process
' WaitForSingleObject dat.hParent, INFINITE
' ' Process has ended, close handle
' CloseHandle dat.hParent
' ' Get handle of the editing of the resource
' hRes = BeginUpdateResource(dat.lpFileName, False)
' If hRes Then
' ' Wirte the needed data to EXE
' UpdateResource hRes, RT_RCDATA, dat.lpRsrcName, 0, ByVal dat.lpData, dat.dwDataCount
' ' Ending of the updating
' EndUpdateResource hRes, False
' End if
' ' Done !!!
' ExitProcess 0
'End Sub
' Assembly code (NASM)
'[BITS 32]
'; ThreadProc
'mov esi,dword [esp+0x04]; ESI = &dat
'xor ebx,ebx ; Const 0&
'push ebx ; Dim hRes As Long
'push 0xFFFFFFFF ; INFINITE
'push dword [esi+0x00] ; dat.hParent
'call [esi+0x14] ; WaitForSingleObject dat.hParent, INFINITE
'push dword [esi+0x00] ; dat.hParent
'call [esi+0x18] ; CloseHandle dat.hParent
'push ebx ; False
'push dword [esi+0x04] ; dat.lpFileName
'call [esi+0x1c] ; BeginUpdateResource(dat.lpFileName, False)
'mov [esp],eax ; hRes = eax
'test eax,eax ; IF hRes=0
'je ExtProc ; GoTo ExtProc
'push dword [esi+0x10] ; dat.dwDataCount
'push dword [esi+0x0c] ; dat.lpData
'push ebx ; 0
'push dword [esi+0x08] ; dat.lpRsrcName
'push 0x0000000a ; RT_RCDATA
'push dword [esp+0x14] ; hRes
'call [esi+0x20] ; UpdateResource hRes, RT_RCDATA, dat.lpRsrcName, 0, ByVal dat.lpData, dat.dwDataCount
'push ebx ; False
'push dword [esp+0x04] ; hRes
'call [esi+0x24] ; EndUpdateResource hRes, False
'ExtProc:
'push ebx ; 0
'call [esi+0x28] ; ExitProcess 0