sslsniff howto dump the temporary key

sslsniff written by Moxie Marlinspike is a pretty nice tool to do SSL analysis. It has two modes of operation:

  • Authority mode
    • Dynamically generates certificates and signs them with the specified CA
  • Targeted mode
    • Uses pre-generated certificates to attack specific sites

Like most (if not all) tools there is always a situation where you want to look at the decrypted data in wireshark.  So yes, for that you would use the ‘targeted mode’, but then again wouldn’t it be nice if you could also do that using the Authority mode? Since I’ve never really messed with reversing and hooking on linux I chose to make a solution that wouldn’t require source code changes to sslsniff. Since the source code is available it helped me to cheat and be able to understand things better. Most of the information I had to read, to actually understand what I was doing can be found at the end of this post under the heading ‘references’. It’s fun to see what you can manage in a short amount of time if you stick to it.

The TL;DR version can be grabbed from my gihub it contains the source code and scripts you need to dump the temporary SSL key. For the ones wondering how I approached his, please keep reading.

General approach

First of all let’s download the source code and see if we can find the function responsible for the generation of the temporary key. I used grep to search for keywords like generate, temporary and rsa. One of those keywords will probably produce the following result:

certificate/AuthorityCertificateManager.cpp:  RSA *rsaKeyPair  = RSA_generate_key(1024, RSA_F4, NULL, NULL);

Which on inspection resides in the function:

EVP_PKEY* AuthorityCertificateManager::buildKeysForClient()

After you have followed the source code path, you can be pretty sure that it is the function responsible for generating the key that is used for the dynamically generated certificates. Let’s see if we can also find that information when we look at the binary. Let’s see if it has debug symbols, since that would make it easier to debug:

nm -a /usr/bin/sslsniff
nm: /usr/bin/sslsniff: no symbols

Well that probably means that the function that are defined in the source code of sslsniff are not available, but the ones called within external libraries probably are, we’ll use readelf to verify:

readelf -a /usr/bin/sslsniff | grep buildKeysForClient
readelf -a /usr/bin/sslsniff | grep RSA_generate_key
0808a35c 0000d207 R_386_JUMP_SLOT 00000000 RSA_generate_key
210: 00000000 0 FUNC GLOBAL DEFAULT UND RSA_generate_key@OPENSSL_1.0.0 (3)

Like you can see the first grep didn’t have any results while the second one had two results. The first line is relocation information and the second line is symbol information. At this point I was thinking how to proceed, basically I had two ideas:

  1. Use GDB to dump the memory section containing the private key while sslsniff is running
  2. Hook the RSA_generate_key function and save the generated private key while sslsniff is running

I ended up discarding the first option after some hours of getting used to gdb and seeing that this was kinda complex. The first option is still fun to look at later, since I’m wondering if it could be done using python to automate the manual gdb steps. You can skip the next section if you don’t want to be bothered with all kind of GDB amateurism and RSA key structures and head on to the hooking section.

GDB & RSA key structures

Knowing which function produced the private key material I was looking for, I started gdb and set a breakpoint on it (on a 32bit machine):

gdb /usr/bin/sslsniff 
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /usr/bin/sslsniff...(no debugging symbols found)...done.
(gdb) b RSA_generate_key
Breakpoint 1 at 0x8050140
(gdb) r -a -c /tmp/cacert.pem -s 8443 -w /tmp/slog.txt
Starting program: /usr/bin/sslsniff -a -c /tmp/cacert.pem -s 8443 -w /tmp/slog.txt
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Breakpoint 1, RSA_generate_key (bits=1024, e_value=65537, callback=0, cb_arg=0x0) at rsa_depr.c:73
73 rsa_depr.c: No such file or directory.

I’ve highlighted the most interesting lines. gdb confirms that there are no debug symbols. The line where the breakpoints hits, tells us that it has the exact same arguments as specified in the source. This is a good sign, we now know we are looking in the right direction. So let’s see what the code looks like:

