Intercepting and decrypting SSL communications between Android phone and 3rd party server
In this article I am going to explain how to intercept and decrypt the SSL traffic between an Android phone and third-party server. This is what sometimes the people have to do to troubleshoot complex communication problems.
What is needed:
- Linux computer with following tools:
- OpenSSL (http://www.openssl.org)
- SoCat (http://www.dest-unreach.org/socat)
- Java (J2SE, at least JRE 1.6 or later)
- Bouncy Castle library (http://bouncycastle.org/download/bcprov-jdk16-141.jar) - place this JAR file into your …/jre/lib/ext directory
- Jailbroken Android phone. In fact, jailbreaking is needed only to install new trusted CA certificate
- Some patience :)
SSL connection are secured end-to-end. Intercepting SSL traffic without having the private key from the server it is going to is mostly pointless, unless you are planning to attach the crypto algorithm itself. And since the connection is going to a 3rd party server most likely you won't be able to get their private key (unless you steal it first ;) ).
The goal is to terminate the SSL connection locally to have the date clear-text and then forward it to the regular destination establishing another SSL connection. Essentially, you want to break one end-to-end SSL connection into two independent segments and see what is in between. Optionally, you can simulate the real backend with some kind of fake server - if this is what you need for your debugging purposes. This does not change the method.
This is definitely a well-known “man-in-the-middle” technique. The goal of this article is to provide a summary of how it can be applied for a particular scenario.
The applications rely on the platform to provide reliable and trust-worthy SSL tunnel (for HTTP transactions, for example). The platform does everything it can to make sure that the server certificate is valid by checking it against the preloaded trusted certificate database.
While it is possible to generate so-called self-signed certificate, in most of the cases it won't work. By default all SSL implementations refuse to accept self-signed certificates (best case - the application or browser may prompt you if you are willing to trust that untrusted certificate, but with the exception of Web browser it is almost never the case). The application has to do something specifically to allow SSL implementation to accept such a certificate. The whole goal of our exercise is to intercept the traffic from the application that is NOT designed to accept untrusted certificates (otherwise it is too easy!).
While you may already understand what are we going to do, you realize that getting another certificate to substitute the existing one that does not belong to you from Thawte will be problematic ;)
So, what we will be doing is creating a new Certificate Authority (CA), creating new server certificate signed by it and loading the certificate of this CA onto the phone so it trusts it the same way as it trusts Thawte or others.
In addition to that, you need to pass the traffic through your machine so you can manipulate with it. There are several scenarios you may consider:
- Redirecting all traffic normally going through your 3G connection to your machine. This can be achieved by setting up a VPN connection to your host.
- Redirecting all WiFi traffic going through the network you control (your home network). In this case the VPN solution will also work but there may be easier wy to do it - you can always set your machine as default gateway if it is located on the same subnet as your phone. You can do it either directly on the phone or by modifying the configuration of your router if it allows you to mess with the routing table.
- USB cable connection. I personally did not try that but it should be relatively easy to use the same trick and set your machine's IP address ad default gateway for your phone.
In either case what is important is that you pass all the traffic from your phone through your machine. By the way, if you are manipulating the routing information on the phone, you can route only the traffic you need to your host, not everything.
The sample environment
Lets assume the phone has 3G data connection and your Linux box has a public IP address of 184.108.40.206. We will set up the VPN tunnel between the phone and your machine using 10.0.0.0/24 network, your phone will get 10.0.0.100 address an your interface on Linux side will be 10.0.0.1.
Lets assume the phone application you want to debug is connecting to 220.127.116.11 on port 443 (HTTPS). It may be easier (and more realistic) if you know the host name the application connects to instead of the IP address. You will have to determine it yourself. One hint I can give - use tcpdump and look at the DNS requests your phone is launching. If you see that it is looking for the host that corresponds to the service the application connects to - this is what you need. You can also reverse-engineer the application to find out if it contains something like a host name inside. It is also possible that the app receives the target host name from another server via its protocol - using some kind of service discovery mechanism. The possibilities are unlimited.
Why it is important? Because if you want to pretend to be “that” server you need to know how the SSL client (application) will be calling you. If it is calling by host name, it may get resolved in many different IP addresses for redundancy and load balancing purposes. You can issue a server certificate for an IP address, a host name or domain name (wildcard certificate). This is why I highly recommend to find out the host name the app is connecting to instead of the IP address as latter may change for each request.
We will be using VPN method. I personally used XL2TP package for Linux as I prefer L2TP over P2TP. You can get any kind of VPN server you want, it does not really matter. We won't be covering the VPN setup in this post.
Once you get your VPN up, your phone will use 10.0.0.1 as default gateway, will have ppp0 interface up with address 10.0.0.100. And your Linux host will also have ppp0 interface up with address 10.0.0.1. If you run tcpdump on it, you will see that all your phone's data traffic is now going through it. Part of the job is done!
Don't forget that if you want your phone to go to the Internet you need to enable IP forwarding and configure NAT on your Linux system. Both are done in two shell commands:
echo 1 > /proc/sys/net/ipv4/ip_forward /sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
For NATing there may be other ways to achieve the same result, just look for HOWTO for iptables.
Lets get to the cryptic stuff :)
First, lets create our CA:
Generating a new key for your CA
openssl genrsa -des3 -out ca.key 4096
Generating you CA certificate
openssl req -new -x509 -days 365 -key ca.key -out ca.crt
One important note concerning the CN value for the CA certificate. Make sure it is different from any CNs of the server certificates you are going to generate later. Do not put the same server name there.
Now lets generate our server certificate. Lets assume we have an application that makes requests to http://myapp.mydomain.com. These two commands will generate the server key and Certificate Signing Request for it. Make sure you enter “myapp.mydomain.com” in the CN field of your CSR - this will identify the server!
openssl genrsa -des3 -out server.key 4096 openssl req -new -key server.key -out server.csr
Now your CA will have to carefully verify your information and charge you a fee for signing your server key. Since you happen to own this CA it is up to you if you want to accept the CSR or not ;) As well as you decide how much do you pay to yourself for issuing the certificate ;)
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
If you want to get rid of the password (this is only for you and for your debugging purposes so why bother?):
openssl rsa -in server.key -out server.key.insecure mv server.key server.key.secure mv server.key.insecure server.key
Great, now you have the server key, server certificate. The only problem is that Android will not consider you a trusted CA at this point as your CA certificate is not it its database. We need to get it on the device.
Android stores the CA certificates in /system/etc/security/cacerts.bks. Pull this file from the phone:
adb pull /system/etc/security/cacerts.bks cacerts.bks
Store a copy of the original file somewhere as you will eventually want to restore it.
Add your CA certificate to this database:
keytool -keystore cacerts.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storepass changeit -importcert -trustcacerts -alias CACERT -file ca.crt
Make sure you answer “yes” to the question “Trust this certificate?”. Now you have updated your database. You need to push it back to the phone and for that your phone has to be jailbroken. I was not able to push the file directly in place here is what I was able to do:
- push modified cacerts.bks to /sdcard
- login to the shell (adb shell), become root (su -) and move the file into right place
- fix the owners and permissions: chown root.root cacerts.bks ; chmod 644 cacerts.bks
- if your filesystem is mounted read-only, you can remount it using "mount -o remount,rw /system" and then back "mount -o remount,ro /system". I did not have to do it on my device.
Now reboot the phone.
Re-routing the traffic to you
We want to intercept the TCP traffic going through our host to myapp.mydomain.com, port 443 (HTTPS). Again, there are different ways to do it with iptables on Linux - depending on how granular you want your configuration to be. For simplicity lets do this:
iptables -t nat -A PREROUTING -i ppp0 -p tcp --dport 443 -j REDIRECT --to-port 4443
This will redirect all TCP traffic to port 443 to your local port 4443.
Testing the set-up
Now lets make sure we are ready. The best way to do it would be to hit your Linux box with the Web browser from Android phone and see if it complains about the invalid certificate or not. If it does not, the app will most likely won't do it too. Since we do not want to run the Web server, we will just simulate it with socat (greatest network debugging tool, by the way!):
socat OPENSSL-LISTEN:4443,reuseaddr,verify=0,cert=/tmp/server.crt, key=/tmp/server.key,cafile=/tmp/ca.crt,debug -
This will set up a listener on local port 4443 that uses SSL protocol, the given server key and certificate (the one you have generated for myapp.mydomain.com, remember?). What do we expect? We expect to see the HTTP request from your Web browser on your console (since the output for socat tool is specified as “-“).
On Android (assuming your VPN is open and ready) open the browser and type “https://myapp.mydomain.com/blah-blah”. Hit “Go”. If you did everything right, your browser will request the DNS to resolve “myapp.mydomain.com” and will get back 18.104.22.168. Then it will open a TCP connection to 22.214.171.124, port 443. The connection will go from 10.0.0.100 address via 10.0.0.1 gateway. Instead of being NATed and sent out, it will be redirected to local port 4443 on your Linux machine because of the iptables rule you have created. And your socat will accept this connection. It will present the SSL certificate issued for “myapp.mydomain.com” which is signed by your own CA. On the device, the SSL implementation will find the appropriate CA certificate in cacerts.bks file and will determine that it is a trusted one. Since the server name you are connecting to (myapp.mydomain.com) matches the CN value of the certificate presented by socat (myapp.mydomain.com) and this certificate is properly signed by trusted CA (yours) the SSL implementation will consider the connection as trusted. As result, you shall see “GET /blah-blah HTTP/1.1…” on your Linux console. If this is the case, you have the working environment. If your browser fails to connect, then you have a routing or iptables problem. If your browser complains about untrusted certificate, you did something wrong when issuing the server certificate or loading CA certificate on the phone.
All you need now it to bridge your connection to the target. You can do something like this:
socat -v OPENSSL-LISTEN:4443,reuseaddr,verify=0,cert=/tmp/server.crt, key=/tmp/server.key,cafile=/tmp/ca.crt,debug,fork OPENSSL: myapp.mydomain.com:443
This command will accept the connection on port 4443, decrypt it, open the SSL connection to myapp.mydomain.com, port 443 and pass unencrypted data between these two sockets. “,fork” option will ensure that it is done for each incoming connection and “-v” option tells socat to display the traffic going back and forth.
This method can be used to debug most of SSL connections between Android phone and a 3rd party server without modifying the applications on the phone and causing any SSL handshake failures. While it may apear to be a hacking technique, the author does want to emphasize that the material is presented for educational purpose only and to perform end-to-end protocol debugging of your own software. Obviously, like in many other cases, the technically feasible method can be used for different purposes and the author is not responsible for any damage caused by it.
In this post I used some recipes from other people and appropriate credits and references are at the end of the post.
Variations of the method
- instead of using Internet or WiFi connection the USB cable connection can be used
- Linux can be replaced with any Unix :)
- Guide on making self-signed certificates
- Importing certificates on Android phones
- OpenSSL library
blog comments powered by Disqus