Close

Android app hacking 101: Finding the hidden gems

A project log for Mi Band reverse engineering

I want to liberate the thing I'm going to be keeping on my wrist

morgan-gangwereMorgan Gangwere 07/02/2015 at 22:000 Comments

There comes a point in the world of Android app manipulation you need to find just where the bodies are buried. Today's little whack at the project involves subverting every avoided call to the android.util.Log.* functions.

If you're unfamiliar with Android's logging framework, I'll give you a rundown:

Amazingly, your phone probably spits out a lot of logging information as it's going on its day to day activities. Developers know about this, but it's always cute to keep an adb logcat terminal open just to see what is going on.

Today, however, we must do some fantastic magic. You see, some application obfuscators wrap the calls to util.log.* and make it harder to debug the application, looking for some call somewhere to switch on debugging. Sometimes, this is called. Sometimes, it's not. I'm looking to make sure that the debugging always happens.

Let's look at an obfuscated function. I'm not going to deobfuscate this because there's simply too many calls to it to fix. First, we'll look at the "decompiled" (in quotes) from Dex bytecode to Java bytecode to Java. I'm using Luyten for my frontend to a beautiful decompiler called Procyon.

The following assumes you are familiar with: a) basic java decompilation. b) apktool and its usage, c) Smali, the language used to express Dalvik bytecode.

    public static void a(final boolean b) {
        if (cn.com.smartdevices.bracelet.o.p && !b) {
            a("DEBUG", ">>> `TRUE` ASSERTION FAILED <<<", 0, 'e');
        }
    }

This function takes in a boolean, b, and logs a little note to say "did this do a thing?" Basic assertions like this are nice and handy when it comes to doing debugging in development, but not so useful when you're running the application elsewhere.

.method public static a (Z)V

.locals 4

sget-boolean v0, Lcn/com/smartdevices/bracelet/o;->p;Z

if-eqz v0, :cond_0
if-nez p0, :cond_0

const-string v0, "DEBUG"
const-string, v1, ">>> `TRUE` ASSERTION FAILED <<<"
const/4 v2 0x0
const/16 v3, 0x65

invoke-static {v0, v1, v2, v3}, Lcn/com/smartdevices/bracelet/o;->a(Ljava/lang/String;Ljava/lang/string;IC)V

:cond_0
return-void

.end method

Any assembler-heads will know this looks like a basic bytecode language. For those who are curious:

This function is easy to circumvent. It has one exit point (cond_0) and has no special exit conditions (nothing early-return). The obfuscation is light with this one.

The flow goes like this:

Changing this function such that the check for v0 being zero is easy. Hell, save a byte or 20, drop the fetch instruction. Now, our code checks the parameter only, not the new value.

Others are... not so easy. This one for example, has a small amount of logic:

    public static void debugCall(final String s, final String str) {
        if (e > a && e < c) {
            Log.i(s, e() + str);
        }
        writeDebugFile(s, str);
    }
I've quietly deobfuscated this one.

The key point here is that first if statement. If E is between A and C, log the thing. I suspect e was at one point the printed logging level, while a was a minimum log level and c was a maximum log level (useful for debugging). This however is a problematic game to play. Let's see what it looks like in Smali! I've truncated this to the relevant bit:

.method public static d( Ljava/lang/String; Ljava/lang/String; ) V
.locals 2

sget v0, e
sget v0, a
if-le v0, v1, :cond_0
sget v1, c
if-ge v0, v1 :cond_0

# ... code

:cond_0
return-void
.end method

Again, this is "Strip it out and don't worry" sort of stuff. Some of these methods with early-return are going to have a label and a return somewhere you might not expect it, such as just after the check for null or 0. This happens, and you have to watch for it.

Discussions