-
Abandonment? Reincarnation!
01/30/2016 at 19:03 • 0 commentsRighto.
Sometimes, we learn that another project has come along and kindof usurped our own. In my case, the app GadgetBridge has done just this for me. I still want to document the occasional chunk of stuff here, but they've got much more of the protocol down than I do.
If you want to help them, go head over to their GitHub.
I'd like to work on making GadgetBridge a little more "pluggable" -- Devices providing a means to more generically be added.
-
Android app hacking 101: Finding the hidden gems
07/02/2015 at 22:00 • 0 commentsThere 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:
- android.util.Log is a class filled with magical calls that let you drag out some debug information while your app is running.
- The logs are transferred over USB via ADB. It's a fairly straightforward process.
- Android helpfully throws anything you accidentally write to stderr to the log. This means that stderr is, by in large, a "spare" log for you to dump things to. It's often used for libraries targeting the JNI.
- Debug logs can contain sensitive things, magical things, and probably be a bad thing for your digestive health.
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:
- android class names are surrounded by L...;
- android member names are "pointed" to
- Z, V, I, C mean Bool, Void, Integer and Character, respectively.
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:
- fetch the value cn.com.smartdevices.bracelet.o.p (as a boolean) into v0
- if v0 is zero, jump to cond_0
- if p0 (the first parameter; here, a boolean) is one (not-zero) jump to cond_0
- set v0 to the string "DEBUG"
- set v1 to the string ">>...<<"
- v2 gets the integer value 0 (one nybble)
- v3 gets the integer value 0x65 (one UTF16 character)
- call a(v0,v1,v2,v3)
- (and here's cond_0): return void.
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.
-
Starters: what we know right now.
07/02/2015 at 17:29 • 1 commentWhat I know about the Xiaomi Mi Band:
- It's BTLE - uses the BT4.0 LE GATT stack.
- The BT Characteristics are... poorly documented.
There has been some work documenting the protocol but it hasn't been complete. There's a packet sniffer from Adafruit that I've looked into. Unfortunately for me though, it's all windows.
I'm going to start by attacking the application that Xiaomi put out (cutely enough, called Mi Fit). I've run it through tools (Luyten, Procyon, smali, etc) and found some... interesting snippets hidden away:
private static void g() { d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MM.: .:' `::: .:`MMMMMMMMMMM|`MMM'|MMMMMMMMMMM': .:' `::: .:'.MM"); d("MMMM. : `MMMMMMMMMM :*' MMMMMMMMMM' : .MMMM"); d("MMMMM. :: . `MMMMMMMM' :: `MMMMMMMM' . :: . .MMMMM"); d("MMMMMM. : :: ::' : :: ::' : :: ::' :: ::' : :: ::.MMMMMM"); d("MMMMMMM ;:: ;:: ;:: ;:: ;:: MMMMMMM"); d("MMMMMMM .:' `::: .:' `::: .:' `::: .:' `::: .:' `::MMMMMMM"); d("MMMMMM' : : : : : `MMMMMM"); d("MMMMM'______::____ :: . :: . :: ___._::____`MMMMM"); d("MMMMMMMMMMMMMMMMMMM`---._ :: ::' : :: ::' _.--::MMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMM::. :: .--MMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM-. ;::-MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM. .:' .MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM. .MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\\ /MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM .:ZylvanaS:. MM"); d("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); }
(for those who can't read wrapped java, that's the Batman logo.)
First, I need to attack the anti-debugging features. This includes their "vaguely" custom "logging" framework. There's a couple of settings that look useful in the long run. The next step is going to be using that logging framework (that they've so *handily* provided me!) to dump every BTLE GATT statement.
(in reality, the first step is getting the app to install under the modified package name and not get conflicts)