xmpp mitm for fun and debugging

lunabee what the hell

So, dino.im keeps not encrypting my OMEMO messages to someone in a MUC I'm in. Which sucks. Per standard, OMEMO messages are encrypted to every member of a MUC, and all MUC participants should be members in an encrypted MUC.

I want to see if somehow, the member list being sent over is fucked up. Which means I need the raw XML. And since I don't wanna fuck around with any actual programming, I'm just gonna bootleg together some MITM bullshit.

the setup

The two basic parts to this are the netcat and openssl command-line tools. openssl (among other, more important things) lets you open up a TLS-encrypted stream to any server, that you can enter or pipe input into. We'll use that to connect to our XMPP server.

Similarily to the functionality of openssl we're using, netcat lets you read and submit plaintext data to any server. We're going to be using it to recieve plaintext XML from our XMPP client, dino.im.

The basic setup is the following command (assuming example.com is our XMPP instance, and using the deprecated 5223 TLS port):

nc -l -p 5222 | openssl s_client -connect example.com:5223

This won't work though. For one, the opening stanza of all XMPP client-to-server connections specifies the domain it's connecting to (presumably, for if the server serves several domains). Using this naieve command, we would point our dino.im to login to username@localhost, and thus we would have to transform localhost to example.com.

This is relatively easy. The harder part is that dino.im will force STARTTLS on any unencrypted connection. This includes localhost.

malcolm in the middle (or, Fuck You GLib)

So, the current plan is to just set up an openssl s_server and an openssl s_client connected to the XMPP server I use, joining them with a circular pipe, while setting up a tee or something to intercept messages.

openssl s_server needs three main things from our ACME client: our certificate (refl.lunabee.space.cer), our private key (refl.lunabee.space.key), and the certifiate chain up to the root (fullchain.cer).

ACME gave me a fullchain.cer with three certificates in it: the ISRG Root X1 cert, the intermediate R3 cert, and then my cert. The thing is, for some motherfucking reason GLib will fucking cry if I provide the whole certificate chain. Gets too much and gets overwhelmed, I guess.

That's probably a bug

So, we extract the middle certificate in fullchain.cer (more specifically, all of the (one) intermediate certificate(s)) into, let's say, inter.cer. Rounding out, we'll rename our private key and cert to malcolm.key and malcolm.cer, respectively. Then, we can just use the following command to set up our server!

openssl s_server -port 5223 -cert malcolm.cer -key malcolm.key -cert_chain inter.cer

The server will provide our certificate to any incoming connections on port 5223, as well as the intermediate certificate in order to construct a full chain up to the root cert. Once a connection is made, stdin and stdout will be forwarded to/recieved from the connected application.

Last piece of the puzzle is the client! For a server at xmpp.example.com that supports direct TLS at 5223 (which, you can discover by digging for the same kind of SRV record we set up above) it'd be:

openssl s_client -connect xmpp.example.com:5223


What we wanna do now is connect these two with a circular pipe, so we don't have to manually pass messages. But we can't just naievely connect them: we also need to log messages, and we need to replace all mentions of the actual server with ours, or else the server will reject our connections due to not serving our domain (...and for dealing with MUC domains. We'll deal with that later).

One problem, though, is that s_client and s_server don't create newlines, so its output is never newline-terminated. Since sed is line-based, it won't work. I created a small replacement for this exact purpose, called fuck you sed (fyed). It's just a find/replace tool but it doesn't buffer its input or output. In that same repository I also made fuck you tee (fyee) and fuck you input edition (fyin), which are just a tee clone and a cat clone, respectively. To finish up our circular pipeline, we just need a named pipe. We get that by executing the following command:

mkfifo lonkle

This will create a file "lonkle" in the current directory, which is our named pipe. It'd be a good idea to rm cock when you're done. Then, finally, we can run the following command to complete our goal!

fyin lonkle | openssl s_server -cert malcolm.cer -key malcolm.key -cert_chain inter.cer -port 5223 -quiet | fyed refl.lunabee.space xmpp.example.com | openssl s_client -connect xmpp.example.com:5223 -quiet | fyed xmpp.example.com refl.lunabee.space | fyee lonkle

Quick notes: first, this command will log everything coming from the XMPP server to your client to stdout. Second, you need to stop and restart this command every time the stream closes. dino closes the stream once right before it asks for your password, and again right after.