Intercepting Android native library calls
Edit: at the time of writing, not many details could be disclosed as part of a responsible disclosure policy. The application in question was BlackBerry Messenger (com.bbm). BlackBerry did not respond to our findings. The full report is now available (unrevised version, including typos, mistakes, etc.).
As part of a uni project, we’ve been busy reverse engineering an Android application. Amongst the things we investigated were the following:
- SSL checks, using signature based static analysis of the Java bytecode
- MitM and decryption/modifying of streams
The APK contains quite some calls to native libraries. The developers decided to include some libraries that can be found on every Android system as well, suggesting that they might not trust the ones that are delivered with stock Android, and decided to compile their own version. The application core is mostly contained in a shared object as well, which makes it painful to get a quick overview of what is going on inside. We don’t have sources, so have to rely on static and dynamic analysis of the ARM binary.
During disassembly, we noticed a particular text file being written to the data folder with AES-256-CBC encryption. The encryption relies on a few functions in the OpenSSL EVP library, which provides a high-level interface to cryptographic functions, of which they used EVP_BytesToKey and EVP_EncryptUpdate amongst others. A quick google shows that they based this mostly on an example implementation. How do we capture/intercept those calls and log them?
Our first approach was to attach a remote debugger to the process with GDB (or IDA Pro if you prefer and have loads of money) and put breakpoints on the parts where encryption is being used. This worked, but we wanted a more permanent solution.
Is it possible to use LD_PRELOAD’s on Android? Yes it is, we can preload shared objects on Android just like we can on other Linux systems. (Though you may not want to attempt this on Android apitrace project is a nice example of a project that uses a very similar technique to trace OpenGL, Direct3D, and other graphics APIs.
The great thing about LD_PRELOAD is that it can intercept hardcoded paths in the binary thanks to the load order. We created a shared object for ARM/Android, which we could then preload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <openssl/evp.h>
#include <android/log.h>
int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md, const unsigned char *salt, const unsigned char *data, int datal, int count, unsigned char *key,unsigned char *iv)
{
int (*new_evp_bytestokey)(const EVP_CIPHER *type,const EVP_MD *md, const unsigned char *salt, const unsigned char *data, int datal, int count, unsigned char *key,unsigned char *iv);
new_evp_bytestokey = dlsym(RTLD_NEXT, "EVP_BytesToKey");
__android_log_print(ANDROID_LOG_DEBUG, "SSLOverride", "Salt: {%d,%d}, Key: %s, Count: %d\n", *(unsigned int*)salt, *(unsigned int*)(salt+4), data, count);
return new_evp_bytestokey(type, md, salt, data, datal, count, key, iv);
}
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl)
{
int (*new_evp_encryptupdate)(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
new_evp_encryptupdate = dlsym(RTLD_NEXT, "EVP_EncryptUpdate");
__android_log_print(ANDROID_LOG_DEBUG, "SSLOverride", "Plaintext: %s \n", in);
return new_evp_encryptupdate(ctx, out, outl, in, inl);
}
|
This will catch the function calls, print the arguments, then pass the call on to the original function.
Compile the C file with your favorite NDK toolchain and linked with -llog (this one has been compiled with API level 14):
1
2
3
4
|
$ ../android-toolchain/bin/arm-linux-androideabi-gcc -shared -o libsslover.so sslover.c -I /home/cedric/toolchain/openssl-1.0.1e/include/ -llog
$ file libsslover.so
libsslover.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, not stripped
|
We then push the shared object to the Android device and preload it into the Dalvik VM by setting the wrap property for the application. You might need root for some of these commands (or remount with read-write permissions):
1
2
|
$ adb push libsslover.so /data/libsslover.so
$ setprop wrap.com.xyz.yourapp LD_PRELOAD=/data/libsslover.so
|
Then start the application as you normally would from the launcher, and watch logcat closely:
1
2
3
4
5
6
|
$ adb logcat | grep --line-buffered -i SSLOver
D/SSLOverride(27373): Salt: {87611,13275}, Key: sUp3r1337h4x0rK3Y, Count: 5
D/SSLOverride(27373): Plaintext: userid cedric:13487965
D/SSLOverride(27373):
D/SSLOverride(27373): Plaintext: pass blaatschaap
|
We’ve got plaintext being spit out in our terminal right before it gets encrypted!
Recent Comments