Patching Android apps: what could possibly go wrong
source link: https://www.tuicool.com/articles/7VBJnyV
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Many tools are timeless: a quality screwdriver will work in ten years just as fine as yesterday. Reverse engineering tools, on the other hand need constant maintenance as the technology we try to inspect with them is a moving target. We’ll show you how just a simple exercise in Android reverse engineering resulted in three patches in an already up-to-date tool.
I got an Android application to test that had issues with emulators and rooted devices, so I started it on a physical, unrooted device. That’s not too bad, one just has to enable the debuggable
flag and then JDB can be used to set breakpoints and examine internals.
This can be done by hand using apktool
: just disassemble the APK, edit AndroidManifest.xml
, rebuild and (re)sign the APK. Objection
makes this much easier, just use patchapk
with the --enable-debug
flag, so I did:
$ objection patchapk -s victim.apk --enable-debug ... Rebuilding the APK with the frida-gadget loaded... Rebuilding the APK may have failed. Read the following output to determine if apktool actually had an error: W: invalid resource directory name: /tmp/tmp14pitz7m.apktemp/res navigation brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [/tmp/brut<em>util</em>Jar_587774932932996925.tmp ...
It seemed that apktool
has some issues with resources, but I don’t need
to touch those, so I just added the --skip-resources
which results in resources being copied as-is without decoding and (re)encoding. Or so I thought:
$ objection patchapk -s victim.apk -d -D ... Unpacking victim.apk App already has android.permission.INTERNET Traceback (most recent call last): File "/home/dnet/.local/bin/objection", line 10, in <module> sys.exit(cli()) File "/home/dnet/.local/lib/python3.7/site-packages/click/core.py", line 764, in __call__ return self.main(*args, **kwargs) File "/home/dnet/.local/lib/python3.7/site-packages/click/core.py", line 717, in main rv = self.invoke(ctx) File "/home/dnet/.local/lib/python3.7/site-packages/click/core.py", line 1137, in invoke return _process_result(sub_ctx.command.invoke(sub_ctx)) File "/home/dnet/.local/lib/python3.7/site-packages/click/core.py", line 956, in invoke return ctx.invoke(self.callback, **ctx.params) File "/home/dnet/.local/lib/python3.7/site-packages/click/core.py", line 555, in invoke return callback(*args, **kwargs) File "/home/dnet/.local/lib/python3.7/site-packages/objection/console/cli.py", line 344, in patchapk patch_android_apk(**locals()) File "/home/dnet/.local/lib/python3.7/site-packages/objection/commands/mobile_packages.py", line 168, in patch_android_apk patcher.flip_debug_flag_to_true() File "/home/dnet/.local/lib/python3.7/site-packages/objection/utils/patchers/android.py", line 413, in flip_debug_flag_to_true xml = self._get_android_manifest() File "/home/dnet/.local/lib/python3.7/site-packages/objection/utils/patchers/android.py", line 240, in _get_android_manifest return ElementTree.parse(os.path.join(self.apk_temp_directory, 'AndroidManifest.xml')) File "/usr/lib/python3.7/xml/etree/ElementTree.py", line 1197, in parse tree.parse(source, parser) File "/usr/lib/python3.7/xml/etree/ElementTree.py", line 598, in parse self._root = parser._parse_whole(source) xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 1, column 0 Cleaning up temp files... Failed to cleanup with error: [Errno 2] No such file or directory: '/tmp/tmpesy509ma.apktemp.objection.apk'
After minutes of debugging, the real issue surfaced: skipping resource decoding also meant that AndroidManifest.xml
was left in its compiled Android binary XML format
, resulting in the above message where the built-in Python XML parser tries to read the binary format. To spare others from this experience, I wrote a tiny patch and submitted it as a pull request
so that this incompatible combination of command line parameters is detected early and a helpful message is displayed.
This still left me with a situation where I needed to find an alternate solution. As it turned out, others already
had the same problem with apktool
and the solution was there: use AAPT2 and apktool
even provided a command line switch for that called --use-aapt2
, it’s just that it couldn’t be used through Objection
so I wrote another tiny patch that made it possible to pass this through and submitted it as a pull request
. This made it possible to enable debugging, I set some breakpoints and started playing with the app… then it promptly crashed with the following backtrace
10-16 11:14:24.138 9824 9824 E AndroidRuntime: FATAL EXCEPTION: main 10-16 11:14:24.138 9824 9824 E AndroidRuntime: Process: com.example, PID: 9824 10-16 11:14:24.138 9824 9824 E AndroidRuntime: java.lang.IllegalStateException: Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' 10-16 11:14:24.138 9824 9824 E AndroidRuntime: at f.a.b.p.n(SourceFile:4) 10-16 11:14:24.138 9824 9824 E AndroidRuntime: at f.a.b.p.b(SourceFile:1) 10-16 11:14:24.138 9824 9824 E AndroidRuntime: at f.a.Z.a(SourceFile:11) 10-16 11:14:24.138 9824 9824 E AndroidRuntime: at f.a.c.a.a(SourceFile:3) 10-16 11:14:24.138 9824 9824 E AndroidRuntime: at kotlinx.coroutines.CoroutineStart.invoke(SourceFile:10)
First, I thought, this must have been an issue with apktool
, so I tried to narrow the range possible causes by using my time machine: 5 years ago I wrote a blog post about quick and dirty Android binary XML edits
so I tried to follow that by doing nothing but deleting the META-INF
directory and (re)signing the APK with no other modifications. Yet I had the just same crash as above.
Searching on the web resulted in
issues with references to the META-INF/services
directory
, and listing the contents of the original APK revealed that indeed there were many other files in the META-INF
directory besides those needed for JAR-style signature verification ( *.RSA
, *.SF
and MANIFEST.MF
). As it turned out, this is also what apktool does
:
There is no META-INF dir in resulting apk. Is this ok?
Yes. META-INF
contains apk signatures. After modifying the apk it is no longer signed. You can use -c / --copy-original
to retain these signatures. However, using -c
uses the original AndroidManifest.xml
file, so changes to it will be lost.
Since Objection uses apktool
as well, I wrote a third patch and submitted it as a pull request
. The only thing needed was a check to see if there were any files in the META-INF
directory of the original APK (carefully saved to the subdirectory original/META-INF
by apktool
) that have nothing to do with signature verification. If anything matched this filter, they got appended to the APK after apktool
processed it but before signing it with jarsigner
by Objection.
After these three patches, I could finally produce an APK that ran fine on a physical device and could be tampered with both using JDB and the Frida gadget injected by the patchapk
command of Objection. On 19th October 2019, version 1.8.0 of Objection was released
with all three of my patches included, so now you can enjoy these improvements as well just by installing Objection using pip
.
Thanks to the Objection team for developing and maintaining such a great tool for reverse engineering mobile apps and also being quick to accept and merge pull requests!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK