Here I am, continuing my ongoing saga of cryptography related coding on Windows. In the last couple blogs I got symmetric encryption and public key encryption working in a kind of minimal sample code type of way. This is all well and good, but it came time to integrate it into the larger program.
Wait, what do you mean you don't know what the "larger program" is? Haven't I told you what this project actually is? ... I haven't!? Man, I'm a real jerk sometimes. Well, might as well get this over with.
The project is called MungeTLS. It is planned to be a relatively minimal implementation of TLS. This protocol, formerly known as SSL and marked by the "https" URL scheme, is absolutely foundational in almost everything you do on the internet today. It uses a lot of other protocols and technologies to allow things like buying stuff online, because it safely encrypts data sent over the Internet.
Why is MungeTLS going to be "minimal"? Well, because TLS has a lot of aspects to it, and a number of extensions and plugins. Also, 90% of the TLS traffic in the world probably doesn't use most of them. I'm mostly concerned with things that are commonly used. For example, I'm not sure if I'm going to bother with client authentication (as opposed to server authentication, which most assuredly is important).
But the main feature of MungeTLS is that it will have a very tightly integrated plugin system, where the caller will get callbacks through every stage of the TLS connection. I mean to allow the caller to mess with data at any part of the process from initiating the connection to the handshake to every message sent and received. The simple rationale for this is that I don't believe something like this exists out there. In my line of work, I've been tasked with testing two or even three different SSL implementations, and never had the time to write this sort of test tool to really screw with the protocol in deep and interesting ways. This project aims to fill this void.
Since I've only needed to test client side TLS implementations so far, I'll be targeting writing the server-side TLS code for now. My plan is to write a functioning web server that can interact against OpenSSL and SChannel (i.e. the TLS implementation used by most Microsoft products, notably Internet Explorer). Once I have that working, I'll be adding in the plugin model.
As far as current progress, I'm mid-way through the TLS handshake. I've just decrypted ClientKeyExchange
message, which is the only message involving public key cryptography. That's what this blog post is about.
Public Key Craziness
So yeah, going back to the intro paragraph, I was ready to integrate my sample public key cryptography code into the rest of the program. I diligently copied over functions with names like EncryptBuffer
, and set up some levels of abstraction that might allow me to be crypto library-agnostic (WindowsPublicKeyCipherer
derives from PublicKeyCipherer
).
I integrated it into the regular message handling and set it up to decrypt the payload of the exchange_keys
field of the ClientKeyExchange
message. Everything was looking good.
Until it wasn't.
I got varying error messages from CryptDecrypt
: things like NTE_BAD_LEN
, and ERROR_OUT_OF_MEMORY
. None of these made any sense to me, because I was sure my buffer allocation sizes were correct, and that I was pulling out the correct part of the message from the network to pass to decryption.
I started passing CRYPT_DECRYPT_RSA_NO_PADDING_CHECK
to CryptDecrypt
in the hopes that I might see some slightly malformed data that I could fix up... I don't even know how. That yielded just blobs of bytes with no pattern. This phenomenon was happening with both OpenSSL and IE, so I had to be doing something pretty wrong.
Encryption in OpenSSL
I needed to see what was being encrypted, and into what data was being encrypted and what it turned into on the wire. I couldn't figure out how to get this on Windows, because they have layers and layers of internals, and I doubt I'd find any code calling functions I was familiar with, like CryptEncrypt
. Instead, I dumped the DLL exports of libeay32.dll (a central DLL from OpenSSL) and looked for anything useful.
>dumpbin /exports \programs\openssl\bin\libeay32.dll | findstr /i rsa
...
490 895 0002F770 RSA_private_decrypt
491 896 0002F740 RSA_private_encrypt
492 897 0002F7A0 RSA_public_decrypt
493 898 0002F710 RSA_public_encrypt
...
So I set a breakpoint on RSA_public_encrypt
and saw it get hit in my OpenSSL test app. I had to guess at the parameters, to see if I could get the cleartext and ciphertext, but it wasn't too bad. Here, might as well show it. I don't show enough debugging on this blog. Debugging without symbols can be especially interesting.
Also, a link to my Windbg Quick Reference, to decipher some of the commands.
2:003> x libeay32!*RSA_public_encrypt*
*** ERROR: Symbol file could not be found. Defaulted to export symbols for f:\programs\openssl\bin\LIBEAY32.dll -
00000000`0046f710 LIBEAY32!RSA_public_encrypt (<no parameter info>)
2:003> bu LIBEAY32!RSA_public_encrypt
2:003> bl
0 e 00000000`0046f710 0001 (0001) 2:**** LIBEAY32!RSA_public_encrypt
Okay, so we've found the function and set a breakpoint on it. Then we hit g
to continue execution to see if it gets hit.
2:003> g
Breakpoint 0 hit
LIBEAY32!RSA_public_encrypt:
00000000`0046f710 b838000000 mov eax,38h
2:003> r
rax=0000000000000001 rbx=0000000001d3a1e0 rcx=0000000000000030
rdx=000000000013ef58 rsi=0000000001d26af0 rdi=0000000001d27ea4
rip=000000000046f710 rsp=000000000013eef8 rbp=0000000001d27ea6
r8=0000000001d27ea6 r9=0000000001d3a1e0 r10=0000000000000010
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000001d27ea0
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
LIBEAY32!RSA_public_encrypt:
00000000`0046f710 b838000000 mov eax,38h
From learning the (much simpler than x86) calling convention for x64, I know that the first four arguments to a function are passed in registers rcx
, rdx
, r8
, and r9
, respectively. Remaining arguments are pushed on the stack. Maybe one of these four arguments could have the cleartext I'm looking for.
Speaking of the cleartext, I actually have an idea of what it ought to look like. The only part of TLS that involves public key encryption is the "client key exchange", in which the client generates a random number, encrypts it with the server's public key (which it knows, since it's public), and sends it to the server, which can decrypt it with the private key. This set of random bytes is called the "premaster secret".
I actually see that rcx
has a value of 0x30, that is, 48. This is exactly the right size of the premaster secret (taken from RFC 5246):
struct {
ProtocolVersion client_version;
opaque random[46];
} PreMasterSecret;
client_version
The latest (newest) version supported by the client. This is
used to detect version rollback attacks.
random
46 securely-generated random bytes.
struct {
public-key-encrypted PreMasterSecret pre_master_secret;
} EncryptedPreMasterSecret;
pre_master_secret
This random value is generated by the client and is used to
generate the master secret, as specified in Section 8.1.
That's 2 bytes for the protocol version, followed by 46 bytes of random data. Time to examine the arguments.
2:003> db @rdx L@rcx
00000000`0013ef58 03 01 17 9f c6 23 5e 48-8a 2c 0b 82 c6 28 1f 29 .....#^H.,...(.)
00000000`0013ef68 19 54 59 70 76 96 7b 45-ab 82 25 ec 0c c1 8d dd .TYpv.{E..%.....
00000000`0013ef78 60 27 c1 e1 8e be 4f 1b-af 7e da 58 24 ac 84 f9 `'....O..~.X$...
Oh hey, look at that! 0x0301
is the protocol version identifier for TLS 1.0. I'm pretty sure this second argument contains the cleartext. That wasn't too bad. One of the other arguments might contain, on completion, the ciphertxt. Let's look before and after.
2:003> db @r8
00000000`01d27ea6 03 00 02 00 30 82 01 fc-30 82 01 69 a0 03 02 01 ....0...0..i....
00000000`01d27eb6 02 02 10 ab 4f 74 b3 a1-7b 3b 85 41 73 e4 51 1a ....Ot..{;.As.Q.
00000000`01d27ec6 9f 72 75 30 09 06 05 2b-0e 03 02 1d 05 00 30 14 .ru0...+......0.
00000000`01d27ed6 31 12 30 10 06 03 55 04-03 13 09 6d 74 6c 73 2d 1.0...U....mtls-
00000000`01d27ee6 74 65 73 74 30 1e 17 0d-31 30 30 34 30 31 30 37 test0...10040107
00000000`01d27ef6 30 30 30 30 5a 17 0d 33-39 31 32 33 31 32 33 35 0000Z..391231235
00000000`01d27f06 39 35 39 5a 30 14 31 12-30 10 06 03 55 04 03 13 959Z0.1.0...U...
00000000`01d27f16 09 6d 74 6c 73 2d 74 65-73 74 30 81 9f 30 0d 06 .mtls-test0..0..
2:003> db @r9
00000000`01d3a1e0 00 00 00 00 00 00 00 00-20 42 56 00 00 00 00 00 ........ BV.....
00000000`01d3a1f0 00 00 00 00 00 00 00 00-f0 a3 d3 01 00 00 00 00 ................
00000000`01d3a200 00 a5 d3 01 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`01d3a210 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`01d3a220 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`01d3a230 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`01d3a240 0d f0 ad ba 0d f0 ad ba-01 00 00 00 06 00 00 00 ................
00000000`01d3a250 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
2:003> gu
SSLEAY32!SSLv3_server_method+0x24b6:
00000001`8000c8c6 0fbaa69c0100001b bt dword ptr [rsi+19Ch],1Bh ds:00000000`01d26c8c=00040004
2:003> db 00000000`01d27ea6
00000000`01d27ea6 49 e6 b6 a6 ef f6 dc ce-b6 ac 49 bc 57 57 b3 24 I.........I.WW.$
00000000`01d27eb6 0b 76 65 8b e9 53 35 2c-f8 68 72 86 bd b4 66 44 .ve..S5,.hr...fD
00000000`01d27ec6 53 7a fe 20 a4 02 dd 1d-61 1d cf c1 0b 36 50 0e Sz. ....a....6P.
00000000`01d27ed6 d0 7b 05 0b 12 b5 66 33-a5 45 c2 6e 50 9a dd f5 .{....f3.E.nP...
00000000`01d27ee6 2f 3c a4 dc c0 34 fa 98-3f ef 71 bd f0 33 93 81 /<...4..?.q..3..
00000000`01d27ef6 9d 82 8e bc 70 40 91 5a-ba ca ba ae 21 da d3 b4 ....p@.Z....!...
00000000`01d27f06 a8 8d 6f f4 15 39 48 19-74 51 1f d1 be ab ab 51 ..o..9H.tQ.....Q
00000000`01d27f16 7d bc 15 0b 83 e8 5f 16-4c d6 46 b3 93 5b 71 5f }....._.L.F..[q_
2:003> db 00000000`01d3a1e0
00000000`01d3a1e0 00 00 00 00 00 00 00 00-20 42 56 00 00 00 00 00 ........ BV.....
00000000`01d3a1f0 00 00 00 00 00 00 00 00-f0 a3 d3 01 00 00 00 00 ................
00000000`01d3a200 00 a5 d3 01 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`01d3a210 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`01d3a220 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`01d3a230 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`01d3a240 0d f0 ad ba 0d f0 ad ba-01 00 00 00 06 00 00 00 ................
00000000`01d3a250 c0 a9 d3 01 00 00 00 00-00 00 00 00 00 00 00 00 ................
Looks like r8
gets totally overwritten when the function completes, whereas r9
is untouched. It's probably safe to say that r8
is the ciphertext. In fact, I did confirm this is true by looking at the traffic sent to the server.
As they say, RTFM
So after all of this, what do I end up with? I know that the cleartext beforehand is correct. I know the ciphertext is reaching the server correctly. I know it's not an OpenSSL specific problem. God, what could be going wrong? Feeling quite deflated after that rush of debugging, I went back to look at the docs.
Wait, what? Let's look at that a bit closer.
Seriously? I'm leaning closer to the screen now.
Squinting furiously at the screen at this point.
Mixture of elation, rage, and aggressive sighing ensues.
I should have known something like this would come up. In fact, when I was manually implementing my padding check, I was finding that the data was "backwards", but I didn't think much of it at the time. Now it makes perfect sense. So I inserted the appropriate array reversals at the right places, and everything magically started working. Infuriating.
So what did we learn? Little endians can be a big pain. I'm sure I'm going to have all kinds of problems getting the next stage of encryption working, too. I'll be sure to tell you all about it.
0 comments:
Post a Comment