Lanchon / haystack

Signature Spoofing Patcher for Android
GNU General Public License v3.0
233 stars 45 forks source link

Haystack

Signature Spoofing Patcher for Android

This is a replacement for Needle (and its fork Tingle) based on the thoroughly awesome and completely ignored DexPatcher(*) instead of smali.

(*) My lawyers insisted that I disclose that I am the author of DexPatcher here.

'Allow signature spoofing' dialog 'Signature Spoofing Checker' app

(See also: Signature Spoofing Checker.)

What is wrong with Needle?

Well, nothing is wrong. It is just that things could be better.

Under the hood, Needle works by disassembling the complete framework.jar via baksmali, editing the resulting text files, then reassembling them via smali.

Unfortunately applying diff patches to smali is a very brittle affair. The bytecode output of javac and dx can completely change as a result of even minor touches to Java source code, preventing the successful application of smali patches across source changes. Also, given the low information content per line in smali, the possibility always exists that a diff patch applies cleanly but at the wrong place, causing disaster.

For these reasons, Needle's author chose not to use diff patching and instead hard-code search and replace actions in a Python script crafted specifically to apply one particular patch. This a lot of work to apply a patch that is conceptually very simple (just a method call hook that invokes an added method), and there is little guarantee that the applied changes will continue to be correct in the face of upstream changes to framework.jar, even if the patch appears to apply cleanly.

On top of this, framework.jar is a multi-dex monster and smali does not handle multi-dex by itself. So sometimes the added method causes a dex overflow during reassembly and the process fails (in Needle at least; I believe that Tingle has a workaround to handle this case).

Also, Tingle's maintainer wanted to be able to patch the framework within the phone itself rather than requiring a PC. Unfortunately he was unable to reach his goal due to smali's massive memory usage during reassembly of the framework. (Not to mention the disk usage for all the intermediate smali files, and the patch time which I estimate would be around 30 minutes.)

So what is Haystack?

Haystack is a proof-of-concept hack to showcase DexPatcher and ideally show that, although smali is great, it is not the best tool for every task.

