A Red Team exercise is lotsa fun not only because you have a more realistic engagement due to the broader scope, but also because you can encounter situations which you normally wouldn’t on a regular narrow scoped penetration test. I’m going to focus on pageant which Slurpgeit recently encountered during one of these red team exercises which peeked my interest.
Apparantly he got access to a machine on which the user used pageant to manage ssh keys and authenticate to servers without having to type his key password every single time he connects. This of course raises the following interesting (or silly) question:
Why does the user only have to type his ssh key in once?
Which has a rather logical (or doh) answer as well:
The pageant process keeps the decrypted key in memory so that you can use them without having to type the decryption password every time you want to use the key.
From an attackers perspective it of course begs the question if you can steal these unencrypted keys? Assuming you are able to make a memory dump of the running process you should be able to get these decrypted ssh keys. During this blog post I’ll be focusing on how you could achieve this and the pitfalls I encountered when approaching this.
Creating the process memory dump
First of all we need a memory dump, to create one we will use the excellent ‘ProcDump.exe‘ utility from the sysinternals tools. This of course has the added benefit that normally it doesn’t trigger any anti virus alarm bells, if you’ve configured your hips correctly it might trigger some alarm bells. The following command should do the trick to create a full process memory dump:
C:\>procdump.exe -ma pageant.exe
ProcDump v7.1 - Writes process dump files Copyright (C) 2009-2014 Mark Russinovich Sysinternals - www.sysinternals.com With contributions from Andrew Richards
[03:56:37] Dump 1 initiated: C:\pageant.exe_150902_035637.dmp [03:56:37] Dump 1 writing: Estimated dump file size is 84 MB. [03:56:39] Dump 1 complete: 84 MB written in 1.7 seconds [03:56:39] Dump count reached.
This should, as can be seen, create a full dump (‘.dmp’) file which you can then use for analysis. The full part (-ma) is important or you won’t be able to extract the juicy bits later on. If you are doing this during a red team exercise like Slurpgeit was doing, don’t forget to add the ‘-accepteula’ argument or your victim will have all kinds of popups. Another nice habit is to always(=as often as possible) make process memory dumps from processes and grabbing the binaries. You should of course be aware that this might cause the process to crash, but sometimes the risk is worth it.
The unlucky shots
I first tried the ‘non parsing’ approach which just involved searching around for RSA key finding tools and letting them loose on the dump file. This however did not result in any usable keys, this could partially be due to my impatience of wanting to extract the unencrypted private keys which of course resulted in also using tools which are not really meant to be used on this kind of memory dump formats. The following is a list of tools / scripts that I (attempted to) tried:
- https://github.com/NetSPI/sshkey-grab/blob/master/parse_mem.py
- https://www.thc.org/releases/keyfinder.c
- https://hacking.ventures/rsa-keys-in-heartbleed-memory/
- http://www.woodmann.com/collaborative/tools/index.php/SSL_Key/Cert_Finder
At first I thought this might have to do with possible fragmentation of the memory regions in the dump format, but after learning more about the format this wasn’t really the issue in my dump.
The good thing about me not succeeding with the above mentioned tools is the fact that it pushed me to wanting to learn and understand the format of the process memory dump file produced by ‘procdump.exe’. The bad thing is that it’s become a more lengthy post then at first expected :)
Getting to know the format
I then decided that I needed to understand the minidump format, which to my surprise seems to be mostly documented by Microsoft. A good starting point to understand this format would be the blog post by @moyix, which provides a great library to parse the mindump format as well.
In it’s most basic form the minidump format contains a header from which you can start to parse the dump file:
typedef struct _MINIDUMP_HEADER { ULONG32 Signature; ULONG32 Version; ULONG32 NumberOfStreams; RVA StreamDirectoryRva; ULONG32 CheckSum; union { ULONG32 Reserved; ULONG32 TimeDateStamp; }; ULONG64 Flags; } MINIDUMP_HEADER, *PMINIDUMP_HEADER;
This header points to an array of stream directory entries which further describe the type and location of each stream:
typedef struct _MINIDUMP_DIRECTORY { ULONG32 StreamType; MINIDUMP_LOCATION_DESCRIPTOR Location; } MINIDUMP_DIRECTORY, *PMINIDUMP_DIRECTORY;
typedef struct _MINIDUMP_LOCATION_DESCRIPTOR { ULONG32 DataSize; RVA Rva; } MINIDUMP_LOCATION_DESCRIPTOR;
This is where at first I got a little bit confused since I started to parse the wrong stream type. I choose to parse and investigate the ‘MemoryInfoListStream’ stream type since I assumed that would contain the raw memory regions that hold the juicy info. Turned out it didn’t contain the raw memory regions, it just contained a description of memory regions and their access rights etc.
So after some fiddling around I finally found the ‘Memory64ListStream’ stream type which does contain the correct structures to access the raw memory regions I was after. The fun part is of course that after understanding all this it turned out that the dump file just contains all the raw memory regions appended to all the defined structures until the end of the file. All you need is the pointer to the start of these raw memory regions if all you wanted is access to the raw memory. Like we’ll see further down this blog post this is not wat we want, since we need to also find some specific offsets in these raw memory regions.
typedef struct _MINIDUMP_MEMORY64_LIST { ULONG64 NumberOfMemoryRanges; RVA64 BaseRva; MINIDUMP_MEMORY_DESCRIPTOR64 MemoryRanges [0]; } MINIDUMP_MEMORY64_LIST, *PMINIDUMP_MEMORY64_LIST;
typedef struct _MINIDUMP_MEMORY_DESCRIPTOR64 { ULONG64 StartOfMemoryRange; ULONG64 DataSize; } MINIDUMP_MEMORY_DESCRIPTOR64, *PMINIDUMP_MEMORY_DESCRIPTOR64;
Like you can see the first structure contains the ‘BaseRva’ pointer to the raw memory regions, which you need to increment with the size of each raw memory regions if you want to read a specific region. This however I didn’t realise until I read this article even though the MSDN pages did state it:
Note that BaseRva is the overall base RVA for the memory list. To locate the data for a particular descriptor, start at BaseRva and increment by the size of a descriptor until you reach the descriptor.
If you need a quick overview of the format then the Minidump Explorer tool as well a moyix’s library do a great job of showing you the parsed file:
One of the things you can for example do with the library is to extract the raw memory and write it to a file:
#!/usr/bin/env python """ DiabloHorn https://diablohorn.wordpress.com Writes the raw memory from a minidump to a file """ import sys import os try: import minidump except: print "You need the minidump library" print "Download http://moyix.blogspot.com.au/2008/05/parsing-windows-minidumps.html" sys.exit() if __name__ == "__main__": if len(sys.argv) != 2: print "Usage %s %s" % (sys.argv[0], "<minidump_file>") sys.exit() minidump_filesize = os.path.getsize(sys.argv[1]) rawmemory_filename = "%s.rawmem" % sys.argv[1] print ":::minidump filesize %s" % minidump_filesize f = open(sys.argv[1],'rb') parsed_minidump = minidump.MINIDUMP_HEADER.parse_stream(f) f.close() for i in parsed_minidump.MINIDUMP_DIRECTORY: if i.StreamType == 'Memory64ListStream': rawmemory_size = (minidump_filesize - i.DirectoryData.BaseRva) print ":::Found raw memory data stream" print ":::Start of raw memory %s" % i.DirectoryData.BaseRva print ":::Size of raw memory %s" % rawmemory_size print ":::Writing raw memory to %s" % rawmemory_filename f = open(sys.argv[1],'rb') f.seek(i.DirectoryData.BaseRva) data = f.read(rawmemory_size) f.close() f = open(rawmemory_filename,'wb') f.write(data) f.close()
Manually finding the private keys
Now that we know the minidump format, let’s see if we can find those decrypted private keys in the process’s memory. We need to know how they are stored in the first place, how else are we otherwise going to search for them in the vast amount of raw memory bytes? Let’s have a look at the source code first, since after all pageant is open source (putty git).
The decrypted key structure
My first thought was as follow:
Find the structure that holds the decrypted key values, convert it to a search pattern, search the dump file
The first file we will be looking at is ‘winpgnt.c’ which seems to contain the bulk of the code for the pageant binary. One of the things I always like to do when I have to look at source code is to just browse through the file to get a feeling for the structure of the code and how functions etc are used by the author, might sound weird for me it works. After this I started pageant locally and added a freshly generated (puttygen.exe) key. This provided me with names of the menus which you can then search for in the code. I used the ‘add key’ text, since this menu item prompted me for the private key file and the corresponding password. This search should land you somewhere here:
case 101: /* add key */ if (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) { if (passphrase_box) { MessageBeep(MB_ICONERROR); SetForegroundWindow(passphrase_box); break; } prompt_add_keyfile();
Now if you follow that ‘prompt_add_keyfile()’ function and then the ‘add_keyfile()’ function in it, you should eventually land on line 560 ‘skey = ssh2_load_userkey(filename, passphrase, &error);’ in the winpgnt.c file. Now that looks like what we are after, a function which takes our file and our passphrase and hopefully returns the decrypted key.
To look at the function in more detail we have to open the file ‘sshpubk.c‘ and go to line 570 on which we’ll find:
struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, char *passphrase, const char **errorstr)
So it seems that this function returns a ‘ssh2_userkey’ structure, which after reading the function’s code it indeed does contain the decrypted private key in a corresponding format:
This is how the ‘ssh2_userkey’ looks like:
struct ssh2_userkey { const struct ssh_signkey *alg; /* the key algorithm */ void *data; /* the key data */ char *comment; /* the key comment */ };
Which of course isn’t much to create a signature from, let’s have a look at that ‘ssh_signkey’ structure as well:
struct ssh_signkey { void *(*newkey) (char *data, int len); void (*freekey) (void *key); char *(*fmtkey) (void *key); unsigned char *(*public_blob) (void *key, int *len); unsigned char *(*private_blob) (void *key, int *len); void *(*createkey) (unsigned char *pub_blob, int pub_len, unsigned char *priv_blob, int priv_len); void *(*openssh_createkey) (unsigned char **blob, int *len); int (*openssh_fmtkey) (void *key, unsigned char *blob, int len); int (*pubkey_bits) (void *blob, int len); char *(*fingerprint) (void *key); int (*verifysig) (void *key, char *sig, int siglen, char *data, int datalen); unsigned char *(*sign) (void *key, char *data, int datalen, int *siglen); char *name; char *keytype; /* for host key cache */ };
That’s a big list of pointers to functions…again not really suitable to create a signature in my opinion. This is partially due to the fact that you’d have to check all pointers and see if they end up in executable code. Seems we have to dig into the structure of how the actual private key data is really stored instead of the meta-info structures.
PE info & structures
You might now be going like ‘whoaaa but you said we would look into the actual key data structure’, yep I said that. This is the exact same process I went through when I talked to Mitchel Sahertian, since he was thinking more along the lines of:
Searching for signatures isn’t really beautiful or error proof, why don’t you just access the structures like pageant itself does? It has to have a beginning somewhere to find all the keys.
Hmm that didn’t sound bad at all, let’s see how pageant itself stores all the keys you load. One of the things that quickly becomes clear is that pageant has some functions with end with ‘234’ and if you look those up you’ll be reading through the ‘tree234.c‘ file. Can’t be any clearer, pageant is using a 2-3-4 tree for storage and retrieval of data. Yes, indeed, this is also the data structure used by pageant to store the key data. We know this because if we continue from the last line winpgnt.c:560 where ‘ssh2_load_userkey‘ was called and keep on reading to see what happens to the ‘skey’ variable we’ll end up at line winpgnt.c:687 which shows us ‘if (add234(ssh2keys, skey) != skey)‘. Now to verify that the ‘ssh2keys’ variable is indeed a 2-3-4 tree, we just search through the code and we’ll end up in the winmain function where we find ‘ssh2keys = newtree234(cmpkeys_ssh2);‘ confirming that indeed is a tree.
The ‘ssh2keys‘ variable is defined as ‘static tree234 *rsakeys, *ssh2keys;‘ of which Mitchel reminded me that it’s stored in the ‘.data’ section of a PE file and thus you can KNOW the offset of where the variable will be stored in memory as also stated by the ‘Peering inside the PE‘ article:
Just as .text is the default section for code, the .data section is where your initialized data goes. This data consists of global and static variables that are initialized at compile time. It also includes string literals. The linker combines all the .data sections from the OBJ and LIB files into one .data section in the EXE. Local variables are located on a thread’s stack, and take no room in the .data or .bss sections.
Now isn’t that nice? We actually have the starting point to the tree in which all the private key data is stored. From here we can parse the tree and extract the private key data, at least that’s the working theory. Let’s put it to the test.
From src to memory
Let’s see if we can find the data we want in the memory dump, to do that we need two things:
- Offset to the ssh2keys variable in memory
- Something to interpret the memory dump
For the first requirement we’ll use IDA and just load the binary into it, after some searching around, renaming functions and matching the disassembly to the src we have, we find the statically defined tree variable:
Luckily for us it seems that pageant doesn’t support ASLR so the 00420D2C offset is perfectly usable without any fiddling to start the journey towards finding the decrypted private keys in memory.
Now that we have a starting offset we need a tool to interpret and navigate through the dump file that we have. For this we will be using WinDBG which is part of the Debugging Tools for Windows collection. Since I’m no windbg expert it’ll probably look clumsy, but for me it got the job done. Just load up the dump file into windbag using the File->Open Crash Dump option. Now that the dump file is loaded you can enable the memory view by pressing alt+5 just make it nice and big to work with, since this will be the only view we’ll be working with.
We’ll enter the offset we have into the virtual text box at the top which will present us with the the following view:
Since memory is displayed in little endian it’s kind of hard to read, so from now on we’ll change the view into ‘long hex’ which makes it easier to work with offset and view the data that we need. We will now just follow the next offset 002D1FA8 by entering it into the virtual text box, which will land us here:
At first it might not look like much when you land here, but if you have the tree234 implementation structures at hand it will start to make sense. The tree consists of a pointer to the first node and a comparison function. We can validate this because the second offset points into executable code. Let’s have a look at that first node:
Like you can see, this again will match the structure for a tree node. Since I had only loaded two keys into pageant there is no need for children nodes and only two elements are in use, which should match the data for our keys. Like you imagined since this is the first node in the tree, it doesn’t have any parent node. Let’s have a look at the first element:
Nice seems we are getting back to the first structures we found in the src, since this is the ssh2_userkey structure which holds the decrypted data, thus confirming this indeed is the tree that holds all the decrypted private keys. To fully confirm it we could look up the comment pointer which should contain the comment for the key that I loaded into pageant:
I changed the view from ‘long hex’ to ‘ASCII’ and yes this is indeed the default comment as generated by puttygen.
OK, now what? Even though with this cleaner method we have a good way to enumerate all the loaded private keys we still need to extract the actual data. As for that unfinished sentence about key data structures, seems we’ll have to dig into it after all.
They key format
So let’s pick up where we left off, this is how the key data is stored in memory if you follow the “void *data” pointer:
Like we can see the struct has a variable part which doesn’t seem to be used if you use the binary downloaded from the official website. One of the things that kept me busy for some time where the first two field “int bits & int bytes” their values in the memory dump didn’t seem to match any logical size for the key or the struct. It was not until I debugged a running pageant instance with ollydbg (I usually trust binaries more than the src, you never know what a compiler might have done) that I realised that they really didn’t seem to be used, although they are present in the struct. This seemed to be confirmed by the src which also doesn’t seem to have a reference to those fields being set. I might have still missed something, so please let me know if I did.
So the only thing left to figure out is the Bignum type which seems pretty well commented in the ‘sshbn.c‘ src:
/* * The Bignum format is an array of `BignumInt'. The first * element of the array counts the remaining elements. The * remaining elements express the actual number, base 2^BIGNUM_INT_BITS, _least_ * significant digit first. (So it's trivial to extract the bit * with value 2^n for any n.) * * All Bignums in this module are positive. Negative numbers must * be dealt with outside it. * * INVARIANT: the most significant word of any Bignum must be * nonzero. */
The ‘BignumInt’ itself, if I’m not mistaken is defined in ‘sshbn.h‘ as:
#elif defined __GNUC__ && defined __i386__ typedef unsigned long BignumInt;
So it seems that the our numbers stored as an array of four byte elements and the first element of the array tells us how many other elements are left to read, which seems to be about right if we have a look at for example the ‘private_exponent‘ struct member in windbg:
Now that, that’s clear let’s have a look at turning this information into usable private keys which we can actually use to authenticate against servers.
Creating usable private keys
So before we just dive blindly into this, let’s think about it:
- We have the primitives used for RSA operations
- They where obtained from a key file format
- We need a key file format again
Heh, that sounds funny put into perspective like that. My first thought was to just produce a putty file again, we began our journey on Windows with pageant after all right? The format if you are wondering is described in the file ‘sshpubk.c‘ starting on line 378.
Then again why not put it into a more universal format like the OpenSSH one:
-----BEGIN RSA PRIVATE KEY-----
all kind of base64 encoded data
-----END RSA PRIVATE KEY-----
At least with this format a lot of tools are able to work with it, which in the end run is just what we need if we want to use the keys to start plundering servers, sort of speak. This however is not something that you want to do manually, since it turns out that the base64 encoded data contains a DER (or BER) encoded ASN.1 structure. If you want to visualise this you can use this online ASN.1 decoder which accepts the base64 encoded form of the key and then generates a hierarchical structures side by side with the hex dump.
Luckily for us pycrypto exists which makes it a lot easier to create workable private keys from the primitives that we have, since it has a function which accepts those primitives and does all the hard work for us:
Construct an RSA key object from a tuple of valid RSA components. See RSAImplementation.construct. Parameters: tup (tuple) - A tuple of long integers, with at least 2 and no more than 6 items. The items come in the following order: RSA modulus (n). Public exponent (e). Private exponent (d). Only required if the key is private. First factor of n (p). Optional. Second factor of n (q). Optional. CRT coefficient, (1/p) mod q (u). Optional. Returns: An RSA key object (_RSAobj).
Using this is relatively simple, since all we have to do is extract the hex values from the memory locations we talked about before, be careful of endianness and feed those values to the function like this:
#!/usr/bin/env python """ DiabloHorn https://diablohorn.wordpress.com Converts rsa primitives extracted with windbg from memory to a workable rsa private key format """ import sys import base64 from Crypto.PublicKey import RSA def string_to_long(data): data = data.split(' ') data.reverse() return long(("".join(data)),16) if __name__ == "__main__": #setup the primitives rsamod = string_to_long('7955549b 79eb3c32 ee6e6b2c 405d4cfb c22ae82b a467ac7b 0f5875bb 5fec483b 72b26f8a 8c27373f a1abcfff d142c88a 88564e3b 1c45d0c4 53535ca6 72695f43 6fdde462 32741a1f ff1e0440 219fffea 04beaa49 73308e60 2a3e7ba6 644f51ba 8a4ddf2d 1fe2ba37 e7bcf094 adf5a610 3845feb6 2349edf5 2eb40451 e0ed9d03 923a0a70 e835a702 b0d4887b a20493ed 17c55930 29b672c9 167dc521 80327c02 daf9b3fe f3c39157 cffb8360 96c5d8db 670e1092 6d4e9f0d 2f517912 d42b8ce1 6fea58d5 7038f788 115a1eaa e5963585 7cdcd082 64d0a88c 66a4a66f fa3648ae c2fb89bc 099a73f7 f3292ffa ce2c2428 55da8859 ce045224 6190274f b1652f29') rsapubexp = long(0x25) rsaprivexp = string_to_long('a396f24d 8fd800ec 6dc00c2e abcd8943 a98f0d92 217299a8 a1ba8dcf c5b87820 96373ebe 76c0a795 92340c3b 05651d18 9ccf90bc 108c2ab3 329fc033 d36e9837 c3f7e413 22c62633 7b854536 acbd5c31 cbe7c3a3 a292eb62 b5c4146d 9f55ffa6 5d241da1 608fcce7 d2de7859 b76b703a c9960358 734329ad 13781aec 3af1eb80 fdf94703 5ac52b0f 9b12eee4 5064b34a 600635f8 c900a55c 65deff1b 41e51bca 8df3ce28 a9a3daa3 ec869e81 699101cc a95ecf9d 2b26323b e95fefd1 8154eba7 3b2c20ea 18d5c879 00b34a20 c05b4199 46051d66 69393345 a21b3f56 0fe84abb a35d2060 61fdf275 7f9f0c85 cf556a67 c478d31a dd0a8a02 1a640542 94a0e253') rawkey = (rsamod,rsapubexp,rsaprivexp) #construct the desired RSA key rsakey = RSA.construct(rawkey) #print the object, publickey, privatekey print rsakey print rsakey.publickey().exportKey('PEM') print rsakey.exportKey('PEM')
Like you can see I’ve been lazy and kept the windbg display format on a single line which for now works fine. With the exported private key you could now attempt to connect to any server on which this key is valid.
Finally let’s compare the keys to make sure they really are the same, I used puttygen for this since it’s nice and visual:
On the left the key extracted from memory and imported into puttygen, like you can see it has no passphrase. On the right the original key that I imported into pageant, you might recognise the comment from earlier screenshots, when I started to write this blog post.
Conclusion
So even though at first the memory dump of a process seems like a collection of gibberish there is a lot of information you can still find in it and access in a structured way, just like how volatility makes the big pile of computer memory dumps accessible in a structured way.
Of course this exercise has been a lot easier due to the availability of pageant’s source code it’s still something that applies to a lot of processes / applications out there. For example the way that mimikatz is able to extract passwords from a LSASS process memory dump is such an example.
If however for some reasons you are not able to access the information in a structured way you could always just search for the structure in which the information is kept. In this case after obtaining a clear picture of all the structures this would certainly have been possible.
Like you can imagine there is a lot more to be extracted from the process memory dump of pageant since it contains two more interesting trees, one for other type of keys and one for short-term passwords. So if you want a nice exercise you should definitely play with it, since having the source code really is a big advantage.
Some things still left to do:
Automate this process with python and moyix’s library- Create a volatility plugin
Hope you had as much fun reading this as I had writing it and learning a lot while doing so.
References
- http://blog.oddbit.com/2011/05/08/converting-openssh-public-keys/
- https://martin.kleppmann.com/2013/05/24/improving-security-of-ssh-private-keys.html
- http://lapo.it/asn1js/
- http://crypto.stackexchange.com/questions/1729/why-does-the-pkcs1-rsa-private-key-structure-contain-more-than-just-exponent-and
- http://mcarpenter.org/blog/2013/10/22/extracting-openssl-private-keys-from-core-dumps
- https://gregsplaceontheweb.wordpress.com/2014/05/30/minidump-explorer-v0-2-reading-minidump-memoryliststream-and-memory64liststream/
- http://moyix.blogspot.com.au/2008/05/parsing-windows-minidumps.html
- http://luca.ntop.org/Teaching/Appunti/asn1.html
- https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
- https://www.dlitz.net/software/pycrypto/api/current/
- http://trapkit.de/research/sslkeyfinder/keyfinder_v1.0_20060205.pdf
Hi,
Could you open an issue here: https://github.com/DiabloHorn/pageant_xkeys
Could you also describe it in more detail? It has been a while since I used this code.
It might be that you are trying it on a new version of the memory dump format….
Hi, When I am trying to run the python code I get a ArrayError in constuct/Core.py.
you now how to fix it?