(gdb) bt
#0 RSA_generate_key (bits=1024, e_value=65537, callback=0, cb_arg=0x0) at rsa_depr.c:73
#1 0x080678af in ?? ()
#2 0x08067a23 in ?? ()
#3 0x08051c02 in ?? ()
#4 0xb7aa14d3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6
#5 0x08054355 in ?? ()
(gdb) disassemble 0x080678aa,0x080678bf
Dump of assembler code from 0x80678aa to 0x80678bf:
 0x080678aa: call 0x8050140 <RSA_generate_key@plt>
 0x080678af: mov %eax,%esi
 0x080678b1: call 0x804f880 <EVP_PKEY_new@plt>
 0x080678b6: mov %esi,0x8(%esp)
 0x080678ba: movl $0x6,0x4(%esp)
End of assembler dump.

This is when I thought…uh WUT? I’m only used to Intel syntax and this was AT&T syntax, so let’s fix that:

(gdb) set disassembly-flavor intel
(gdb) disassemble 0x080678aa,0x080678bf
Dump of assembler code from 0x80678aa to 0x80678bf:
 0x080678aa: call 0x8050140 <RSA_generate_key@plt>
 0x080678af: mov esi,eax
 0x080678b1: call 0x804f880 <EVP_PKEY_new@plt>
 0x080678b6: mov DWORD PTR [esp+0x8],esi
 0x080678ba: mov DWORD PTR [esp+0x4],0x6
End of assembler dump.

Much better, the highlighted line is the one we are after, since EAX contains the result of the call to RSA_generate_key which is pointer to a RSA structure. The next call (EVP_PKEY_new) is nice to see, since it confirms we are in the correct part of the binary. Let’s put a breakpoint on the mov esi,eax so that we can inspect EAX:

(gdb) b *0x080678af
Breakpoint 2 at 0x80678af
(gdb) c
Continuing.
Breakpoint 2, 0x080678af in ?? ()

Now before we have a peek at EAX, let’s first see how the RSA structure even looks like. You can either look in /usr/include/openssl/rsa.h or search for it online. Both should yield the following result:

