Disabling Device Restrictions in an Android APK
I recently installed one of those horrible Android apps which restricts features to only whitelisted devices. The application allowed video playback on my OG Droid but not my much more capable Android tablet. This post explains my approach to patching the APK to remove the device restriction.
How devices are restricted
Device restrictions can be applied to an Android Marketplace listing or at runtime by checking values of the android.os.Build class. This post is only relevant to the latter.
The typical approach to changing the values returned by android.os.Build is to edit /system/build.prop, but that requires root and screws up Android Market. An alternate approach, and the one I’ve implemented below, is to decompile the APK to Dalvik bytecode and replace references to android.os.Build with string constants. This process results in a new APK that does not require root and will run on any device.
Prerequisites & Tools
- smali: assembler/disassembler for Android
- keytool and jarsigner: for signing Android packages
- The APK you’re planning to modify
Diassembly
unzip source.apk java -jar baksmali.jar -o ouput classes.dex
You should then have an output directory containing disassembled byte code. You can find a good write up on Android opcodes here, but we’ll only need two: sget-object and const-string.
Editting Source Files
I’m assuming the original java source used android.os.Build class to determine the device details. If the original java looked like this:
String model = android.os.Build.MODEL;
The generated byte code (assuming no obfuscation) would be:
sget-object [register], Landroid/os/Build;->MODEL:Ljava/lang/String;
To trick the code that its running on different hardware, we just need to swap this instruction with one that loads a static string:
const-string [register], "Galaxy Nexus"
Now, regardless of where code is run, whenever it references android.os.Build.MODEL, it will see the value “Galaxy Nexus”. The instruction may exist in multiple places so you’ll need to search all the byte code that was generated by baksami. Code may also reference other properties of android.os.Build which need to be updated.
The exact values to define as constants and which properties of android.os.Build to override are somewhat dependent on the application being modified. If in doubt use something reported as working in build.prop.
Packaging
Once bytecode has been updated, it needs to be assembled to DEX, and the APK needs to be repackaged.
java -jar smali-1.3.2.jar -o classes.dex ouput zip ... jarsigner ... zipalign ...
The precise steps are covered here. That’s pretty much it, uninstall the old application from your phone/tablet, and install the one you just built.
Assumptions and Limitations
- The newly created APK will be signed, but not with the publisher’s key. You’ll have to make sure to remove any previous installations, and auto-update from Market will fail.
- Most of this procedure assumes that the developer isn’t actively trying to stop us. Obfuscations are certainly possible, and some additional hacking may be required.
- This procedure only forces the application to try to execute on your device, there is no guarantee it will work as expected.
- Remember the original author’s code is copyrighted, so you’re probably not free to redistribute an APK you’ve modified in this manor.
- Be sure to give the original author feedback, and remind them that filtering functionality by device is just stupid.
Example
Here’s a rather ugly shell script I created to replace all references to android.os.Build.MODEL with constant value “Galaxy Nexus”:
#!/bin/bash
BUILD_DIR=./build
if [ ! -r "$1" -o ! -f "$1" ] ; then
echo "usage: $0 "
exit 1
fi
# create the BUILD_DIR
install -d "$BUILD_DIR/unpacked"
# upack the APK to BUILD_DIR/unpacked
unzip -o -d "$BUILD_DIR/unpacked" "$1"
# disassemble dex to BUILD_DIR/diasm
java -jar lib/baksmali-1.3.2.jar -o "$BUILD_DIR/diasm" "$BUILD_DIR/unpacked/classes.dex"
# perfom a search/replace of disasm code replacing sget-object with const-string
find "$BUILD_DIR/diasm" -type f -print -exec \
sed -r -i 's/sget-object\s+([^,]+),\s+Landroid\/os\/Build;->MODEL:Ljava\/lang\/String;/const-string \1, "Galaxy Nexus"/' {} \;
# assemble the byte code back into classes.dex
java -jar lib/smali-1.3.2.jar -o "$BUILD_DIR/unpacked/classes.dex" "$BUILD_DIR/diasm"
# remove any signatures packed with this APK, we'll self sign
rm -rf "$BUILD_DIR/unpacked/META-INF"
# zip the new APK
(cd build/unpacked && zip -r - .) > ${1%%.apk}.UNALIGNED.apk
# generate a keystore if one doesn't already exist
if [ ! -f "$BUILD_DIR/keystore" ] ; then
keytool -genkey -v -keystore "$BUILD_DIR/keystore" -alias patch \
-keyalg RSA -keysize 2048 -validity 10000 -storepass changeme -keypass changeme
fi
# sign the APK with our key
jarsigner -verbose -keystore "$BUILD_DIR/keystore" -storepass changeme ${1%%.apk}.UNALIGNED.apk patch
# align zip
zipalign -f -v 4 ${1%%.apk}.UNALIGNED.apk ${1%%.apk}.PATCHED.apk
Tags: android
