Studying APK reverse engineering by breaking the anonymity of BlindSpot app
BlindSpot is an Israeli app that provides anonymous chat features, and was founded by Dor Refaeli (Bar Refaeli’s brother)
The app started its journey with an aggressive advertising campaign which included billboards and virtual ads.
The app has drawn criticism due to the concern that it would encourage verbal abuse among children and teenagers — its main users.
I’ve performed the penetration test in order to understand whether the app is secure and in fact maintains the privacy of the clients, and also to practice and learn new concepts in the field of Android penetration testing.
After a short test, I was able to break the anonymity of the app and expose the identity of the people who sent me messages.
From an information security point of view, the vulnerability itself is not very interesting — the app’s server sends the phone number of client A to client B, during the process of establishing a new “anonymous” chat.
However, while performing the test, I faced some barriers that made the process harder and more interesting.
I believe this process could be interesting for security researches, so this article will describe in detail all the steps that I took to exploit the vulnerability, in hope that I could help you study something new.
There are more generic and easy ways to achieve the results I have obtained, but the goal of the research and the article is to teach general concepts related to Android penetration tests.
In order to understand the article, you should be familiar with penetration testing and Android operating system, and know the following terms:
Java Bytecode, SMALI, De-compilation, tunneling and WebSockets.
· The code is obfuscated.
· The app uses WebSockets protocol that isn’t passed through the default proxy on Android.
· Each HTTP/s message to the server is signed by OAuth.
The app allows us to start a chat with a user from our contact list, who has installed the app too.
The chat window gives us the option to send text/media messages.
1. First analysis:
One of the tools that can help us, as Android security researches, is the ability to inspect traffic from the client to the server.
With this ability, we can look for application vulnerabilities on the server and also understand what happens on the client side without messing with the code too much.
As a first step, we can sniff the following types of traffic:
DNS queries — Use them to understand which servers the app communicates with. There is no option to perform “flush DNS” on Android, therefore we won’t see new queries at each running of the app. It could be a good idea to run Wireshark during the first running.
(The DNS cache is cleaned every 10 minutes, and sometimes a reset can help).
HTTP/s traffic — Usually the sniffing of HTTP and HTTPS shouldn’t be a complex issue, since we can configure a proxy in the Android level for these types of traffic.
By a long click on the name of the Wi-Fi that you’re connected to and then “Modify network”, you can set an HTTP proxy, and use your favorite web proxy to inspect the traffic. (Fiddler ♥)
It’s a good idea to test the app, send different types of messages and try to understand what happens behind the scenes.
The article doesn’t talk about this, but during the authentication process, the page “api.blindspot/join/activate” returns a JSON that contains a key and a token, that will be used to sign every message that is sent to the server.
The signature is done by using OAuth and is added to the ‘authorization’ header.
You can sign messages manually or be a 1337 and write an extension to Fiddler to do it.
After sending several messages, we can conclude that:
A. We’re not able to see text messages that have been sent to the server / received from the server.
B. There are DNS queries to “chat.blindspot.im”, and we do not see this host on Fiddler.
We can assume that the messages that are sent to “chat.blindspot.im” are not HTTP/s messages, and this is the reason we don’t see them on Fiddler.
Due to the fact that the communication with this host is performed on port 443, it makes sense that the actual protocol is WebSockets.
A quick search of the string “WebSockets” in the Java/SMALI code confirms our suspicions.
2. Code analysis:
After getting a first impression by inspecting traffic from the app, we would like to understand the code a little bit.
The easiest way to perform it is to de-compile the DEX file.
The de-compilation process: inside the APK there is a DEX file that contains all the code of the app.
This file can be converted to a JAR file, by using “dex2jar”. The JAR file can be opened by your favorite Java De-compiler.
Unfortunately, there is no one Java de-compiler that does the job perfectly (As opposed to .NET Reflector); Therefore, in many cases the right way to work is to use a few de-compilers simultaneously + the SMALI code.
The software “Bytecode Viewer” can help us do that.
Furthermore, we can use the Manifest file to see names of packages that are set for each activity, service and broadcast receiver.
Because the code has been obfuscated, it’s difficult to track the flow.
We can find interesting classes by searching for specific strings. For example:
· A class that handles communication would contain a “socket” object.
· A class that handles encryption would contain a “decrypt” function.
As you probably know, usually there is no magic way to understand an obfuscated code.
Before we start wandering around the code, we should understand what is the goal (e.g. if you want to look for server vulnerabilities, it’s not a good idea to reverse classes that are related to graphics).
As stated earlier, BlindSpot app uses WebSockets protocol that isn’t passed through Android’s default proxy.
So, let’s build a patch that will make the protocol go to a web proxy that supports WebSockets.
A few words about WebSockets, proxies, HTTP tunneling and the meaning of life:
· A relatively new protocol that allows creating a “raw socket” over browsers.
· Saves many resources such as HTTP headers and TCP handshakes (in comparison with HTTP).
· Efficient when we want to display live data from the server.
· A reciprocal connection that is alive as long as the user hasn’t closed the website.
· A prefix of wss:// for encrypted connection and ws:// for unencrypted.
· Handshake: The only link between WebSockets and HTTP is the handshake.
it’s a little bit tricky:
1. The Browser creates a regular socket with the server.
2. The First request is “HTTP compatible” (looks like a regular HTTP request).
3. The Server returns a “HTTP compatible” response (look like a regular HTTP response).
4. The socket becomes a raw socket for all intents and purposes.
Notice the upgrade header.
- It’s important to understand that since we’re talking about a raw socket, the WebSockets protocol is unaware of HTTP and HTTP Proxies, which is what makes it not so trivial to pass it through a proxy.
HTTP Tunneling + Proxy servers:
HTTP Tunneling is a technique to tunnel different protocols over HTTP. Most proxy servers today support it, including our web proxy (Burp/Fiddler).
The process of creating an HTTP tunnel: The browser sends an HTTP request with a “Connect” method to the proxy server. This request contains the actual address and port of the remote host. The proxy server then returns a “200 OK” response, and from that moment on, the proxy server passes all the traffic from the client directly to the server without any modification.
It’s actually a way to establish a TCP proxy over HTTP.
With the understanding of those two topics, the conclusion is that the way to create a proxy connection of WebSockets is by creating an HTTP Tunnel, by an HTTP Connect method, and then establishing the WebSockets handshake through this tunnel.
Modern web browsers do it by default, in case they recognize a proxy configuration.
Back to the code.
After we take a quick glance at the code, we can find something that seems, to me, a little bit strange: a regular socket object is used to perform WebSockets connection, instead of using Java libraries that provide convenient wrap of the protocol.
The socket creation code in “dbq” class:
The code that starts the HTTP compatible handshake in “ckb” class:
This kind of implementation doesn’t make our life easier, because there is no prepared function that performs the HTTP tunneling that was explained previously.
Fortunately, I happened to find an implementation of the HTTP tunneling inside the code (Probably debugging leftovers) in “ckh” class:
Furthermore, we can clearly see an “if” condition (after this function is being called) that leads the app to the HTTP tunnel flow.
During a regular running of the app the condition is always false.
3. Let’s patch!
3.1 Print messages to log:
For starters, we would like to create a patch that causes the app to print text messages to the log, before they are encrypted and sent to the server.
An example for a function that handles messages of a certain type, is the function “a” from class “cki”.
(It doesn’t handle all messages types)
The next step is to create a patch that prints to the log the value of ‘arrby’, in each call to the function.
Because ‘arrby’ is an array of bytes, we should convert is to a String object first.
Two basic principles when writing patches:
· Change as little code as you can — if possible, add a new function to the class, and then add only a call to the function in the original code.
· If you have no experience in SMALI, try to rely on SMALI code that is compiled from Java code that you can write. You can use an extension to IntelliJ to convert java to SMALI.
The function that we want to add looks like:
And after conversion to SMALI:
This SMALI code will be added to the cki.class in the segment of “virtual methods”.
After that, add a call to our new function, at the beginning of the function cki.a(byte):
Now we can build the patched APK, sign it and install on an Android machine.
Next, we would like to watch the log. It’s recommended to delete the old log before we do it, by using “adb logcat –c”. After that, use the command “adb logcat” to watch the log in real time:
In this screenshot, you can see the messages that our patch has printed to the log.
3.2. Change the socket object:
After the code analysis in step 2, we figured out that we should perform two changes in the code in order to make the WebSockets protocol go through our web proxy:
a. Create a proxy socket:
Make the “dbq” class create a socket by using the function “a(string,int,Boolean,int)” in class “ckh”. This function will perform the HTTP tunneling for us.
The easiest way to do it is by changing the condition that determines whether a regular socket or a proxy socket is created:2
We can see that the “if-eqz” command checks if the register v0 is equal to zero. If true, a jump to the label “cond_16” is then performed. If false, a call to “ckh.a” function, that creates a socket with a proxy, is performed.
The change is minor — change the command “if-eqz” to “if-enz”. It assures that on each running of the app, the function “ckh.a” is called.
b. Set the IP address and the port of the web proxy:
We should set the IP address and the port of our web proxy server in the “ckh” class.
The easiest way to do it is by changing the code before the call to the constructor function — InetSocketAddress(string,int). Let’s override the values inside the registers that are sent to the function, and replace them with new values:
The address “10.0.3.2” represents the host machine on Genymotion emulator, and “0x22b8” represents the number 8888 in IEEE 754 standard (Fiddler’s port).
With all my love to Fiddler, Burp Suite does a better job when it comes to WebSockets.
When we run the patched app and Burp Suite (in our proxy) at the same time, we can sniff the WebSocket traffic under “Proxy” → “WebSockets history”.
Embarrassingly, the first message that is received from the server, when another user starts an anonymous chat, contains his phone number:
A great thank you, to Yuval Kohen who has contributed a lot to the research.