struct rsa_st
        {
        /* The first parameter is used to pickup errors where
         * this is passed instead of aEVP_PKEY, it is set to 0 */
        int pad;
        long version;
        const RSA_METHOD *meth;
        /* functional reference if 'meth' is ENGINE-provided */
        ENGINE *engine;
        BIGNUM *n;
        BIGNUM *e;
        BIGNUM *d;
        BIGNUM *p;
        BIGNUM *q;
        BIGNUM *dmp1;
        BIGNUM *dmq1;
        BIGNUM *iqmp;
        [...]

So memory wise we should have 4 dwords(4 bytes each) before hitting the first actual RSA related information. To verify this the 5th dword should point to a BIGNUM structure right? Thus making me search around to know how the BIGNUM structure actually looks like:

struct bignum_st
        {
        BN_ULONG *d;    /* Pointer to an array of 'BN_BITS2' bit chunks. */
        int top;        /* Index of last used d +1. */
        /* The next are internal book keeping for bn_expand. */
        int dmax;       /* Size of the d array. */
        int neg;        /* one if the number is negative */
        int flags;
        };

If we follow all the way through we should see an in memory representation of the above. To be sure that we are able to interpret it, we are going to look at the 6th dword since it contains the value of E (e_value=65537), which we already know because of the first breakpoint on the function.

(gdb) x/6wx $eax
0x809d958:	0x00000000	0x00000000	0xb7edab00	0x00000000
0x809d968:	0x0809e1b8	0x0809e1e8
(gdb) x/6wx 0x0809e1e8
0x809e1e8:	0x0809b410	0x00000001	0x00000001	0x00000000
0x809e1f8:	0x00000001	0x00000019

We can see EAX points to the RSA structure and if we inspect the 6th item we have the BIGNUM structure. The first value points to the actual array, which is only one big that also makes sense due to the value that it holds. So if we inspect the first value we should see the hexadecimal representation of the E value:

(gdb) x/1wx 0x0809b410
0x809b410:	0x00010001

Now that’s pretty nice right? We can actually view the generated temporary RSA key in memory. As said before I decided to discard this option due to the fact that it’s more complicated then hooking the function. I stumbled upon some nice information though, gdb has a python interface. So maybe I’ll try and see if I can automate the dumping of the key using python and gdb.

Hooking the RSA_generate_key function

So before even thinking about looking at the hooking stuff I started with writing a program that used the same RSA key generating function sslsniff does and then save that information to a file. Since this was the first time I had to deal with openssl and such I had to read up on all kind of functions and search for some examples. In the end it resulted in a working program, which you can find here. This looked promising since it only took me like 1/5 of the time I had spend on gdb. After reading about the ELF file format I ended up reading this article, which explained exactly what I was looking for. Hooking under Linux resembles a lot the win32 hooking I’m used to but somehow it seems easier and more elegant. For example the (old) and widely known LD_PRELOAD trick to interpose a library before another library is intended for exactly that purpose, with the goal of debugging or creating wrappers. Also getting the address of the original function from the wrapper is provided by as explained by the man page of dlsym (similar to GetProcAddress under windows):

There are two special pseudo-handles, RTLD_DEFAULT and RTLD_NEXT. The former will find the first occurrence of the desired symbol using the default library search order. The latter will find the next occurrence of a function in the search order after the current library. This allows one to provide a wrapper around a function in another shared library.

So by following the article and making sure the function prototype matched that off the RSA_generate_key I was dumping the private key in no time. The source code is pretty tiny also:

RSA *RSA_generate_key(int num, unsigned long e, void (*callback)(int,int,void *), void *cb_arg){
RSA *rsa;
BIO* rsaPrivateBio;
typeof(RSA_generate_key) *old_RSA_generate_key;

printf("hooked function: RSA_generate_key\n");
old_RSA_generate_key = dlsym(RTLD_NEXT, "RSA_generate_key");

//rsa = (*old_RSA_generate_key)(1024,RSA_F4,NULL,NULL); //the hardcoded example
rsa = (*old_RSA_generate_key)(num,e,callback,cb_arg); //not sure if this will always work, if not use the hardcoded line
if(rsa == NULL){
printf("error in generating keypair..");
return 0;
}
RSA_blinding_on(rsa, NULL);
rsaPrivateBio = BIO_new_file("rsa.key", "w");
PEM_write_bio_RSAPrivateKey(rsaPrivateBio, rsa, NULL, NULL, 0, NULL, NULL);
printf("saved private key to file\n");
BIO_free(rsaPrivateBio);
//return the private key we just saved to file
return rsa;
}

When you put it all together it results into the following output when you run sslsniff with our own library interposed:

hooked function: RSA_generate_key
saved private key to file
sslsniff 0.8 by Moxie Marlinspike running…

To verify that it actually is dumped correctly you can parse it with openssl:

openssl rsa -in rsa.key -noout -text
Private-Key: (1024 bit)
modulus:
    00:c1:91:2d:f5:22:db:3b:ad:a2:aa:4d:65:d9:07:
    93:a8:02:a0:c5:de:11:71:43:e4:f0:48:f2:8c:ec:
    d1:a4:1b:b0:50:dd:4b:59:4f:08:12:b1:5d:e0:7e:
    c8:16:96:15:79:9d:72:10:b7:e4:a0:75:e1:77:31:
    3c:7d:08:a7:cf:52:75:b4:78:57:f3:41:67:a8:46:
    57:1b:4a:c3:66:f6:ea:c1:3b:73:f5:99:7b:ef:3c:
    89:07:58:ab:84:b2:ca:98:ae:ff:26:14:2b:21:6b:
    be:46:f3:5f:f8:ef:19:aa:7e:7a:02:fa:01:7f:fa:
    f0:a2:ee:83:3a:29:58:61:49
publicExponent: 65537 (0x10001)
privateExponent:
    00:90:66:37:7c:99:c6:26:9c:ff:ae:40:12:ec:76:
    a4:86:3f:7f:a4:5c:67:72:b1:8d:86:5b:44:e6:30:

Conclusion

Even under Linux you don’t always need the source code to be able to make modifications to a program. To reproduce this result you can compile the source with the provided ‘compilehook.sh’ script, then start sslsniff using the provided ‘sslsniff_hooked.sh’ script. Read the sslsniff readme if you want to know how to intercept the traffic from a machine. I also ended up liking gdb a bit more then I did before. For the ones wondering how you can use this private key to decrypt the captured traffic have a look at this: http://wiki.wireshark.org/SSL

References

3 thoughts on “sslsniff howto dump the temporary key”

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.