The smali patching in Needle including the patching code and the injected smali can be replaced by this simple DexPatcher patch that I wrote in less than 5 minutes (based on the original source-level patch by microG's Marvin). The DexPatcher patch is plain Java and very readable, even for people that never encountered DexPatcher before. Try to understand what Needle actually does just by looking at its source code and you are in for a ride, even if you are versed in Python and smali. Yes, you can get an idea; but are you sure it does what you think it does?

DexPatcher understands Dalvik bytecode to a deeper level than the usual smali manipulation tools, resulting in higher assurance that patches do exactly what you expect if they apply without warnings and errors. DexPatcher can patch Android 6.0's framework.jar with a constrained Java heap of less than 50 MiB and produces no temporary files. It is coded to be efficient, with 90+% of the typical run time used up in writing the output dex files. And it natively supports multi-dex.

Ok, I want to try this thing

Requirements:

Because of feature creep, Haystack is not as simple as the patch linked above. It is broken up in layers to avoid code duplication and to give you choices of what to apply.

Haystack is based on the concepts of filesets and patches. Filesets are groups of files that are typically pulled from a device to be operated upon (patched) and later pushed back to the same device. Haystack patches are sets of DexPatcher patches applied as a single unit to one or more files of a fileset. Haystack includes bash scripts to apply binary patches that do most of the work for you:

Additionally there are scripts to build patches from source:

And finally there are scripts to bulk-build all patches:

The resulting log of the bulk-build process can be found here.

Ok, I said I wanted to try this thingy already!

Let's run through an example patching of a OnePlus 2 running Android 6.0.

Connect the phone to your PC via USB and either:

Then pull a fileset from the device to directory fs-oneplus2:

$ ./pull-fileset fs-oneplus2

>>> target directory: fs-oneplus2
>>> adb pull /system/framework/framework.jar fs-oneplus2/
[100%] /system/framework/framework.jar
>>> adb pull /system/framework/framework2.jar fs-oneplus2/
adb: error: remote object '/system/framework/framework2.jar' does not exist
>>> adb pull /system/framework/core.jar fs-oneplus2/
adb: error: remote object '/system/framework/core.jar' does not exist
>>> adb pull /system/framework/core-libart.jar fs-oneplus2/
[100%] /system/framework/core-libart.jar
>>> adb pull /system/framework/core-oj.jar fs-oneplus2/
adb: error: remote object '/system/framework/core-oj.jar' does not exist
>>> adb pull /system/framework/ext.jar fs-oneplus2/
[100%] /system/framework/ext.jar
>>> adb pull /system/framework/services.jar fs-oneplus2/
[100%] /system/framework/services.jar
>>> adb pull /system/priv-app/Settings/Settings.apk fs-oneplus2/
[100%] /system/priv-app/Settings/Settings.apk

*** pull-fileset: success

IMPORTANT: Make sure you keep a backup of the fileset you just pulled somewhere safe. You will need this backup to patch your device again or to restore it to its unpatched state. To do these things without a backup you will need to reinstall Android on your device.

Patch the fileset with the required hook for Android 6.0 (which is API level 23):

$ ls patches

bulk-patch-builder.log
sigspoof-core
sigspoof-hook-1.5-2.3
sigspoof-hook-4.0
sigspoof-hook-4.1-6.0
sigspoof-hook-7.0-9.0
sigspoof-ui-global-4.0
sigspoof-ui-global-4.1
sigspoof-ui-global-4.2-5.0
sigspoof-ui-global-5.1-6.0
sigspoof-ui-global-7.0-7.1
sigspoof-ui-global-8.0-8.1
sigspoof-ui-global-9.0

$ ./patch-fileset

usage: patch-fileset <patch-dex-dir> <fileset-api-level> <fileset-dir> [ <target-dir-to-create> | --dry-run [ <dexpatcher-option> ... ] ]

$ ./patch-fileset patches/sigspoof-hook-4.1-6.0/ 23 fs-oneplus2/

>>> target directory: fs-oneplus2__sigspoof-hook-4.1-6.0
>>> apply patch: services.jar
>>> dexpatcher --api-level 23 --verbose --output fs-oneplus2__sigspoof-hook-4.1-6.0/tmp/services.jar/patched-dex --multi-dex fs-oneplus2/services.jar patches/sigspoof-hook-4.1-6.0/services.jar.dex
info: read 'fs-oneplus2/services.jar'
info: read 'patches/sigspoof-hook-4.1-6.0/services.jar.dex'
info: write 'fs-oneplus2__sigspoof-hook-4.1-6.0/tmp/services.jar/patched-dex'
0 error(s), 0 warning(s)
>>> repack: services.jar
deleting: classes.dex
  adding: classes.dex (deflated 55%)

*** patch-fileset: success

The patched fileset was output to fs-oneplus2__sigspoof-hook-4.1-6.0 by default.

Now patch this resulting fileset again to add the core patch:

$ ./patch-fileset patches/sigspoof-core/ 23 fs-oneplus2__sigspoof-hook-4.1-6.0/

>>> target directory: fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core
>>> apply patch: services.jar
>>> dexpatcher --api-level 23 --verbose --output fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core/tmp/services.jar/patched-dex --multi-dex fs-oneplus2__sigspoof-hook-4.1-6.0/services.jar patches/sigspoof-core/services.jar.dex
info: read 'fs-oneplus2__sigspoof-hook-4.1-6.0/services.jar'
info: read 'patches/sigspoof-core/services.jar.dex'
info: write 'fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core/tmp/services.jar/patched-dex'
0 error(s), 0 warning(s)
>>> repack: services.jar
deleting: classes.dex
  adding: classes.dex (deflated 55%)

*** patch-fileset: success

Finally, add the corresponding UI patch:

$ ./patch-fileset patches/sigspoof-ui-global-5.1-6.0/ 23 fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core/

>>> target directory: fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0
>>> apply patch: services.jar
>>> dexpatcher --api-level 23 --verbose --output fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/tmp/services.jar/patched-dex --multi-dex fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core/services.jar patches/sigspoof-ui-global-5.1-6.0/services.jar.dex
info: read 'fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core/services.jar'
info: read 'patches/sigspoof-ui-global-5.1-6.0/services.jar.dex'
info: write 'fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/tmp/services.jar/patched-dex'
0 error(s), 0 warning(s)
>>> repack: services.jar
deleting: classes.dex
  adding: classes.dex (deflated 55%)
>>> apply patch: Settings.apk
>>> dexpatcher --api-level 23 --verbose --output fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/tmp/Settings.apk/patched-dex --multi-dex fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core/Settings.apk patches/sigspoof-ui-global-5.1-6.0/Settings.apk.dex
info: read 'fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core/Settings.apk'
info: read 'patches/sigspoof-ui-global-5.1-6.0/Settings.apk.dex'
info: write 'fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/tmp/Settings.apk/patched-dex'
0 error(s), 0 warning(s)
>>> repack: Settings.apk
deleting: classes.dex
  adding: classes.dex (deflated 56%)

*** patch-fileset: success

The triple-patched fileset is now in fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0.

Push it to the device:

$ ./push-fileset fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/

>>> adb root
restarting adbd as root
>>> adb remount
remount succeeded
>>> adb push fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/framework.jar /system/framework/
[100%] /system/framework/framework.jar
>>> adb push fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/ext.jar /system/framework/
[100%] /system/framework/ext.jar
>>> adb push fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/services.jar /system/framework/
[100%] /system/framework/services.jar
>>> adb push fs-oneplus2__sigspoof-hook-4.1-6.0__sigspoof-core__sigspoof-ui-global-5.1-6.0/Settings.apk /system/priv-app/Settings/
[100%] /system/priv-app/Settings/Settings.apk

*** push-fileset: success

Ok! Now reboot to Android:

$ adb reboot

Upon reboot, download and install my signature spoofing checker app to verify that spoofing works:

$ wget 'https://f-droid.org/repo/lanchon.sigspoof.checker_2.apk'

$ adb install lanchon.sigspoof.checker_2.apk

Launch the checker app and verify that signature spoofing is in fact disabled. Open up settings, go to developer settings (unlock the item if you need to), scroll to "Signature spoofing" (the last option), enable it, go back to the checker app... And profit!