Compare commits

...

62 Commits
v13.3 ... v14.0

Author SHA1 Message Date
topjohnwu
44b93e7cc4 Bump Magisk Manager version 2017-09-06 23:33:46 +08:00
topjohnwu
0eb79e5acd Update scripts 2017-09-06 23:33:46 +08:00
topjohnwu
eceba26894 Make MagiskHide work without magisk.img 2017-09-06 02:25:40 +08:00
topjohnwu
0bf404f75e Bump busybox to 1.27.2 2017-09-05 21:45:13 +08:00
topjohnwu
cd8dd65a65 chcon on Android 5.0 doesn't support -h options 2017-09-05 21:44:53 +08:00
topjohnwu
50c56f8b50 Simplify flash scripts 2017-09-05 21:44:22 +08:00
topjohnwu
9e9f8ca8f3 Restore cwd 2017-09-02 23:28:17 +08:00
topjohnwu
f63af0601c Support Magisk Manager boot image file patch 2017-08-31 03:08:09 +08:00
topjohnwu
189c671ce2 Rewrite environment setup 2017-08-31 02:20:06 +08:00
topjohnwu
bb39a01361 Minor adjust magiskpolicy 2017-08-28 02:14:14 +08:00
topjohnwu
764999704a Collect va_list before fork 2017-08-27 02:17:37 +08:00
topjohnwu
ecfa4aafc1 Fix clone_attr 2017-08-26 23:19:56 +08:00
topjohnwu
a1e33c4d2f Build everything ourselves 2017-08-24 12:14:17 +08:00
topjohnwu
7f8ba74dac Add hide Magisk Manager feature 2017-08-22 03:02:08 +08:00
topjohnwu
e3df62d812 Refactor several utility functions 2017-08-20 21:36:32 +08:00
topjohnwu
1913125881 Update README.MD 2017-08-20 15:03:26 +08:00
topjohnwu
e8e58f3fed Adjust run_command 2017-08-19 19:27:51 +08:00
topjohnwu
1ca9ec384b Fix typo in cloning attribute 2017-08-19 18:59:06 +08:00
topjohnwu
9522255e3a Proper magisk manual injection support 2017-08-17 03:46:01 +08:00
topjohnwu
2a22fa694e Fix removing modules from images 2017-08-17 02:19:14 +08:00
topjohnwu
1591f5a0ca Update magiskpolicy help message 2017-08-17 00:58:02 +08:00
topjohnwu
3bc4e9a724 Finalize docs 2017-08-17 00:41:50 +08:00
topjohnwu
f7a6bb0723 Add more details and tips 2017-08-15 10:13:19 +08:00
topjohnwu
e9c17a3ef7 Add bootstages docs 2017-08-15 02:27:07 +08:00
topjohnwu
29bb5840b5 Fix duplicate in applets.md 2017-08-15 01:38:22 +08:00
topjohnwu
c9d8d860f6 Update diagram link 2017-08-15 01:32:24 +08:00
topjohnwu
cc18096882 Update docs 2017-08-15 01:28:45 +08:00
topjohnwu
15f2a664d1 Update help messages 2017-08-15 01:25:54 +08:00
topjohnwu
70b4f62ddc Update links 2017-08-14 03:54:05 +08:00
topjohnwu
e1023fdfaf Fix typo in procedure chart 2017-08-14 03:52:37 +08:00
topjohnwu
5e9648387a Merge module and repo docs 2017-08-14 03:46:23 +08:00
topjohnwu
2ba8b4df67 Complete module.md 2017-08-14 03:25:11 +08:00
topjohnwu
3a084c5d7b Add docs 2017-08-14 02:41:27 +08:00
topjohnwu
f7200e39c3 Bump MagiskManager version 2017-08-13 01:50:36 +08:00
topjohnwu
a7dfc20967 Adjust several operation orders 2017-08-13 01:15:18 +08:00
topjohnwu
6eb7c0b5d6 Add new util functions for new template version 2017-08-12 23:15:39 +08:00
topjohnwu
0b3c078aeb Fix README.MD 2017-08-12 19:17:06 +08:00
topjohnwu
750872cc37 Add boot actions 2017-08-12 17:03:31 +08:00
topjohnwu
29895ff474 Fix multiuser in user independent mode 2017-08-12 01:09:20 +08:00
topjohnwu
44adccc147 Add magisk binary mirror 2017-08-11 17:43:36 +08:00
topjohnwu
2a7e2c70b5 Extract functions 2017-08-11 14:53:24 +08:00
topjohnwu
8d431b6762 Remove IS_VENDOR flag (not needed) 2017-08-11 14:53:17 +08:00
topjohnwu
273849c0c8 Reorganize functions 2017-08-11 11:07:58 +08:00
topjohnwu
5cc14405c7 Combine image related functions 2017-08-10 17:26:37 +08:00
topjohnwu
f0cfd60e62 Fix multiuser 2017-08-07 00:32:04 +08:00
topjohnwu
d6547f0701 Fix installer options not read properly 2017-08-04 23:49:22 +08:00
topjohnwu
3b68905037 Swap buffer to fix module scripts 2017-08-03 18:07:34 +08:00
topjohnwu
eae611c54d Add b64xz to handle busybox decode/decompress in scripts 2017-08-02 18:25:24 +08:00
topjohnwu
b37bad35c2 Fuse busybox into update-binary and remove from Magisk Manager APK 2017-08-02 02:22:33 +08:00
topjohnwu
5fab15fee5 Add busybox into PATH for scripts 2017-08-01 15:34:16 +08:00
topjohnwu
10c8ea17aa Fix debug message 2017-08-01 01:24:27 +08:00
topjohnwu
7058c8ff5a Force main binaries recompile everytime 2017-07-31 23:52:50 +08:00
topjohnwu
64e85da59f Merge Android.mk 2017-07-31 23:47:50 +08:00
topjohnwu
f79fad64aa Fix several script issues 2017-07-31 23:31:40 +08:00
topjohnwu
cb70eebb08 Update scripts 2017-07-31 03:03:52 +08:00
topjohnwu
edaf8787d1 Provide external files to Magisk Manager with build script 2017-07-31 00:59:41 +08:00
topjohnwu
24164c8580 Add busybox to source 2017-07-30 20:14:12 +08:00
topjohnwu
9fca7011aa Move headers 2017-07-30 18:15:00 +08:00
topjohnwu
b13eb3fd40 Force start logd ASAP 2017-07-25 05:33:25 +08:00
topjohnwu
b7986a351c Overcome some quirks in Android Lollipop 2017-07-25 05:33:25 +08:00
topjohnwu
ce87591c62 Fix unable to get SHA1 value issue 2017-07-25 02:40:26 +08:00
topjohnwu
25c289ad3e Fix typo in uninstaller 2017-07-19 03:24:07 +08:00
65 changed files with 2373 additions and 1035 deletions

2
.gitattributes vendored
View File

@@ -8,9 +8,11 @@
# Declare files that will always have CRLF line endings on checkout.
*.cmd text eol=crlf
*.bat text eol=crlf
# Denote all files that are truly binary and should not be modified.
chromeos/** binary
*.jar binary
*.exe binary
*.apk binary
*.png binary

3
.gitmodules vendored
View File

@@ -13,3 +13,6 @@
[submodule "MagiskManager"]
path = MagiskManager
url = https://github.com/topjohnwu/MagiskManager.git
[submodule "jni/busybox"]
path = jni/busybox
url = https://github.com/topjohnwu/ndk-busybox.git

View File

@@ -4,33 +4,35 @@
#### Building has been tested on 3 major platforms:
***macOS 10.12.5***
***Ubuntu 17.04 x64***
***Windows 10 Creators Update x64***
**macOS 10.12**
**Ubuntu 17.04 x64**
**Windows 10 x64**
#### Environment Requirements
1. Python 3 **(>= 3.5)**: `python3` (or in some cases `python`) should be accessible
2. Java runtime: `java` should be accessible
3. (Unix only) C compiler: `gcc` should be accessible
4. Android SDK: `ANDROID_HOME` environment variable should point to the Android SDK folder
5. NDK: Install NDK using `sdkmanager`, or through Android SDK Manager
6. Android build-tools: Should have build-tools version matching `MagiskManager/app/build.gradle` installed
1. A 64-bit machine: `cmake` for Android is only available in 64-bit
2. Python 3.5+: to run the build script
3. Java Development Kit (JDK) 8: To compile Magisk Manager and sign zips
4. C compiler (Unix only): To build `zipadjust`. Windows users can use the pre-built `zipadjust.exe`
5. Android SDK: `ANDROID_HOME` environment variable should point to the Android SDK folder
6. Android NDK: Install NDK via `sdkmanager`, or via Android SDK Manager in Android Studio
#### Instructions and Notes
1. The python build script uses ANSI color codes to change the color of the terminal output. For Windows, this **only** works on Windows 10, as previous Windows console do not support them. If you insist to use an older Windows version, a quick Google search should provide many workarounds
2. After installing the latest Python 3 on Windows (allow the installer to add Python to PATH, or you'll have to manually set the environment), instead of calling `python3` like most Unix environment, you should call `python` in shell (cmd or Powershell both OK). You can double check the version by `python --version`
3. The build script will do several checks, it will refuse to run if the environment doesn't meet the requirements
4. For further instructions, please check the built in help message by `python3 build.py -h`
(Unix users can simply `./build.py -h`, Windows users, as mentioned, call `python` instead)
5. Each action has its own help message, access them by commands like `python3 build.py all -h`
6. To build Magisk for release (enabled through the `--release` flag, the script builds in debug mode by default), you will need to provide a Java keystore file, and place it in `release_signature.jks` to sign Magisk Manager APK for release builds. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually)
7. To properly setup the Android SDK environment, the easiest way is to use Android Studio and open Magisk Manager. If gradle sync passed, your build-tools etc. should be set properly. You can also access SDK Manager GUI within Android Studio to download NDK. Don't forget to add Android Studio's SDK path into environment variable ANDROID_HOME.
1. The easiest way to setup a working environment is to open Magisk Manager with Android Studio. The IDE will download required components and construct the environment for you. Don't forget to set `ANDROID_HOME` environment variable to the SDK path.
2. Windows users: while installing Python 3 on Windows, allow the installer to add Python to `PATH`, or you'll have to add it manually afterwards. By default, the Python executable is setup as `python`, not `python3` like most Unix environment. If you have both Python 2 and Python 3 installed, you'll have to deal with the executable name and `PATH` yourself. To double check the Python version, call `python --version`.
3. To run the script, on Windows call `python build.py [args...]`; on Unix call `python3 build.py [args...]`, or simply `./build.py [args...]`. To see the built-in help message, call the script with `-h` as an argument. The `-h` option also works for each supported actions to see the help message for the specific action.
4. By default, the script will build binaries and Magisk Manager in debug mode, which will enable verbose debugging messages. If you want to build Magisk Manager in release mode (through the flag `--release`), you will need to place your Java Keystore file in `release_signature.jks` to sign Magisk Manager's APK. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
5. The python build script uses ANSI color codes to change the color of the terminal output. For Windows, this **only** works on Windows 10, as previous Windows console do not support them. If you use an older Windows version, a quick Google search should provide many workarounds.
## License
Magisk, including all subprojects (git submodule) is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
```
Magisk, including all subprojects (git submodule) is free software:
you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -39,14 +41,17 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
## Credits
**MagiskManager** (`MagiskManager`)
* Copyright 2016-2017, John Wu (@topjohnwu)
* All contributors and translators
**MagiskSU** (`jni/su`)
* Copyright 2016-2017, John Wu (@topjohnwu)
* Copyright 2015, Pierre-Hugues Husson (phh@phh.me)
* Copyright 2013, Koushik Dutta (@koush)
@@ -54,25 +59,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
* Copyright 2008, Zinx Verituse (@zinxv)
**MagiskPolicy** (`jni/magiskpolicy`)
* Copyright 2016-2017, John Wu (@topjohnwu)
* Copyright 2015, Pierre-Hugues Husson (phh@phh.me)
* Copyright 2015, Joshua Brindle (@joshua_brindle)
**MagiskHide** (`jni/magiskhide`)
* Copyright 2016-2017, John Wu (@topjohnwu)
* Copyright 2016, Pierre-Hugues Husson (phh@phh.me) (original hidesu)
**resetprop** (`jni/resetprop`)
* Copyright 2016-2017 John Wu (@topjohnwu)
* Copyright 2016 nkk71 (nkk71x@gmail.com)
**SELinux** (`jni/selinux`)
* Makefile for NDK: Copyright 2016-2017, John Wu (@topjohnwu)
* It is maintained by many developers in SELinux project, copyright belongs to them
* Maintained by many developers in SELinux project
**ndk-compression** (`jni/ndk-compression`)
* Makefile for NDK: Copyright 2017, John Wu (@topjohnwu)
* Each library has its own copyright message in each directories
* Each library has its own copyright message in corresponding directories
**ndk-busybox** (`jni/busybox`)
* Makefile for NDK, generated by [ndk-busybox-kitchen](https://github.com/topjohnwu/ndk-busybox-kitchen): Copyright 2017, John Wu (@topjohnwu)
* Patches for NDK: Many contributors along the way, all placed in [osm0sis/android-busybox-ndk](https://github.com/osm0sis/android-busybox-ndk)
* The copyright message for busybox should be included in its own directory
**Others Not Mentioned**
* Copyright 2016-2017, John Wu (@topjohnwu)

186
build.py
View File

@@ -34,6 +34,9 @@ import multiprocessing
import zipfile
import datetime
import errno
import shutil
import lzma
import base64
def silentremove(file):
try:
@@ -45,7 +48,7 @@ def silentremove(file):
def zip_with_msg(zipfile, source, target):
if not os.path.exists(source):
error('{} does not exist! Try build \'binary\' and \'apk\' before zipping!'.format(source))
print('zip: ' + source + ' -> ' + target)
print('zip: {} -> {}'.format(source, target))
zipfile.write(source, target)
def build_all(args):
@@ -57,8 +60,11 @@ def build_all(args):
def build_binary(args):
header('* Building Magisk binaries')
# Force update Android.mk timestamp to trigger recompilation
os.utime(os.path.join('jni', 'Android.mk'))
ndk_build = os.path.join(os.environ['ANDROID_HOME'], 'ndk-bundle', 'ndk-build')
debug_flag = '' if args.release else '-DDEBUG'
debug_flag = '' if args.release else '-DMAGISK_DEBUG'
proc = subprocess.run('{} APP_CFLAGS=\"-DMAGISK_VERSION=\\\"{}\\\" -DMAGISK_VER_CODE={} {}\" -j{}'.format(
ndk_build, args.versionString, args.versionCode, debug_flag, multiprocessing.cpu_count()), shell=True)
if proc.returncode != 0:
@@ -67,12 +73,36 @@ def build_binary(args):
def build_apk(args):
header('* Building Magisk Manager')
for key in ['public.certificate.x509.pem', 'private.key.pk8']:
source = os.path.join('ziptools', key)
target = os.path.join('MagiskManager', 'app', 'src', 'main', 'assets', key)
print('cp: {} -> {}'.format(source, target))
shutil.copyfile(source, target)
for script in ['magisk_uninstaller.sh', 'util_functions.sh']:
source = os.path.join('scripts', script)
target = os.path.join('MagiskManager', 'app', 'src', 'main', 'assets', script)
print('cp: {} -> {}'.format(source, target))
shutil.copyfile(source, target)
os.chdir('MagiskManager')
# Build unhide app and place in assets
proc = subprocess.run('{} unhide::assembleRelease'.format(os.path.join('.', 'gradlew')), shell=True)
if proc.returncode != 0:
error('Build Magisk Manager failed!')
source = os.path.join('unhide', 'build', 'outputs', 'apk', 'release', 'unhide-release-unsigned.apk')
target = os.path.join('app', 'src', 'main', 'assets', 'unhide.apk')
print('cp: {} -> {}'.format(source, target))
shutil.copyfile(source, target)
print('')
if args.release:
if not os.path.exists(os.path.join('..', 'release_signature.jks')):
error('Please generate a java keystore and place it in \'release_signature.jks\'')
proc = subprocess.run('{} assembleRelease'.format(os.path.join('.', 'gradlew')), shell=True)
proc = subprocess.run('{} app::assembleRelease'.format(os.path.join('.', 'gradlew')), shell=True)
if proc.returncode != 0:
error('Build Magisk Manager failed!')
@@ -88,12 +118,21 @@ def build_apk(args):
proc = subprocess.run([
os.path.join(os.environ['ANDROID_HOME'], 'build-tools', build_tool, 'zipalign'),
'-v', '-p', '4', unsigned, aligned])
'-v', '-p', '4', unsigned, aligned], stdout=subprocess.DEVNULL)
if proc.returncode != 0:
error('Zipalign Magisk Manager failed!')
proc = subprocess.run('{} sign --ks {} --out {} {}'.format(
'java -jar {}'.format(os.path.join('../ziptools/apksigner.jar')),
# Find apksigner.jar
apksigner = ''
for root, dirs, files in os.walk(os.path.join(os.environ['ANDROID_HOME'], 'build-tools', build_tool)):
if 'apksigner.jar' in files:
apksigner = os.path.join(root, 'apksigner.jar')
break
if not apksigner:
error('Cannot find apksigner.jar in Android SDK build tools')
proc = subprocess.run('java -jar {} sign --ks {} --out {} {}'.format(
apksigner,
os.path.join('..', 'release_signature.jks'),
release, aligned), shell=True)
if proc.returncode != 0:
@@ -102,7 +141,7 @@ def build_apk(args):
silentremove(unsigned)
silentremove(aligned)
else:
proc = subprocess.run('{} assembleDebug'.format(os.path.join('.', 'gradlew')), shell=True)
proc = subprocess.run('{} app::assembleDebug'.format(os.path.join('.', 'gradlew')), shell=True)
if proc.returncode != 0:
error('Build Magisk Manager failed!')
@@ -110,20 +149,33 @@ def build_apk(args):
os.chdir('..')
def sign_adjust_zip(unsigned, output):
header('* Signing / Adjusting Zip')
# Unsigned->signed
proc = subprocess.run(['java', '-jar', os.path.join('ziptools', 'signapk.jar'),
os.path.join('ziptools', 'test.certificate.x509.pem'),
os.path.join('ziptools', 'test.key.pk8'), unsigned, 'tmp_signed.zip'])
if proc.returncode != 0:
error('First sign flashable zip failed!')
zipsigner = os.path.join('ziptools', 'zipsigner', 'build', 'libs', 'zipsigner.jar')
if os.name != 'nt' and not os.path.exists(os.path.join('ziptools', 'zipadjust')):
header('* Building zipadjust')
# Compile zipadjust
proc = subprocess.run('gcc -o ziptools/zipadjust ziptools/src/*.c -lz', shell=True)
proc = subprocess.run('gcc -o ziptools/zipadjust ziptools/zipadjust_src/*.c -lz', shell=True)
if proc.returncode != 0:
error('Build zipadjust failed!')
if not os.path.exists(zipsigner):
header('* Building zipsigner.jar')
os.chdir(os.path.join('ziptools', 'zipsigner'))
proc = subprocess.run('{} shadowJar'.format(os.path.join('.', 'gradlew')), shell=True)
if proc.returncode != 0:
error('Build zipsigner.jar failed!')
os.chdir(os.path.join('..', '..'))
header('* Signing / Adjusting Zip')
publicKey = os.path.join('ziptools', 'public.certificate.x509.pem')
privateKey = os.path.join('ziptools', 'private.key.pk8')
# Unsigned->signed
proc = subprocess.run(['java', '-jar', zipsigner,
publicKey, privateKey, unsigned, 'tmp_signed.zip'])
if proc.returncode != 0:
error('First sign flashable zip failed!')
# Adjust zip
proc = subprocess.run([os.path.join('ziptools', 'zipadjust'), 'tmp_signed.zip', 'tmp_adjusted.zip'])
@@ -131,9 +183,8 @@ def sign_adjust_zip(unsigned, output):
error('Adjust flashable zip failed!')
# Adjusted -> output
proc = subprocess.run(['java', '-jar', os.path.join('ziptools', 'minsignapk.jar'),
os.path.join('ziptools', 'test.certificate.x509.pem'),
os.path.join('ziptools', 'test.key.pk8'), 'tmp_adjusted.zip', output])
proc = subprocess.run(['java', '-jar', zipsigner,
"-m", publicKey, privateKey, 'tmp_adjusted.zip', output])
if proc.returncode != 0:
error('Second sign flashable zip failed!')
@@ -142,11 +193,46 @@ def sign_adjust_zip(unsigned, output):
silentremove('tmp_signed.zip')
silentremove('tmp_adjusted.zip')
def gen_update_binary():
update_bin = []
binary = os.path.join('libs', 'armeabi-v7a', 'b64xz')
if not os.path.exists(binary):
error('Please build \'binary\' before zipping!')
with open(binary, 'rb') as b64xz:
update_bin.append('#! /sbin/sh\nEX_ARM=')
update_bin.append(''.join("\\\\x{:02X}".format(c) for c in b64xz.read()))
binary = os.path.join('libs', 'x86', 'b64xz')
with open(binary, 'rb') as b64xz:
update_bin.append('\nEX_X86=')
update_bin.append(''.join("\\\\x{:02X}".format(c) for c in b64xz.read()))
binary = os.path.join('libs', 'armeabi-v7a', 'busybox')
with open(binary, 'rb') as busybox:
update_bin.append('\nBB_ARM=')
update_bin.append(base64.b64encode(lzma.compress(busybox.read())).decode('ascii'))
binary = os.path.join('libs', 'x86', 'busybox')
with open(binary, 'rb') as busybox:
update_bin.append('\nBB_X86=')
update_bin.append(base64.b64encode(lzma.compress(busybox.read())).decode('ascii'))
update_bin.append('\n')
with open(os.path.join('scripts', 'update_binary.sh'), 'r') as script:
update_bin.append(script.read())
return ''.join(update_bin)
def zip_main(args):
header('* Packing Flashable Zip')
with zipfile.ZipFile('tmp_unsigned.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zipf:
# Compiled Binaries
with zipfile.ZipFile('tmp_unsigned.zip', 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# META-INF
# update-binary
target = os.path.join('META-INF', 'com', 'google', 'android', 'update-binary')
print('zip: ' + target)
zipf.writestr(target, gen_update_binary())
# updater-script
source = os.path.join('scripts', 'flash_script.sh')
target = os.path.join('META-INF', 'com', 'google', 'android', 'updater-script')
zip_with_msg(zipf, source, target)
# Binaries
for lib_dir, zip_dir in [('arm64-v8a', 'arm64'), ('armeabi-v7a', 'arm'), ('x86', 'x86'), ('x86_64', 'x64')]:
for binary in ['magisk', 'magiskboot']:
source = os.path.join('libs', lib_dir, binary)
@@ -160,32 +246,6 @@ def zip_main(args):
zip_with_msg(zipf, source, target)
# Scripts
# flash_script.sh
source = os.path.join('scripts', 'flash_script.sh')
with open(source, 'r') as script:
# Add version info into flash script
update_binary = script.read().replace(
'MAGISK_VERSION_STUB', 'Magisk v{} Installer'.format(args.versionString))
target = os.path.join('META-INF', 'com', 'google', 'android', 'update-binary')
print('zip: ' + source + ' -> ' + target)
zipf.writestr(target, update_binary)
# addon.d.sh
source = os.path.join('scripts', 'addon.d.sh')
with open(source, 'r') as script:
# Add version info addon.d.sh
addond = script.read().replace(
'MAGISK_VERSION_STUB', 'Magisk v{} addon.d'.format(args.versionString))
target = os.path.join('addon.d', '99-magisk.sh')
print('zip: ' + source + ' -> ' + target)
zipf.writestr(target, addond)
# updater-script
target = os.path.join('META-INF', 'com', 'google', 'android', 'updater-script')
print('zip: ' + target)
zipf.writestr(target, '#MAGISK\n')
# init.magisk.rc
source = os.path.join('scripts', 'init.magisk.rc')
target = os.path.join('common', 'init.magisk.rc')
zip_with_msg(zipf, source, target)
# boot_patch.sh
source = os.path.join('scripts', 'boot_patch.sh')
target = os.path.join('common', 'boot_patch.sh')
@@ -195,10 +255,19 @@ def zip_main(args):
with open(source, 'r') as script:
# Add version info util_functions.sh
util_func = script.read().replace(
'MAGISK_VERSION_STUB', 'SCRIPT_VERSION={}'.format(args.versionCode))
'MAGISK_VERSION_STUB', 'MAGISK_VER="{}"\nMAGISK_VER_CODE={}'.format(args.versionString, args.versionCode))
target = os.path.join('common', 'util_functions.sh')
print('zip: ' + source + ' -> ' + target)
zipf.writestr(target, util_func)
# addon.d.sh
source = os.path.join('scripts', 'addon.d.sh')
target = os.path.join('addon.d', '99-magisk.sh')
zip_with_msg(zipf, source, target)
# init.magisk.rc
source = os.path.join('scripts', 'init.magisk.rc')
target = os.path.join('common', 'init.magisk.rc')
zip_with_msg(zipf, source, target)
# Prebuilts
for chromeos in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
source = os.path.join('chromeos', chromeos)
@@ -212,8 +281,18 @@ def zip_main(args):
def zip_uninstaller(args):
header('* Packing Uninstaller Zip')
with zipfile.ZipFile('tmp_unsigned.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zipf:
# Compiled Binaries
with zipfile.ZipFile('tmp_unsigned.zip', 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zipf:
# META-INF
# update-binary
target = os.path.join('META-INF', 'com', 'google', 'android', 'update-binary')
print('zip: ' + target)
zipf.writestr(target, gen_update_binary())
# updater-script
source = os.path.join('scripts', 'uninstaller_loader.sh')
target = os.path.join('META-INF', 'com', 'google', 'android', 'updater-script')
zip_with_msg(zipf, source, target)
# Binaries
for lib_dir, zip_dir in [('arm64-v8a', 'arm64'), ('armeabi-v7a', 'arm'), ('x86', 'x86'), ('x86_64', 'x64')]:
source = os.path.join('libs', lib_dir, 'magiskboot')
target = os.path.join(zip_dir, 'magiskboot')
@@ -223,6 +302,7 @@ def zip_uninstaller(args):
target = 'magisk_uninstaller.sh'
zip_with_msg(zipf, source, target)
# Scripts
# util_functions.sh
source = os.path.join('scripts', 'util_functions.sh')
with open(source, 'r') as script:
@@ -233,14 +313,6 @@ def zip_uninstaller(args):
print('zip: ' + source + ' -> ' + target)
zipf.writestr(target, util_func)
source = os.path.join('scripts', 'uninstaller_loader.sh')
target = os.path.join('META-INF', 'com', 'google', 'android', 'update-binary')
zip_with_msg(zipf, source, target)
target = os.path.join('META-INF', 'com', 'google', 'android', 'updater-script')
print('zip: ' + target)
zipf.writestr(target, '#MAGISK\n')
# Prebuilts
for chromeos in ['futility', 'kernel_data_key.vbprivk', 'kernel.keyblock']:
source = os.path.join('chromeos', chromeos)

27
docs/README.MD Normal file
View File

@@ -0,0 +1,27 @@
# Magisk Documentations
(Updated on 2017.8.16) ([Changelog](changelog.md))
## Table of Content
- [Introduction](#introduction)
- [Procedure Diagram](https://cdn.rawgit.com/topjohnwu/Magisk/cc1809688299f1f8b5db494a234850852712c0c9/docs/procedures.html)
- [Magisk Details](details.md)
- [Boot Stages](details.md#boot-stages)
- [Magic Mount Details](details.md#magic-mount-details)
- [Simple Mount Details](details.md#simple-mount-details)
- [Available Applets](applets.md)
- [magisk](applets.md#magisk)
- [su](applets.md#su)
- [resetprop](applets.md#resetprop)
- [magiskpolicy](applets.md#magiskpolicy)
- [magiskhide](applets.md#magiskhide)
- [Modules and Repos](module_repo.md)
- [Modules and Templates](module_repo.md#magisk-module-format)
- [Submit Modules to Repo](module_repo.md#submit-your-module-to-magisk-modules-repo)
- [Tips and Tricks](tips.md)
- [Remove Files](tips.md#remove-files)
- [Remove Folders](tips.md#remove-folders)
## Introduction
Magisk is a suite of open source tools for devices running Android version higher than 5.0 Lollipop (API 21). It establishes an environment which covers most Android aftermarket customization needs, such as root, boot scripts, SELinux patches, dm-verity/forceencrypt patches etc.. Furthermore, Magisk also provides a **Systemless Interface** to alter the system (or vendor) arbitrarily while the actual partitions stay completely intact, all of which accomplished by only patching the boot image, and adding some files into `/data`. With its systemless nature along with several other hacks, Magisk can hide modifications from device integrity verifications, one of the main target is to hide from [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html).

171
docs/applets.md Normal file
View File

@@ -0,0 +1,171 @@
## Available Applets
Magisk has a core binary which acts as a multi-call program with many applets. Here is an introduction to all available applets.
### magisk
The magisk binary itself provides a lot of utility functions when called with the name `magisk`. They are used in both Magisk installation and module installation. The entry point for `init` to invoke magisk's boot procedures are also listed here.
Command help message:
```
Usage: magisk [applet [arguments]...]
or: magisk --install [SOURCE] DIR
if SOURCE not provided, will link itself
or: magisk --list
or: magisk --createimg IMG SIZE
create ext4 image, SIZE is interpreted in MB
or: magisk --imgsize IMG
or: magisk --resizeimg IMG SIZE
SIZE is interpreted in MB
or: magisk --mountimg IMG PATH
mount IMG to PATH and prints the loop device
or: magisk --umountimg PATH LOOP
or: magisk --[boot stage]
start boot stage service
or: magisk [options]
or: applet [arguments]...
Supported boot stages:
post-fs, post-fs-data, service
Options:
-c print client version
-v print daemon version
-V print daemon version code
Supported applets:
su, resetprop, magiskpolicy, supolicy, sepolicy-inject, magiskhide
```
### su
The MagiskSU entrypoint. Call `su` to gain a root shell.
Command help message:
```
Usage: su [options] [--] [-] [LOGIN] [--] [args...]
Options:
-c, --command COMMAND pass COMMAND to the invoked shell
-h, --help display this help message and exit
-, -l, --login pretend the shell to be a login shell
-m, -p,
--preserve-environment do not change environment variables
-s, --shell SHELL use SHELL instead of the default /system/bin/sh
-u display the multiuser mode and exit
-v, --version display version number and exit
-V display version code and exit,
this is used almost exclusively by Superuser.apk
-mm, -M,
--mount-master run in the global mount namespace,
use if you need to publicly apply mounts
```
Note: even though the `-Z, --context` option is not listed above, it actually still exists. However MagiskSU will silently ignore the option since it's not needed anymore. It is still left over because some apps still request root shell with specific contexts as an option.
### resetprop
An advanced system prop manipulation utility; you can arbitrarily alter system props using this tool. Here's some background knowledge:
> System props are stored in a hybrid trie/binary tree data structure in memory; it was originally designed to only support adding nodes, and no nodes should be removed. Props can be read by many processes (e.g. via the `getprop` command); however, only the `init` process have write access to the property data. `init` provides a `property_service` to accept property update requests, so all property changes are monitored and controlled by `init` Restrictions such as **read-only** props (props that starts with `ro.`), which can only be set once and cannot be changed afterwards, is therefore implemented in `init`.
**resetprop** acts just like `init`: directly access the data structure, bypassing the whole `property_service` part. Doing so, we gain **arbitrary modification** power, including altering read-only props and deleting properties. Delete properties, which was stated *"forbidden"* in the data structure, is implemented through some tricks in the data structure.
One subtle thing to be aware of is that if we change props by directly modifying the data structure, `on property:foo=bar` triggers registered in `*.rc` scripts will not be triggered properly. This may be a good thing or a bad thing, depending on what behavior you expect. I made the default behavior to match the original setprop command, which **WILL** trigger events, but of course I provide a flag (`-n`) to disable it if you need this special behavior.
Command help message:
```
Usage: resetprop [options] [args...]
Options:
-v show verbose output
-n only modify property in memory
resetprop NAME VALUE set property entry NAME with VALUE
resetprop --file FILE load props from FILE
resetprop --delete NAME remove prop entry NAME
```
### magiskpolicy
(This tool is aliased to `supolicy` and `sepolicy-injection` for legacy reasons)
A tool to patch `sepolicy`. **magiskpolicy** also comes with built-in rules to unleash restrictions to make Magisk work properly. `sepolicy` is a compiled binary containing SELinux rules; we directly patch rules in the binary format since we don't have access to the SELinux policy source (`*.te`) files.
The Magisk daemon itself, the root shell, and all processes spawned from the daemon and root shell are all running in the context `u:r:su:s0`. This context is not only patched to be permissive, but also patched to allow any transition from `u:r:su:s0` to any domain. This was done because Samsung devices do not support permissive out of the box.
The built in patches are split to 3 parts: minimal, medium, and large. The full patch will result in a huge policy file, which might cause the `sepolicy` file unable to fit in `boot.img`.
- The minimal patch is just enough to start Magisk daemon and allow the daemon to further patch the policy during boot time (which is called **live patch**). It is done at installation and directly into `boot.img`.
- The medium patch covers most common operations, and is live patched as soon as Magisk daemon is started (blocking boot process).
- The large patch contains the full patch. Due to the concern of greatly increasing the boot time, it is designed to run in the background until it's joined in the non-blocking late_start bootstage.
What this all means is that **only late_start service mode is guaranteed to run in a fully patched environment**. If any script is not time critical, it is **highly recommended to run those scripts in late_start service mode**.
Command help message:
```
Usage: magiskpolicy [--options...] [policystatements...]
Options:
--live directly load patched policy to device
--minimal minimal patches, used for boot image patches
--load <infile> load policies from <infile>
(load from live policies if not specified)
--save <outfile> save policies to <outfile>
One policy statement should be treated as one parameter;
this means a full policy statement should be enclosed in quotes;
multiple policy statements can be provided in a single command
The statements has a format of "<action> [args...]"
Use '*' in args to represent every possible match.
Collections wrapped in curly brackets can also be used as args.
Supported policy statements:
Type 1:
"<action> source-class target-class permission-class permission"
Action: allow, deny, auditallow, auditdeny
Type 2:
"<action> source-class target-class permission-class ioctl range"
Action: allowxperm, auditallowxperm, dontauditxperm
Type 3:
"<action> class"
Action: create, permissive, enforcing
Type 4:
"attradd class attribute"
Type 5:
"typetrans source-class target-class permission-class default-class (optional: object-name)"
Notes:
- typetrans does not support the all match '*' syntax
- permission-class cannot be collections
- source-class and target-class can also be attributes
Example: allow { source1 source2 } { target1 target2 } permission-class *
Will be expanded to:
allow source1 target1 permission-class { all-permissions }
allow source1 target2 permission-class { all-permissions }
allow source2 target1 permission-class { all-permissions }
allow source2 target2 permission-class { all-permissions }
```
### magiskhide
This is the CLI to control the state of MagiskHide.
Command help message:
```
Usage: magiskhide [--options [arguments...] ]
Options:
--enable Start magiskhide
--disable Stop magiskhide
--add PROCESS Add PROCESS to the hide list
--rm PROCESS Remove PROCESS from the hide list
--ls Print out the current hide list
```

3
docs/changelog.md Normal file
View File

@@ -0,0 +1,3 @@
# Changelog
- 2017.8.16
- Initial version for Magisk v13.5+

60
docs/details.md Normal file
View File

@@ -0,0 +1,60 @@
## Boot Stages
If you are working on complicated projects, you shall need more control to the whole process. Magisk can run scripts in different boot stages, so you can fine tune exactly what you want to do. It's recommended to read this documentation along with the procedure graph.
- post-fs mode
- **This stage is BLOCKING. Boot process will NOT continue until everything is done, or 20 seconds has passed**
- Happens after most partitions are mounted, except `/data`
- Magisk will bind mount files under `/cache/magisk_mount/system` to corresponding paths
- It is only **Simple Mount**, which means it will replace existing files, but cannot add/remove files.
- post-fs-data mode
- **This stage is BLOCKING. Boot process will NOT continue until everything is done, or 60 seconds has passed**
- Happens after `/data` is ready (including the case when `/data` is encrypted)
- Happens before Zygote and system servers are started (which means pretty much everything)
- Mirrors will be mounted. These mirrors play a critical role in **Magic Mount**
- `/data/magisk.img` will be merged, trimmed, and mounted to `/magisk`
- Magisk will run scripts under `/magisk/.core/post-fs-data.d`
- Magisk will run scripts: `/magisk/$MODID/post-fs-data.sh` (placed in each module directory)
- Magisk will finally **Magisk Mount** module files
- late_start service mode
- **This stage is NON-BLOCKING, it will run in parallel with other processes**
- Happens when class late_start is started
- Put time consuming but non time critical tasks here. Boot process will be stuck if it took too long to finish your tasks in previous modes
- SELinux is guaranteed to be fully patched in this stage; **it is recommended to run most scripts in this stage**, unless your scripts require some time critical operations
- Magisk will run scripts under `/magisk/.core/service.d`
- Magisk will run scripts: `/magisk/$MODID/service.sh` (placed in each module directory)
## Magic Mount Details
### Terminology
- **Item**: A folder, file, or symbolic link
- **Leaf**: The very end of a directory structure tree, can be either a file or symbolic link
- **`$MODPATH`**: A variable to represent the path of a module folder
- **Source item**: An item under `$MODPATH/system`, for example, `$MODPATH/system/build.prop` is a source item
- **Existing item**: An item under `/system`, for example, `/system/bin/app_process` is an existing item
- **Target item**: A corresponding item of a source item. For example, the target item of `$MODPATH/system/build.prop` is `/system/build.prop`
Note: A target item does not imply it is an existing item. A target item might not exist in the actual `/system`
### Policies
- For a source leaf: if its target item is also an existing item, the existing item will be replaced with the source leaf
- For a source leaf: if its target item is not an existing item, the source leaf will be added to the path of its target item
- For any existing item that's not a target item, it will stay intact
Above is the rule of thumb. Basically it means that Magic Mount merges the files from `$MODPATH/system` into the real `/system`. A simpler way to understand is to think as it dirty copies the contents from `$MODPATH/system` into `/system`.
However, an addition rule will override the above policies:
- For a source folder containing the file `.replace`, the source folder will be treated as if it is a source leaf. That is, the items within the target folder will be completely discarded, and the target folder will be replaced with the source folder.
Directories containing a file named `.replace` will NOT be merged into the system. It will directly replace the target directory. A simpler way to understand is to think as if it wipes the target folder, and then copies the whole folder to the target path.
### Notes
- Sometimes, completely replacing a folder is inevitable. For example you want to replace `/system/priv-app/SystemUI` in your stock rom. In stock roms, system apps usually comes with pre-optimized files. If your replacement `SystemUI.apk` is deodexed (which is most likely the case), you would want to replace the whole `/system/priv-app/SystemUI` to make sure the folder only contains the modified `SystemUI.apk` and **NOT** merge with the pre-optimized files.
- If you are using the [Magisk Module Template](https://github.com/topjohnwu/magisk-module-template), you can create a list of folders you want to replace in the file `config.sh`. The installation scripts will handle the creation of `.replace` files into the listed folders for you.
- Adding non-existing target items is a relatively expensive operation. Magisk would need to do **MANY** under-the-hood tasks to achieve it. Replacing a whole folder is recommended if viable, it reduces the complexity of the construction of the mounting tree and could speed up the booting time
## Simple Mount Details
Some files require to be mounted much earlier in the boot process, currently known are bootanimation and some libs (most users won't change them). You can simply place your modified files into the corresponding path under `/cache/magisk_mount`. At boot time, Magisk will **clone all the attributes from the target file**, which includes selinux context, permission mode, owner, group. It'll then bind mount the file to the target. This means you don't need to worry about the metadatas for files placed under `/cache/magisk_mount`: copy the file to the correct place, reboot then you're done!
This mode does not feature the same complex Magic Mount implementation, it will mount a source leaf to an existing target item under `/system`.
For example, you want to replace `/system/media/bootanimation.zip`, copy your new boot animation zip to `/cache/magisk_mount/system/media/bootanimation.zip,` Magisk will mount your files in the next reboot.

76
docs/module_repo.md Normal file
View File

@@ -0,0 +1,76 @@
# Magisk Modules and Online Repo
## Magisk Module Format
The Magisk module is a folder under `magisk`, which has a structure as described below:
```
magisk
├── .
├── .
├── a_module <--- The ID of the module, should match with module.prop
│   ├── auto_mount <--- If this file exists, auto mount is enabled
│   ├── disable <--- If this file exists, the module is disabled
│   ├── module.prop <--- This files stores the indentity and properties of the module
│   ├── post-fs-data.sh <--- This script will be executed in post-fs-data
│   ├── remove <--- If this file exists, the module will be removed next reboot
│   ├── service.sh <--- This script will be executed in late_start service
│   ├── system.prop <--- This file will be loaded as system props
│   ├── system <--- If auto mount is enabled, Magisk will "Magic Mount" this folder
│   │   ├── app
│   │   ├── .
│   │   └── .
│   ├── vendor <--- Auto generated. A symlink to $MODID/system/vendor
│   ├── . <--- Any other files/folders are allowed
│   └── .
├── another_module
│   ├── .
│   └── .
├── .
├── .
```
## Magisk Module Template
The **Magisk Module Template** is hosted **[here](https://github.com/topjohnwu/magisk-module-template)**.
It is a template to create a flashable zip to install Magisk Modules. It is created to be simple to use so that anyone can create their own modules easily. The template itself contains minimal scripting for installation; most of the functions are located externally in [util_functions.sh](https://github.com/topjohnwu/Magisk/blob/master/scripts/util_functions.sh), which will be installed along with Magisk, and can be upgraded through a Magisk upgrade without the need of a template update.
The template should meet most modules' needs. Add files into `system`, which will be cloned into `/system` by Magisk after installing the module, and in addition to module files, `system.prop`, `post-fs-data.sh`, `service.sh` are also installable through this template.
Here are some files you would want to know:
- `config.sh`: A simple script used as a configuration file for the actual installation file to correctly install your module. It is the place you can select which features your module needs/disables.
- `module.prop`: This file contains your module's indentity and properties, including name and versions etc.. This file will be used to identify your module on an actual device and in the [Magisk Modules Repo](https://github.com/Magisk-Modules-Repo)
- `common/*`: These files will be installed to the module with the correspond role
- `META-INF/com/google/android/update-binary`: The actual installation script. Modify this file for advanced custom behavior
## Create a Magisk Module With The Template
1. Clone / download [this repo](https://github.com/topjohnwu/magisk-module-template)
1. Open `config.sh` and carefully read the fully documented file. Follow the instructions within the script
1. You should at least have `config.sh` and `module.prop` modified
1. Directly zip all files; the result zip file is a flashable zip that can be used in both Magisk Manager and custom recoveries
1. Please check [**Notes**](#notes) for several precautions
## Submit Your Module to Magisk Modules Repo
If you want to share your module with others, you can submit your modules to [Magisk Modules Repo](https://github.com/Magisk-Modules-Repo). You would need some basic `git` knowledge here.
1. Create a module as stated above, and test if it works properly
1. Fork [this repo](https://github.com/topjohnwu/magisk-module-template) to your account
1. Commit and push your changes to your forked repo
1. Open an issue in [topjohnwu/Magisk_Repo_Central](https://github.com/topjohnwu/Magisk_Repo_Central/issues/new) with your repo link
1. I will review your module, and once accepted, your repo should be cloned into [Magisk-Modules-Repo](https://github.com/Magisk-Modules-Repo)
1. You should receive an email to become the collaborator so you can edit the repo in the future.
#### Once your module is live on the Modules Repo, the description of your repo should be the ID of your module. Please do NOT change the description, repeat, do NOT change the description.
![repo_description.png](repo_description.png)
## Notes
- The Module Template depends on external scripts, be aware of the minimal required Magisk version of the template.
- **Windows users please check here!!** The line endings of all text files should be in the **Unix format**. Please use advanced text editors like Sublime, Atom, Notepad++ etc. to edit **ALL** text files, **NEVER** use Windows Notepad.
- In `module.prop`, `version` can be an arbitrary string, so any fancy version name (e.g. ultra-beta-v1.1.1.1) is allowed. However, `versionCode` **MUST** be an integer. The value is used for version comparison.
- Make sure your module ID **does not contain any spaces**.
## Notes For Repo Developers
- Magisk Manager monitors all repo's `master` branch. Any changes to the branch `master` will be reflected to all users immediately. If you are working on an update for a module, please work on another branch, make sure it works, then finally merge the changes back to `master`.
- Once you finished upgrading your repo, increase at least the `versionCode` in `module.prop`. Magisk Manager uses this value to compare with the local installed module to determine whether an update is availible.
- The description of your repo should be the same as your module ID. If you changed your description, Magisk Manager will fail to identify your repo, and cannot relate installed module to the online repo together.

11
docs/procedures.html Normal file

File diff suppressed because one or more lines are too long

BIN
docs/repo_description.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

6
docs/tips.md Normal file
View File

@@ -0,0 +1,6 @@
# Tips and Tricks
## Remove Files
How to efficiently remove a file systemless-ly? To actually make the file **disappear** within the folder is complicated (possible, not worth the effort). **Replacing it with a dummy file should be good enough**! Create an empty file with the same name and place it in the same path within a module, then you're basically done!
## Remove Folders
Same as mentioned above, actually making the folder to **disappear** is not worth the effort. **Replacing it with an empty folder should be good enough**! A handy trick for module developers using [Magisk Module Template](https://github.com/topjohnwu/magisk-module-template) is to add the folder you want to remove into the `REPLACE` list within `config.sh`. If your module doesn't provide a correspond folder, it will create an empty folder, and automatically add `.replace` into the empty folder so the dummy folder will properly replace the one in `/system`.

View File

@@ -1,29 +1,31 @@
LOCAL_PATH := $(call my-dir)
########################
# Binaries
########################
# magisk main binary
include $(CLEAR_VARS)
LOCAL_MODULE := magisk
LOCAL_STATIC_LIBRARIES := libsepol
LOCAL_SHARED_LIBRARIES := libsqlite libselinux
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/utils \
$(LOCAL_PATH)/daemon \
$(LOCAL_PATH)/resetprop \
$(LOCAL_PATH)/magiskpolicy \
$(LOCAL_PATH)/include \
$(LOCAL_PATH)/external \
$(LOCAL_PATH)/selinux/libsepol/include
LOCAL_SRC_FILES := \
main.c \
daemon/magisk.c \
daemon/daemon.c \
daemon/socket_trans.c \
daemon/log_monitor.c \
daemon/bootstages.c \
utils/misc.c \
utils/vector.c \
utils/xwrap.c \
utils/list.c \
utils/img.c \
daemon/daemon.c \
daemon/socket_trans.c \
daemon/log_monitor.c \
daemon/bootstages.c \
magiskhide/magiskhide.c \
magiskhide/proc_monitor.c \
magiskhide/hide_utils.c \
@@ -42,15 +44,64 @@ LOCAL_SRC_FILES := \
su/su_socket.c
LOCAL_CFLAGS := -Wno-implicit-exception-spec-mismatch
LOCAL_CPPFLAGS := -std=c++11
LOCAL_LDLIBS := -llog
include $(BUILD_EXECUTABLE)
# External shared libraries, build stub libraries for linking
# magiskboot
include $(CLEAR_VARS)
LOCAL_MODULE := magiskboot
LOCAL_STATIC_LIBRARIES := libz liblzma liblz4 libbz2
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/magiskboot \
$(LOCAL_PATH)/include \
$(LOCAL_PATH)/ndk-compression/zlib \
$(LOCAL_PATH)/ndk-compression/xz/src/liblzma/api \
$(LOCAL_PATH)/ndk-compression/lz4/lib \
$(LOCAL_PATH)/ndk-compression/bzip2
LOCAL_SRC_FILES := \
magiskboot/main.c \
magiskboot/bootimg.c \
magiskboot/hexpatch.c \
magiskboot/compress.c \
magiskboot/boot_utils.c \
magiskboot/cpio.c \
magiskboot/sha1.c \
utils/xwrap.c \
utils/vector.c \
utils/list.c
LOCAL_CFLAGS := -DZLIB_CONST
include $(BUILD_EXECUTABLE)
# 32-bit static binaries
ifneq ($(TARGET_ARCH_ABI), x86_64)
ifneq ($(TARGET_ARCH_ABI), arm64-v8a)
# b64xz
include $(CLEAR_VARS)
LOCAL_MODULE := b64xz
LOCAL_STATIC_LIBRARIES := liblzma
LOCAL_C_INCLUDES := $(LOCAL_PATH)/ndk-compression/xz/src/liblzma/api
LOCAL_SRC_FILES := b64xz.c
LOCAL_LDFLAGS := -static
include $(BUILD_EXECUTABLE)
# Busybox
include jni/busybox/Android.mk
endif
endif
########################
# Libraries
########################
# External shared libraries, include stub libselinux and libsqlite
include jni/external/Android.mk
# libsepol, static library
include jni/selinux/libsepol/Android.mk
# Build magiskboot
include jni/magiskboot/Android.mk
# Compression libraries for magiskboot
include jni/ndk-compression/zlib/Android.mk
include jni/ndk-compression/xz/src/liblzma/Android.mk
include jni/ndk-compression/lz4/lib/Android.mk
include jni/ndk-compression/bzip2/Android.mk

View File

@@ -1,4 +1,2 @@
APP_ABI := x86 x86_64 armeabi-v7a arm64-v8a
APP_PLATFORM := android-21
APP_UNIFIED_HEADERS := true
APP_CPPFLAGS += -std=c++11

83
jni/b64xz.c Normal file
View File

@@ -0,0 +1,83 @@
/* b64xz.c - Base64-XZ Extractor
*
* This program expects data from stdin. The data should be compressed with xz and
* then encoded into base64 format. What b64xz does is basically the reverse of the
* mentioned process: decode base64 to uint8_ts, decompress xz, then dump to stdout
*
* The compiled binary will be hex-dumped into update-binary
* Busybox will be xz-compressed, base64 encoded and dumped into update-binary
* This program is to recover busybox for Magisk installation environment
*
* I intentionally removed stdio. This will result in a smaller binary size because
* all I/O are handled by system calls (read/write) instead of libc wrappers
*/
#include <unistd.h>
#include <lzma.h>
#define BUFSIZE 8192
static const char trans_tbl[] =
"|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq";
static void decodeblock(uint8_t* in, uint8_t* out) {
out[0] = (uint8_t)(in[0] << 2 | in[1] >> 4);
out[1] = (uint8_t)(in[1] << 4 | in[2] >> 2);
out[2] = (uint8_t)(((in[2] << 6) & 0xc0) | in[3]);
}
static int unxz(lzma_stream *strm, void *buf, size_t size) {
lzma_ret ret = 0;
uint8_t out[BUFSIZE];
strm->next_in = buf;
strm->avail_in = size;
do {
strm->next_out = out;
strm->avail_out = sizeof(out);
ret = lzma_code(strm, LZMA_RUN);
write(STDOUT_FILENO, out, sizeof(out) - strm->avail_out);
} while (strm->avail_out == 0 && ret == LZMA_OK);
if (ret != LZMA_OK && ret != LZMA_STREAM_END)
write(STDERR_FILENO, "LZMA error!\n", 13);
return ret;
}
int main(int argc, char const* argv[]) {
if (argc > 1)
return 0;
uint8_t in[4], buf[BUFSIZE];
int len = 0, pos = 0;
char c;
// Setup lzma stream
lzma_stream strm = LZMA_STREAM_INIT;
if (lzma_auto_decoder(&strm, UINT64_MAX, 0) != LZMA_OK) {
write(STDERR_FILENO, "Unable to init lzma stream\n", 28);
return 1;
}
while (read(STDIN_FILENO, &c, sizeof(c)) == 1) {
c = ((c < 43 || c > 122) ? -1 : (trans_tbl[c - 43] == '$' ? -1 : trans_tbl[c - 43] - 62));
if (c >= 0)
in[len++] = c;
if (len < 4)
continue;
len = 0;
decodeblock(in, buf + pos);
pos += 3;
if (pos > sizeof(buf) - 3) {
// Buffer is full, unxz
if (unxz(&strm, buf, pos))
return 1;
pos = 0;
}
}
if (pos) {
if (unxz(&strm, buf, pos))
return 1;
}
lzma_end(&strm);
return 0;
}

1
jni/busybox Submodule

Submodule jni/busybox added at 90c9c4fd96

View File

@@ -24,8 +24,11 @@
static char *buf, *buf2;
static struct vector module_list;
static int seperate_vendor = 0;
#ifdef DEBUG
extern char **environ;
#ifdef MAGISK_DEBUG
static int debug_log_pid, debug_log_fd;
#endif
@@ -39,7 +42,9 @@ static int debug_log_pid, debug_log_fd;
#define IS_SKEL 0x04 /* mount from skeleton */
#define IS_MODULE 0x08 /* mount from module */
#define IS_VENDOR 0x10 /* special vendor placeholder */
#define IS_DIR(n) (n->type == DT_DIR)
#define IS_LNK(n) (n->type == DT_LNK)
#define IS_REG(n) (n->type == DT_REG)
struct node_entry {
const char *module; /* Only used when status & IS_MODULE */
@@ -50,143 +55,18 @@ struct node_entry {
struct vector *children;
};
#define IS_DIR(n) (n->type == DT_DIR)
#define IS_LNK(n) (n->type == DT_LNK)
#define IS_REG(n) (n->type == DT_REG)
/******************
* Image handling *
******************/
#define round_size(a) ((((a) / 32) + 2) * 32)
#define SOURCE_TMP "/dev/source"
#define TARGET_TMP "/dev/target"
static int merge_img(const char *source, const char *target) {
if (access(source, F_OK) == -1)
return 0;
if (access(target, F_OK) == -1) {
rename(source, target);
return 0;
}
// resize target to worst case
int s_used, s_total, t_used, t_total, n_total;
get_img_size(source, &s_used, &s_total);
get_img_size(target, &t_used, &t_total);
n_total = round_size(s_used + t_used);
if (n_total != t_total)
resize_img(target, n_total);
xmkdir(SOURCE_TMP, 0755);
xmkdir(TARGET_TMP, 0755);
char *s_loop, *t_loop;
s_loop = mount_image(source, SOURCE_TMP);
if (s_loop == NULL) return 1;
t_loop = mount_image(target, TARGET_TMP);
if (t_loop == NULL) return 1;
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(SOURCE_TMP)))
return 1;
while ((entry = xreaddir(dir))) {
if (entry->d_type == DT_DIR) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0 ||
strcmp(entry->d_name, ".core") == 0 ||
strcmp(entry->d_name, "lost+found") == 0)
continue;
// Cleanup old module if exists
snprintf(buf, PATH_MAX, "%s/%s", TARGET_TMP, entry->d_name);
if (access(buf, F_OK) == 0) {
LOGI("Upgrade module: %s\n", entry->d_name);
rm_rf(buf);
} else {
LOGI("New module: %s\n", entry->d_name);
}
}
}
closedir(dir);
clone_dir(SOURCE_TMP, TARGET_TMP);
// Unmount all loop devices
umount_image(SOURCE_TMP, s_loop);
umount_image(TARGET_TMP, t_loop);
rmdir(SOURCE_TMP);
rmdir(TARGET_TMP);
free(s_loop);
free(t_loop);
unlink(source);
return 0;
static void concat_path(struct node_entry *node) {
if (node->parent)
concat_path(node->parent);
int len = strlen(buf);
buf[len] = '/';
strcpy(buf + len + 1, node->name);
}
static void trim_img(const char *img) {
int used, total, new_size;
get_img_size(img, &used, &total);
new_size = round_size(used);
if (new_size != total)
resize_img(img, new_size);
}
/***********
* Scripts *
***********/
void exec_common_script(const char* stage) {
DIR *dir;
struct dirent *entry;
snprintf(buf, PATH_MAX, "%s/%s.d", COREDIR, stage);
if (!(dir = opendir(buf)))
return;
while ((entry = xreaddir(dir))) {
if (entry->d_type == DT_REG) {
snprintf(buf2, PATH_MAX, "%s/%s", buf, entry->d_name);
if (access(buf2, X_OK) == -1)
continue;
LOGI("%s.d: exec [%s]\n", stage, entry->d_name);
char *const command[] = { "sh", buf2, NULL };
int pid = run_command(0, NULL, "/system/bin/sh", command);
if (pid != -1)
waitpid(pid, NULL, 0);
}
}
closedir(dir);
}
void exec_module_script(const char* stage) {
char *module;
vec_for_each(&module_list, module) {
snprintf(buf, PATH_MAX, "%s/%s/%s.sh", MOUNTPOINT, module, stage);
if (access(buf, F_OK) == -1)
continue;
LOGI("%s: exec [%s.sh]\n", module, stage);
char *const command[] = { "sh", buf, NULL };
int pid = run_command(0, NULL, "/system/bin/sh", command);
if (pid != -1)
waitpid(pid, NULL, 0);
}
}
/***************
* Magic Mount *
***************/
static char *get_full_path(struct node_entry *node) {
char buffer[PATH_MAX], temp[PATH_MAX];
// Concat the paths
struct node_entry *cur = node;
strcpy(buffer, node->name);
while (cur->parent) {
strcpy(temp, buffer);
snprintf(buffer, sizeof(buffer), "%s/%s", cur->parent->name, temp);
cur = cur->parent;
}
return strdup(buffer);
buf[0] = '\0';
concat_path(node);
return strdup(buf);
}
// Free the node
@@ -235,6 +115,77 @@ static struct node_entry *insert_child(struct node_entry *p, struct node_entry *
return c;
}
/***********
* setenvs *
***********/
static void bb_setenv(struct vector *v) {
for (int i = 0; environ[i]; ++i) {
if (strncmp(environ[i], "PATH=", 5) == 0) {
snprintf(buf, PATH_MAX, "PATH=%s:%s", BBPATH, strchr(environ[i], '=') + 1);
vec_push_back(v, strdup(buf));
} else {
vec_push_back(v, strdup(environ[i]));
}
}
vec_push_back(v, NULL);
}
static void pm_setenv(struct vector *v) {
for (int i = 0; environ[i]; ++i) {
if (strncmp(environ[i], "CLASSPATH=", 10) != 0)
vec_push_back(v, strdup(environ[i]));
}
vec_push_back(v, strdup("CLASSPATH=/system/framework/pm.jar"));
vec_push_back(v, NULL);
}
/***********
* Scripts *
***********/
static void exec_common_script(const char* stage) {
DIR *dir;
struct dirent *entry;
snprintf(buf, PATH_MAX, "%s/%s.d", COREDIR, stage);
if (!(dir = opendir(buf)))
return;
while ((entry = xreaddir(dir))) {
if (entry->d_type == DT_REG) {
snprintf(buf2, PATH_MAX, "%s/%s", buf, entry->d_name);
if (access(buf2, X_OK) == -1)
continue;
LOGI("%s.d: exec [%s]\n", stage, entry->d_name);
int pid = exec_command(0, NULL, bb_setenv, "sh", buf2, NULL);
if (pid != -1)
waitpid(pid, NULL, 0);
}
}
closedir(dir);
}
static void exec_module_script(const char* stage) {
char *module;
vec_for_each(&module_list, module) {
snprintf(buf2, PATH_MAX, "%s/%s/%s.sh", MOUNTPOINT, module, stage);
snprintf(buf, PATH_MAX, "%s/%s/disable", MOUNTPOINT, module);
if (access(buf2, F_OK) == -1 || access(buf, F_OK) == 0)
continue;
LOGI("%s: exec [%s.sh]\n", module, stage);
int pid = exec_command(0, NULL, bb_setenv, "sh", buf2, NULL);
if (pid != -1)
waitpid(pid, NULL, 0);
}
}
/***************
* Magic Mount *
***************/
static void construct_tree(const char *module, struct node_entry *parent) {
DIR *dir;
struct dirent *entry;
@@ -261,11 +212,11 @@ static void construct_tree(const char *module, struct node_entry *parent) {
* 1. File in module is a symlink
* 2. Target file do not exist
* 3. Target file is a symlink, but not /system/vendor
*/
*/
int clone = 0;
if (IS_LNK(node) || access(buf, F_OK) == -1) {
clone = 1;
} else if (strcmp(parent->name, "/system") != 0 || strcmp(node->name, "vendor") != 0) {
} else if (parent->parent != NULL || strcmp(node->name, "vendor") != 0) {
struct stat s;
xstat(buf, &s);
if (S_ISLNK(s.st_mode))
@@ -296,7 +247,7 @@ static void construct_tree(const char *module, struct node_entry *parent) {
construct_tree(module, node);
}
}
closedir(dir);
cleanup:
@@ -341,7 +292,7 @@ static void clone_skeleton(struct node_entry *node) {
close(open_new(buf));
// Links will be handled later
if (child->status & IS_VENDOR) {
if (child->parent->parent == NULL && strcmp(child->name, "vendor") == 0) {
if (IS_LNK(child)) {
cp_afc(MIRRDIR "/system/vendor", "/system/vendor");
LOGI("cplink: %s -> %s\n", MIRRDIR "/system/vendor", "/system/vendor");
@@ -433,6 +384,107 @@ static void simple_mount(const char *path) {
closedir(dir);
}
/*****************
* Miscellaneous *
*****************/
static void mount_mirrors() {
LOGI("* Mounting mirrors");
struct vector mounts;
vec_init(&mounts);
file_to_vector("/proc/mounts", &mounts);
char *line;
vec_for_each(&mounts, line) {
if (strstr(line, " /system ")) {
sscanf(line, "%s", buf);
xmkdir_p(MIRRDIR "/system", 0755);
xmount(buf, MIRRDIR "/system", "ext4", MS_RDONLY, NULL);
LOGI("mount: %s -> %s\n", buf, MIRRDIR "/system");
continue;
}
if (strstr(line, " /vendor ")) {
seperate_vendor = 1;
sscanf(line, "%s", buf);
xmkdir_p(MIRRDIR "/vendor", 0755);
xmount(buf, MIRRDIR "/vendor", "ext4", MS_RDONLY, NULL);
LOGI("mount: %s -> %s\n", buf, MIRRDIR "/vendor");
continue;
}
}
vec_deep_destroy(&mounts);
if (!seperate_vendor) {
symlink(MIRRDIR "/system/vendor", MIRRDIR "/vendor");
LOGI("link: %s -> %s\n", MIRRDIR "/system/vendor", MIRRDIR "/vendor");
}
mkdir_p(MIRRDIR "/bin", 0755);
bind_mount(DATABIN, MIRRDIR "/bin");
}
static void link_busybox() {
mkdir_p(BBPATH, 0755);
exec_command_sync(MIRRDIR "/bin/busybox", "--install", "-s", BBPATH, NULL);
symlink(MIRRDIR "/bin/busybox", BBPATH "/busybox");
}
static int prepare_img() {
int new_img = 0;
if (access(MAINIMG, F_OK) == -1) {
if (create_img(MAINIMG, 64))
return 1;
new_img = 1;
}
LOGI("* Mounting " MAINIMG "\n");
// Mounting magisk image
char *magiskloop = mount_image(MAINIMG, MOUNTPOINT);
if (magiskloop == NULL)
return 1;
vec_init(&module_list);
if (new_img) {
xmkdir(COREDIR, 0755);
xmkdir(COREDIR "/post-fs-data.d", 0755);
xmkdir(COREDIR "/service.d", 0755);
xmkdir(COREDIR "/props", 0755);
} else {
DIR *dir = xopendir(MOUNTPOINT);
struct dirent *entry;
while ((entry = xreaddir(dir))) {
if (entry->d_type == DT_DIR) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0 ||
strcmp(entry->d_name, ".core") == 0 ||
strcmp(entry->d_name, "lost+found") == 0)
continue;
snprintf(buf, PATH_MAX, "%s/%s/remove", MOUNTPOINT, entry->d_name);
if (access(buf, F_OK) == 0) {
snprintf(buf, PATH_MAX, "%s/%s", MOUNTPOINT, entry->d_name);
exec_command_sync(BBPATH "/rm", "-rf", buf, NULL);
continue;
}
snprintf(buf, PATH_MAX, "%s/%s/disable", MOUNTPOINT, entry->d_name);
if (access(buf, F_OK) == 0)
continue;
vec_push_back(&module_list, strdup(entry->d_name));
}
}
closedir(dir);
// Trim image
umount_image(MOUNTPOINT, magiskloop);
free(magiskloop);
trim_img(MAINIMG);
// Remount them back :)
magiskloop = mount_image(MAINIMG, MOUNTPOINT);
free(magiskloop);
}
return 0;
}
/****************
* Entry points *
****************/
@@ -484,41 +536,37 @@ void post_fs_data(int client) {
if (!check_data())
goto unblock;
#ifdef DEBUG
#ifdef MAGISK_DEBUG
// Start debug logs in new process
debug_log_fd = xopen(DEBUG_LOG, O_WRONLY | O_CREAT | O_CLOEXEC | O_TRUNC, 0644);
char *const command[] = { "logcat", "-v", "brief", NULL };
debug_log_pid = run_command(0, &debug_log_fd, "/system/bin/logcat", command);
debug_log_pid = exec_command(0, &debug_log_fd, NULL, "logcat", "-v", "brief", NULL);
close(debug_log_fd);
#endif
LOGI("** post-fs-data mode running\n");
// uninstaller
if (access(UNINSTALLER, F_OK) == 0) {
close(open(UNBLOCKFILE, O_RDONLY | O_CREAT));
system("(BOOTMODE=true sh " UNINSTALLER ") &");
return;
}
// Allocate buffer
if (buf == NULL) buf = xmalloc(PATH_MAX);
if (buf2 == NULL) buf2 = xmalloc(PATH_MAX);
// Cache support
if (access("/cache/data_bin", F_OK) == 0) {
rm_rf(DATABIN);
rename("/cache/data_bin", DATABIN);
// Magisk binaries
char *bin_path = NULL;
if (access("/cache/data_bin", F_OK) == 0)
bin_path = "/cache/data_bin";
else if (access("/data/data/com.topjohnwu.magisk/install", F_OK) == 0)
bin_path = "/data/data/com.topjohnwu.magisk/install";
if (bin_path) {
exec_command_sync("rm", "-rf", DATABIN, NULL);
exec_command_sync("cp", "-r", bin_path, DATABIN, NULL);
exec_command_sync("rm", "-rf", bin_path, NULL);
exec_command_sync("chmod", "-R", "755", bin_path, NULL);
// Lazy.... use shell blob to match files
exec_command_sync("sh", "-c", "mv /data/magisk/stock_boot* /data", NULL);
}
// Magisk Manual Injector support
if (access("/data/local/tmp/magisk_inject", F_OK) == 0) {
rm_rf(DATABIN);
rename("/data/local/tmp/magisk_inject", DATABIN);
}
// Lazy.... use shell blob
system("mv /data/magisk/stock_boot* /data;");
// Link busybox
mount_mirrors();
link_busybox();
// Merge images
if (merge_img("/cache/magisk.img", MAINIMG)) {
@@ -530,26 +578,18 @@ void post_fs_data(int client) {
goto unblock;
}
int new_img = 0;
if (access(MAINIMG, F_OK) == -1) {
if (create_img(MAINIMG, 64))
goto unblock;
new_img = 1;
// uninstaller
if (access(UNINSTALLER, F_OK) == 0) {
close(open(UNBLOCKFILE, O_RDONLY | O_CREAT));
setenv("BOOTMODE", "true", 1);
exec_command(0, NULL, bb_setenv, "sh", UNINSTALLER, NULL);
return;
}
LOGI("* Mounting " MAINIMG "\n");
// Mounting magisk image
char *magiskloop = mount_image(MAINIMG, MOUNTPOINT);
if (magiskloop == NULL)
goto unblock;
if (new_img) {
xmkdir(COREDIR, 0755);
xmkdir(COREDIR "/post-fs-data.d", 0755);
xmkdir(COREDIR "/service.d", 0755);
xmkdir(COREDIR "/props", 0755);
}
// Trim, mount magisk.img, which will also travel through the modules
// After this, it will create the module list
if (prepare_img())
goto core_only; // Mounting fails, we can only do core only stuffs
// Run common scripts
LOGI("* Running post-fs-data.d scripts\n");
@@ -559,119 +599,51 @@ void post_fs_data(int client) {
if (access(DISABLEFILE, F_OK) == 0)
goto core_only;
DIR *dir;
struct dirent *entry;
// Execute module scripts
LOGI("* Running module post-fs-data scripts\n");
exec_module_script("post-fs-data");
char *module;
struct node_entry *sys_root, *ven_root = NULL, *child;
dir = xopendir(MOUNTPOINT);
// Create the system root entry
sys_root = xcalloc(sizeof(*sys_root), 1);
sys_root->name = strdup("/system");
sys_root->name = strdup("system");
sys_root->status = IS_INTER;
int has_modules = 0;
// Travel through each modules
vec_init(&module_list);
LOGI("* Loading modules\n");
while ((entry = xreaddir(dir))) {
if (entry->d_type == DT_DIR) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0 ||
strcmp(entry->d_name, ".core") == 0 ||
strcmp(entry->d_name, "lost+found") == 0)
continue;
snprintf(buf, PATH_MAX, "%s/%s", MOUNTPOINT, entry->d_name);
// Check whether remove
snprintf(buf2, PATH_MAX, "%s/remove", buf);
if (access(buf2, F_OK) == 0) {
rm_rf(buf);
continue;
}
// Check whether disable
snprintf(buf2, PATH_MAX, "%s/disable", buf);
if (access(buf2, F_OK) == 0)
continue;
// Add the module to list
module = strdup(entry->d_name);
vec_push_back(&module_list, module);
// Read props
snprintf(buf2, PATH_MAX, "%s/system.prop", buf);
if (access(buf2, F_OK) == 0) {
LOGI("%s: loading [system.prop]\n", module);
read_prop_file(buf2, 0);
}
// Check whether enable auto_mount
snprintf(buf2, PATH_MAX, "%s/auto_mount", buf);
if (access(buf2, F_OK) == -1)
continue;
// Double check whether the system folder exists
snprintf(buf2, PATH_MAX, "%s/system", buf);
if (access(buf2, F_OK) == -1)
continue;
// Construct structure
has_modules = 1;
LOGI("%s: constructing magic mount structure\n", module);
// If /system/vendor exists in module, create a link outside
snprintf(buf2, PATH_MAX, "%s/system/vendor", buf);
if (access(buf2, F_OK) == 0) {
snprintf(buf, PATH_MAX, "%s/%s/vendor", MOUNTPOINT, module);
unlink(buf);
symlink(buf2, buf);
}
construct_tree(module, sys_root);
vec_for_each(&module_list, module) {
// Read props
snprintf(buf, PATH_MAX, "%s/%s/system.prop", MOUNTPOINT, module);
if (access(buf, F_OK) == 0) {
LOGI("%s: loading [system.prop]\n", module);
read_prop_file(buf, 0);
}
// Check whether enable auto_mount
snprintf(buf, PATH_MAX, "%s/%s/auto_mount", MOUNTPOINT, module);
if (access(buf, F_OK) == -1)
continue;
// Double check whether the system folder exists
snprintf(buf, PATH_MAX, "%s/%s/system", MOUNTPOINT, module);
if (access(buf, F_OK) == -1)
continue;
// Construct structure
has_modules = 1;
LOGI("%s: constructing magic mount structure\n", module);
// If /system/vendor exists in module, create a link outside
snprintf(buf, PATH_MAX, "%s/%s/system/vendor", MOUNTPOINT, module);
if (access(buf, F_OK) == 0) {
snprintf(buf2, PATH_MAX, "%s/%s/vendor", MOUNTPOINT, module);
unlink(buf2);
symlink(buf, buf2);
}
construct_tree(module, sys_root);
}
closedir(dir);
// Trim image
umount_image(MOUNTPOINT, magiskloop);
free(magiskloop);
trim_img(MAINIMG);
// Remount them back :)
magiskloop = mount_image(MAINIMG, MOUNTPOINT);
free(magiskloop);
if (has_modules) {
// Mount mirrors
LOGI("* Mounting system/vendor mirrors");
int seperate_vendor = 0;
struct vector mounts;
vec_init(&mounts);
file_to_vector("/proc/mounts", &mounts);
char *line;
vec_for_each(&mounts, line) {
if (strstr(line, " /system ")) {
sscanf(line, "%s", buf);
snprintf(buf2, PATH_MAX, "%s/system", MIRRDIR);
xmkdir_p(buf2, 0755);
xmount(buf, buf2, "ext4", MS_RDONLY, NULL);
LOGI("mount: %s -> %s\n", buf, buf2);
continue;
}
if (strstr(line, " /vendor ")) {
seperate_vendor = 1;
sscanf(line, "%s", buf);
snprintf(buf2, PATH_MAX, "%s/vendor", MIRRDIR);
xmkdir_p(buf2, 0755);
xmount(buf, buf2, "ext4", MS_RDONLY, NULL);
LOGI("mount: %s -> %s\n", buf, buf2);
continue;
}
}
vec_deep_destroy(&mounts);
if (!seperate_vendor) {
snprintf(buf, PATH_MAX, "%s/system/vendor", MIRRDIR);
snprintf(buf2, PATH_MAX, "%s/vendor", MIRRDIR);
symlink(buf, buf2);
LOGI("link: %s -> %s\n", buf, buf2);
}
// Extract the vendor node out of system tree and swap with placeholder
vec_for_each(sys_root->children, child) {
if (strcmp(child->name, "vendor") == 0) {
@@ -679,10 +651,10 @@ void post_fs_data(int client) {
child = xcalloc(sizeof(*child), 1);
child->type = seperate_vendor ? DT_LNK : DT_DIR;
child->parent = ven_root->parent;
child->name = ven_root->name;
child->status = IS_VENDOR;
child->name = strdup("vendor");
child->status = 0;
// Swap!
vec_entry(sys_root->children)[_] = child;
ven_root->name = strdup("/vendor");
ven_root->parent = NULL;
break;
}
@@ -697,10 +669,6 @@ void post_fs_data(int client) {
destroy_subtree(sys_root);
if (ven_root) destroy_subtree(ven_root);
// Execute module scripts
LOGI("* Running module post-fs-data scripts\n");
exec_module_script("post-fs-data");
core_only:
// Systemless hosts
if (access(HOSTSFILE, F_OK) == 0) {
@@ -739,30 +707,30 @@ void late_start(int client) {
exec_common_script("service");
// Core only mode
if (access(DISABLEFILE, F_OK) == 0) {
setprop("ro.magisk.disable", "1");
return;
}
if (access(DISABLEFILE, F_OK) == 0)
goto core_only;
LOGI("* Running module service scripts\n");
exec_module_script("service");
core_only:
// Install Magisk Manager if exists
if (access(MANAGERAPK, F_OK) == 0) {
while (1) {
sleep(5);
char *const command[] = { "sh", "-c",
"CLASSPATH=/system/framework/pm.jar "
"/system/bin/app_process /system/bin "
"com.android.commands.pm.Pm install -r " MANAGERAPK, NULL };
int apk_res = -1, pid;
pid = run_command(1, &apk_res, "/system/bin/sh", command);
waitpid(pid, NULL, 0);
fdgets(buf, PATH_MAX, apk_res);
close(apk_res);
// Keep trying until pm is started
if (strstr(buf, "Error:") == NULL)
break;
pid = exec_command(1, &apk_res, pm_setenv,
"app_process",
"/system/bin", "com.android.commands.pm.Pm",
"install", "-r", MANAGERAPK, NULL);
if (pid != -1) {
waitpid(pid, NULL, 0);
fdgets(buf, PATH_MAX, apk_res);
close(apk_res);
// Keep trying until pm is started
if (strstr(buf, "Error:") == NULL)
break;
}
}
unlink(MANAGERAPK);
}
@@ -773,7 +741,7 @@ void late_start(int client) {
buf = buf2 = NULL;
vec_deep_destroy(&module_list);
#ifdef DEBUG
#ifdef MAGISK_DEBUG
// Stop recording the boot logcat after every boot task is done
kill(debug_log_pid, SIGTERM);
waitpid(debug_log_pid, NULL, 0);

View File

@@ -40,6 +40,7 @@ static void *request_handler(void *args) {
case STOP_MAGISKHIDE:
case ADD_HIDELIST:
case RM_HIDELIST:
case LS_HIDELIST:
case POST_FS:
case POST_FS_DATA:
case LATE_START:
@@ -65,6 +66,9 @@ static void *request_handler(void *args) {
case RM_HIDELIST:
rm_hide_list(client);
break;
case LS_HIDELIST:
ls_hide_list(client);
break;
case SUPERUSER:
su_daemon_receiver(client);
break;
@@ -96,7 +100,7 @@ static int setup_socket(struct sockaddr_un *sun) {
int fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
memset(sun, 0, sizeof(*sun));
sun->sun_family = AF_LOCAL;
memcpy(sun->sun_path, REQUESTOR_DAEMON_PATH, REQUESTOR_DAEMON_PATH_LEN);
memcpy(sun->sun_path, REQUESTOR_DAEMON_PATH, sizeof(REQUESTOR_DAEMON_PATH) - 1);
return fd;
}

View File

@@ -26,15 +26,11 @@ static void *logger_thread(void *args) {
while (1) {
// Start logcat
char *const command[] = { "logcat", "-s", "Magisk", "-v", "thread", NULL };
log_pid = run_command(0, &log_fd, "/system/bin/logcat", command);
log_pid = exec_command(0, &log_fd, NULL, "logcat", "-s", "Magisk", "-v", "thread", NULL);
if (log_pid > 0)
waitpid(log_pid, NULL, 0);
// For some reason it went here, clear buffer and restart
char *const restart[] = { "logcat", "-c", NULL };
log_pid = run_command(0, NULL, "/system/bin/logcat", restart);
if (log_pid > 0)
waitpid(log_pid, NULL, 0);
exec_command_sync("logcat", "-c", NULL);
}
// Should never be here, but well...

View File

@@ -25,34 +25,35 @@ static void usage() {
"Magisk v" xstr(MAGISK_VERSION) "(" xstr(MAGISK_VER_CODE) ") (by topjohnwu) multi-call binary\n"
"\n"
"Usage: %s [applet [arguments]...]\n"
" or: %s --install [SOURCE] <DIR> \n"
" or: %s --install [SOURCE] DIR\n"
" if SOURCE not provided, will link itself\n"
" or: %s --list\n"
" or: %s --createimg <PATH> <SIZE>\n"
" or: %s --createimg IMG SIZE\n"
" create ext4 image, SIZE is interpreted in MB\n"
" or: %s --imgsize <PATH>\n"
" or: %s --resizeimg <PATH> <SIZE>\n"
" or: %s --imgsize IMG\n"
" or: %s --resizeimg IMG SIZE\n"
" SIZE is interpreted in MB\n"
" or: %s --mountimg <IMG> <PATH>\n"
" Prints out the loop device\n"
" or: %s --umountimg <PATH> <LOOP>\n"
" or: %s --mountimg IMG PATH\n"
" mount IMG to PATH and prints the loop device\n"
" or: %s --umountimg PATH LOOP\n"
" or: %s --[boot stage]\n"
" start boot stage service\n"
" or: %s [options]\n"
" or: applet [arguments]...\n"
"\n"
"Supported boot stages:\n"
" post-fs, post-fs-data, service\n"
" post-fs, post-fs-data, service\n"
"\n"
"Options:\n"
" -c print client version\n"
" -v print daemon version\n"
" -V print daemon version code\n"
" -c print client version\n"
" -v print daemon version\n"
" -V print daemon version code\n"
"\n"
"Supported applets:\n"
, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0);
for (int i = 0; applet[i]; ++i) {
fprintf(stderr, i ? ", %s" : " %s", applet[i]);
fprintf(stderr, i ? ", %s" : " %s", applet[i]);
}
fprintf(stderr, "\n");
exit(1);

View File

@@ -15,6 +15,7 @@ typedef enum {
STOP_MAGISKHIDE,
ADD_HIDELIST,
RM_HIDELIST,
LS_HIDELIST,
SUPERUSER,
CHECK_VERSION,
CHECK_VERSION_CODE,
@@ -69,6 +70,7 @@ void launch_magiskhide(int client);
void stop_magiskhide(int client);
void add_hide_list(int client);
void rm_hide_list(int client);
void ls_hide_list(int client);
/*************
* Superuser *

View File

@@ -10,13 +10,11 @@
#include <pthread.h>
#include <android/log.h>
#define MAGISK_VER_STR xstr(MAGISK_VERSION) ":MAGISK"
#define str(a) #a
#define xstr(a) str(a)
#define MAGISK_VER_STR xstr(MAGISK_VERSION) ":MAGISK"
#define REQUESTOR_DAEMON_PATH "\0MAGISK"
#define REQUESTOR_DAEMON_PATH_LEN 7
#define LOG_TAG "Magisk"
@@ -40,6 +38,7 @@
#define MAGISKTMP "/dev/magisk"
#define MIRRDIR MAGISKTMP "/mirror"
#define DUMMDIR MAGISKTMP "/dummy"
#define BBPATH MAGISKTMP "/bin"
#define CACHEMOUNT "/cache/magisk_mount"
#define SELINUX_PATH "/sys/fs/selinux/"
@@ -60,7 +59,7 @@ static inline void do_nothing() {}
// Dummy function to depress debug message
static inline void stub(const char *fmt, ...) {}
#ifdef DEBUG
#ifdef MAGISK_DEBUG
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#else
#define LOGD(...) stub(__VA_ARGS__)

View File

@@ -0,0 +1,36 @@
/* magiskpolicy.h - Public API for policy patching
*/
#ifndef _MAGISKPOLICY_H
#define _MAGISKPOLICY_H
#include <stdlib.h>
#define ALL NULL
// policydb functions
int load_policydb(const char *filename);
int dump_policydb(const char *filename);
void destroy_policydb();
// Handy functions
int sepol_allow(char *s, char *t, char *c, char *p);
int sepol_deny(char *s, char *t, char *c, char *p);
int sepol_auditallow(char *s, char *t, char *c, char *p);
int sepol_auditdeny(char *s, char *t, char *c, char *p);
int sepol_typetrans(char *s, char *t, char *c, char *d, char *o);
int sepol_allowxperm(char *s, char *t, char *c, char *range);
int sepol_auditallowxperm(char *s, char *t, char *c, char *range);
int sepol_dontauditxperm(char *s, char *t, char *c, char *range);
int sepol_create(char *s);
int sepol_permissive(char *s);
int sepol_enforce(char *s);
int sepol_attradd(char *s, char *a);
int sepol_exists(char *source);
// Built in rules
void sepol_min_rules();
void sepol_med_rules();
void sepol_full_rules();
#endif

View File

@@ -75,30 +75,35 @@ unsigned get_radio_uid();
int check_data();
int file_to_vector(const char* filename, struct vector *v);
int vector_to_file(const char* filename, struct vector *v);
int isNum(const char *s);
ssize_t fdgets(char *buf, size_t size, int fd);
void ps(void (*func)(int));
void ps_filter_proc_name(const char *filter, void (*func)(int));
int create_links(const char *bin, const char *path);
void unlock_blocks();
void setup_sighandlers(void (*handler)(int));
int run_command(int err, int *fd, const char *path, char *const argv[]);
int exec_command(int err, int *fd, void (*setupenv)(struct vector*), const char *argv0, ...);
int exec_command_sync(char *const argv0, ...);
int mkdir_p(const char *pathname, mode_t mode);
int bind_mount(const char *from, const char *to);
int open_new(const char *filename);
int cp_afc(const char *source, const char *target);
int clone_dir(const char *source, const char *target);
int rm_rf(const char *target);
void fclone_attr(const int sourcefd, const int targetfd);
void clone_attr(const char *source, const char *target);
void get_client_cred(int fd, struct ucred *cred);
int switch_mnt_ns(int pid);
// img.c
#define round_size(a) ((((a) / 32) + 2) * 32)
#define SOURCE_TMP "/dev/source"
#define TARGET_TMP "/dev/target"
int create_img(const char *img, int size);
int get_img_size(const char *img, int *used, int *total);
int resize_img(const char *img, int size);
char *mount_image(const char *img, const char *target);
void umount_image(const char *target, const char *device);
int merge_img(const char *source, const char *target);
void trim_img(const char *img);
#endif

View File

@@ -32,4 +32,4 @@ struct vector *vec_dup(struct vector *v);
e = v ? (v)->data[(v)->size - 1] : NULL; \
for (size_t _ = (v)->size; v && _ > 0; --_, e = (v)->data[_ - 1])
#endif
#endif

View File

@@ -1,30 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := magiskboot
LOCAL_STATIC_LIBRARIES := libz liblzma liblz4 libbz2
LOCAL_C_INCLUDES := \
jni/utils \
jni/ndk-compression/zlib/ \
jni/ndk-compression/xz/src/liblzma/api/ \
jni/ndk-compression/lz4/lib/ \
jni/ndk-compression/bzip2/
LOCAL_SRC_FILES := \
main.c \
bootimg.c \
hexpatch.c \
compress.c \
boot_utils.c \
cpio.c \
sha1.c \
../utils/xwrap.c \
../utils/vector.c \
../utils/list.c
LOCAL_CFLAGS += -DZLIB_CONST
include $(BUILD_EXECUTABLE)
include jni/ndk-compression/zlib/Android.mk
include jni/ndk-compression/xz/src/liblzma/Android.mk
include jni/ndk-compression/lz4/lib/Android.mk
include jni/ndk-compression/bzip2/Android.mk

View File

@@ -470,6 +470,24 @@ static int cpio_restore(struct vector *v) {
return ret;
}
static void cpio_stocksha1(struct vector *v) {
cpio_file *f;
char sha1[41];
vec_for_each(v, f) {
if (strcmp(f->filename, "init.magisk.rc") == 0) {
for (char *pos = f->data; pos < f->data + f->filesize; pos = strchr(pos + 1, '\n') + 1) {
if (memcmp(pos, "# STOCKSHA1=", 12) == 0) {
pos += 12;
memcpy(sha1, pos, 40);
sha1[40] = '\0';
printf("%s\n", sha1);
return;
}
}
}
}
}
int cpio_commands(const char *command, int argc, char *argv[]) {
int recursive = 0, ret = 0;
command_t cmd;
@@ -480,6 +498,8 @@ int cpio_commands(const char *command, int argc, char *argv[]) {
cmd = TEST;
} else if (strcmp(command, "restore") == 0) {
cmd = RESTORE;
} else if (strcmp(command, "stocksha1") == 0) {
cmd = STOCKSHA1;
} else if (argc == 1 && strcmp(command, "backup") == 0) {
cmd = BACKUP;
} else if (argc > 0 && strcmp(command, "rm") == 0) {
@@ -510,6 +530,9 @@ int cpio_commands(const char *command, int argc, char *argv[]) {
case RESTORE:
ret = cpio_restore(&v);
break;
case STOCKSHA1:
cpio_stocksha1(&v);
return 0;
case BACKUP:
cpio_backup(argv[0], &v);
case RM:

View File

@@ -55,7 +55,8 @@ typedef enum {
TEST,
PATCH,
BACKUP,
RESTORE
RESTORE,
STOCKSHA1
} command_t;
extern char *SUP_LIST[];

View File

@@ -20,15 +20,16 @@ static void usage(char *arg0) {
" Search <hexpattern1> in <file>, and replace with <hexpattern2>\n"
"\n"
"%s --cpio-<cmd> <incpio> [flags...] [params...]\n"
" Do cpio related cmds to <incpio> (modifications are done directly)\n Supported commands:\n"
" --cpio-rm <incpio> [-r] <entry>\n Remove entry from cpio, flag -r to remove recursively\n"
" --cpio-mkdir <incpio> <mode> <entry>\n Create directory as an <entry>\n"
" --cpio-add <incpio> <mode> <entry> <infile>\n Add <infile> as an <entry>; replaces <entry> if already exists\n"
" --cpio-extract <incpio> <entry> <outfile>\n Extract <entry> to <outfile>\n"
" --cpio-test <incpio>\n Return value: 0/not patched 1/Magisk 2/Other (e.g. phh, SuperSU)\n"
" --cpio-patch <KEEPVERITY> <KEEPFORCEENCRYPT>\n Patch cpio for Magisk. KEEP**** are true/false values\n"
" --cpio-backup <incpio> <origcpio>\n Create ramdisk backups into <incpio> from <origcpio>\n"
" --cpio-restore <incpio>\n Restore ramdisk from ramdisk backup within <incpio>\n"
" Do cpio related cmds to <incpio> (modifications are done directly)\n Supported commands and params:\n"
" -rm [-r] <entry>\n Remove entry from <incpio>, flag -r to remove recursively\n"
" -mkdir <mode> <entry>\n Create directory as an <entry>\n"
" -add <mode> <entry> <infile>\n Add <infile> as an <entry>; replaces <entry> if already exists\n"
" -extract <entry> <outfile>\n Extract <entry> to <outfile>\n"
" -test \n Return value: 0/not patched 1/Magisk 2/Other (e.g. phh, SuperSU)\n"
" -patch <KEEPVERITY> <KEEPFORCEENCRYPT>\n Patch cpio for Magisk. KEEP**** are true/false values\n"
" -backup <origcpio>\n Create ramdisk backups into <incpio> from <origcpio>\n"
" -restore\n Restore ramdisk from ramdisk backup within <incpio>\n"
" -stocksha1\n Get stock boot SHA1 recorded within <incpio>\n"
"\n"
"%s --compress[=method] <infile> [outfile]\n"
" Compress <infile> with [method] (default: gzip), optionally to [outfile]\n Supported methods: "
@@ -68,8 +69,8 @@ int main(int argc, char *argv[]) {
mmap_ro(argv[2], (unsigned char **) &buf, &size);
SHA1(sha1, buf, size);
for (int i = 0; i < 20; ++i)
fprintf(stderr, "%02x", sha1[i]);
fprintf(stderr, "\n");
printf("%02x", sha1[i]);
printf("\n");
munmap(buf, size);
} else if (argc > 2 && strcmp(argv[1], "--unpack") == 0) {
unpack(argv[2]);

View File

@@ -19,7 +19,7 @@
#include "daemon.h"
static char *prop_key[] =
{ "ro.boot.verifiedbootstate", "ro.boot.flash.locked", "ro.boot.veritymode", "ro.boot.warranty_bit", "ro.warranty_bit",
{ "ro.boot.verifiedbootstate", "ro.boot.flash.locked", "ro.boot.veritymode", "ro.boot.warranty_bit", "ro.warranty_bit",
"ro.debuggable", "ro.secure", "ro.build.type", "ro.build.tags", "ro.build.selinux", NULL };
static char *prop_value[] =
@@ -72,7 +72,7 @@ void clean_magisk_props() {
void relink_sbin() {
struct stat st;
if (stat("/sbin_orig", &st) == -1 && errno == ENOENT) {
// Re-link all binaries and bind mount
// Re-link all binaries and bind mount
DIR *dir;
struct dirent *entry;
char from[PATH_MAX], to[PATH_MAX];
@@ -98,7 +98,7 @@ void relink_sbin() {
symlink(from, to);
lsetfilecon(to, "u:object_r:rootfs:s0");
}
closedir(dir);
xmount("/dev/sbin_bind", "/sbin", NULL, MS_BIND, NULL);
@@ -140,10 +140,7 @@ int add_list(char *proc) {
pthread_mutex_unlock(&hide_lock);
pthread_mutex_lock(&file_lock);
if (vector_to_file(HIDELIST, hide_list)) {
pthread_mutex_unlock(&file_lock);
return DAEMON_ERROR;
}
vector_to_file(HIDELIST, hide_list); // Do not complain if file not found
pthread_mutex_unlock(&file_lock);
return DAEMON_SUCCESS;
}
@@ -184,8 +181,7 @@ int rm_list(char *proc) {
ret = DAEMON_SUCCESS;
pthread_mutex_lock(&file_lock);
if (vector_to_file(HIDELIST, hide_list))
ret = DAEMON_ERROR;
vector_to_file(HIDELIST, hide_list); // Do not complain if file not found
pthread_mutex_unlock(&file_lock);
} else {
ret = HIDE_ITEM_NOT_EXIST;
@@ -241,3 +237,18 @@ void rm_hide_list(int client) {
write_int(client, rm_list(proc));
close(client);
}
void ls_hide_list(int client) {
err_handler = do_nothing;
if (!hideEnabled) {
write_int(client, HIDE_NOT_ENABLED);
return;
}
write_int(client, DAEMON_SUCCESS);
write_int(client, vec_size(hide_list));
char *s;
vec_for_each(hide_list, s) {
write_string(client, s);
}
close(client);
}

View File

@@ -29,13 +29,13 @@ void kill_proc(int pid) {
static void usage(char *arg0) {
fprintf(stderr,
"MagiskHide v" xstr(MAGISK_VERSION) "(" xstr(MAGISK_VER_CODE) ") (by topjohnwu) - Hide Magisk!\n\n"
"%s [--options [arguments...] ]\n\n"
"Usage: %s [--options [arguments...] ]\n\n"
"Options:\n"
" --enable: Start the magiskhide daemon\n"
" --disable: Stop the magiskhide daemon\n"
" --add <process name>: Add <process name> to the list\n"
" --rm <process name>: Remove <process name> from the list\n"
" --ls: Print out the current hide list\n"
" --enable Start magiskhide\n"
" --disable Stop magiskhide\n"
" --add PROCESS Add PROCESS to the hide list\n"
" --rm PROCESS Remove PROCESS from the hide list\n"
" --ls Print out the current hide list\n"
, arg0);
exit(1);
}
@@ -123,15 +123,7 @@ int magiskhide_main(int argc, char *argv[]) {
} else if (strcmp(argv[1], "--rm") == 0 && argc > 2) {
req = RM_HIDELIST;
} else if (strcmp(argv[1], "--ls") == 0) {
FILE *fp = fopen(HIDELIST, "r");
if (fp == NULL)
return 1;
char buffer[512];
while (fgets(buffer, sizeof(buffer), fp)) {
printf("%s", buffer);
}
fclose(fp);
return 0;
req = LS_HIDELIST;
}
int fd = connect_daemon();
write_int(fd, req);
@@ -139,28 +131,38 @@ int magiskhide_main(int argc, char *argv[]) {
write_string(fd, argv[2]);
}
daemon_response code = read_int(fd);
close(fd);
switch (code) {
case DAEMON_ERROR:
fprintf(stderr, "Error occured in daemon...\n");
break;
return code;
case DAEMON_SUCCESS:
break;
case ROOT_REQUIRED:
fprintf(stderr, "Root is required for this operation\n");
break;
return code;
case HIDE_NOT_ENABLED:
fprintf(stderr, "Magisk hide is not enabled yet\n");
break;
return code;
case HIDE_IS_ENABLED:
fprintf(stderr, "Magisk hide is already enabled\n");
break;
return code;
case HIDE_ITEM_EXIST:
fprintf(stderr, "Process [%s] already exists in hide list\n", argv[2]);
break;
return code;
case HIDE_ITEM_NOT_EXIST:
fprintf(stderr, "Process [%s] does not exist in hide list\n", argv[2]);
break;
return code;
}
return code;
if (req == LS_HIDELIST) {
int argc = read_int(fd);
for (int i = 0; i < argc; ++i) {
char *s = read_string(fd);
printf("%s\n", s);
free(s);
}
}
close(fd);
return 0;
}

View File

@@ -1,5 +1,5 @@
/* proc_monitor.c - Monitor am_proc_start events and unmount
*
*
* We monitor the logcat am_proc_start events. When a target starts up,
* we pause it ASAP, and fork a new process to join its mount namespace
* and do all the unmounting/mocking
@@ -181,15 +181,11 @@ void proc_monitor() {
while (1) {
// Clear previous logcat buffer
char *const restart[] = { "logcat", "-b", "events", "-c", NULL };
log_pid = run_command(0, NULL, "/system/bin/logcat", restart);
if (log_pid > 0)
waitpid(log_pid, NULL, 0);
exec_command_sync("logcat", "-b", "events", "-c", NULL);
// Monitor am_proc_start
char *const command[] = { "logcat", "-b", "events", "-v", "raw", "-s", "am_proc_start", NULL };
log_fd = -1;
log_pid = run_command(0, &log_fd, "/system/bin/logcat", command);
log_pid = exec_command(0, &log_fd, NULL, "logcat", "-b", "events", "-v", "raw", "-s", "am_proc_start", NULL);
if (log_pid < 0) continue;
if (kill(log_pid, 0)) continue;

View File

@@ -1,10 +1,10 @@
/* resetprop.cpp - Manipulate any system props
*
*
* Copyright 2016 nkk71 <nkk71x@gmail.com>
* Copyright 2016 topjohnwu <topjohnwu@gmail.com>
*
*
* Info:
*
*
* all changes are in
*
* bionic/libc/bionic/system_properties.cpp
@@ -26,23 +26,23 @@
*
* static prop_area* map_fd_ro(const int fd)
* we dont want this read only so change: 'PROT_READ' to 'PROT_READ | PROT_WRITE'
*
*
*
* Copy the code of prop_info *prop_area::find_property, and modify to delete props
* const prop_info *prop_area::find_property_and_del(prop_bt *const trie, const char *name)
* {
* ...
* ...
* ... Do not alloc a new prop_bt here, remove all code involve alloc_if_needed
* ...
*
*
* if (prop_offset != 0) {
* atomic_store_explicit(&current->prop, 0, memory_order_release); // Add this line to nullify the prop entry
* return to_prop_info(&current->prop);
* } else {
*
*
* ....
* }
*
*
*
* by patching just those functions directly, all other functions should be ok
* as is.
@@ -98,14 +98,16 @@ static int usage(char* arg0) {
fprintf(stderr,
"resetprop v" xstr(MAGISK_VERSION) "(" xstr(MAGISK_VER_CODE) ") (by topjohnwu & nkk71) - System Props Modification Tool\n\n"
"Usage: %s [options] [args...]\n"
"%s <name> <value>: Set property entry <name> with <value>\n"
"%s --file <prop file>: Load props from <prop file>\n"
"%s --delete <name>: Remove prop entry <name>\n"
"\n"
"Options:\n"
" -v verbose output\n"
" -n don't trigger events when changing props\n"
" if used with deleteprop determines whether remove persist prop file\n"
" -v show verbose output\n"
" -n only modify property in memory\n"
"\n"
"%s NAME VALUE set property entry NAME with VALUE\n"
"%s --file FILE load props from FILE\n"
"%s --delete NAME remove prop entry NAME\n"
"\n"
, arg0, arg0, arg0, arg0);
return 1;
}
@@ -164,7 +166,7 @@ int setprop(const char *name, const char *value) {
int setprop2(const char *name, const char *value, const int trigger) {
if (init_resetprop()) return -1;
int ret;
prop_info *pi = (prop_info*) __system_property_find2(name);
if (pi != NULL) {
if (trigger) {
@@ -250,7 +252,7 @@ int read_prop_file(const char* filename, const int trigger) {
int resetprop_main(int argc, char *argv[]) {
int del = 0, file = 0, trigger = 1;
int exp_arg = 2;
char *name, *value, *filename;

2
jni/su

Submodule jni/su updated: 86a113a536...c66632227b

View File

@@ -14,8 +14,7 @@ static int e2fsck(const char *img) {
// Check and repair ext4 image
char buffer[128];
int pid, fd = -1;
char *const command[] = { "e2fsck", "-yf", (char *) img, NULL };
pid = run_command(1, &fd, "/system/bin/e2fsck", command);
pid = exec_command(1, &fd, NULL, "e2fsck", "-yf", img, NULL);
if (pid < 0)
return 1;
while (fdgets(buffer, sizeof(buffer), fd))
@@ -56,19 +55,17 @@ int create_img(const char *img, int size) {
char file_contexts[] = "/magisk(/.*)? u:object_r:system_file:s0\n";
// If not root, attempt to create in current diretory
char *filename = getuid() == UID_ROOT ? "/dev/file_contexts_image" : "file_contexts_image";
int pid, status, fd = xopen(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
int ret, fd = xopen(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
xwrite(fd, file_contexts, sizeof(file_contexts));
close(fd);
char buffer[16];
snprintf(buffer, sizeof(buffer), "%dM", size);
char *const command[] = { "make_ext4fs", "-l", buffer, "-a", "/magisk", "-S", filename, (char *) img, NULL };
pid = run_command(0, NULL, "/system/bin/make_ext4fs", command);
if (pid < 0)
ret = exec_command_sync("make_ext4fs", "-l", buffer, "-a", "/magisk", "-S", filename, img, NULL);
if (ret < 0)
return 1;
waitpid(pid, &status, 0);
unlink(filename);
return WEXITSTATUS(status);
return ret;
}
int get_img_size(const char *img, int *used, int *total) {
@@ -76,8 +73,7 @@ int get_img_size(const char *img, int *used, int *total) {
return 1;
char buffer[PATH_MAX];
int pid, fd = -1, status = 1;
char *const command[] = { "e2fsck", "-n", (char *) img, NULL };
pid = run_command(1, &fd, "/system/bin/e2fsck", command);
pid = exec_command(1, &fd, NULL, "e2fsck", "-n", img, NULL);
if (pid < 0)
return 1;
while (fdgets(buffer, sizeof(buffer), fd)) {
@@ -109,8 +105,7 @@ int resize_img(const char *img, int size) {
char buffer[128];
int pid, status, fd = -1;
snprintf(buffer, sizeof(buffer), "%dM", size);
char *const command[] = { "resize2fs", (char *) img, buffer, NULL };
pid = run_command(1, &fd, "/system/bin/resize2fs", command);
pid = exec_command(1, &fd, NULL, "resize2fs", img, buffer, NULL);
if (pid < 0)
return 1;
while (fdgets(buffer, sizeof(buffer), fd))
@@ -146,3 +141,72 @@ void umount_image(const char *target, const char *device) {
ioctl(fd, LOOP_CLR_FD);
close(fd);
}
int merge_img(const char *source, const char *target) {
if (access(source, F_OK) == -1)
return 0;
if (access(target, F_OK) == -1) {
rename(source, target);
return 0;
}
char buffer[PATH_MAX];
// resize target to worst case
int s_used, s_total, t_used, t_total, n_total;
get_img_size(source, &s_used, &s_total);
get_img_size(target, &t_used, &t_total);
n_total = round_size(s_used + t_used);
if (n_total != t_total)
resize_img(target, n_total);
xmkdir(SOURCE_TMP, 0755);
xmkdir(TARGET_TMP, 0755);
char *s_loop, *t_loop;
s_loop = mount_image(source, SOURCE_TMP);
if (s_loop == NULL) return 1;
t_loop = mount_image(target, TARGET_TMP);
if (t_loop == NULL) return 1;
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(SOURCE_TMP)))
return 1;
while ((entry = xreaddir(dir))) {
if (entry->d_type == DT_DIR) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0 ||
strcmp(entry->d_name, ".core") == 0 ||
strcmp(entry->d_name, "lost+found") == 0)
continue;
// Cleanup old module if exists
snprintf(buffer, sizeof(buffer), "%s/%s", TARGET_TMP, entry->d_name);
if (access(buffer, F_OK) == 0) {
LOGI("Upgrade module: %s\n", entry->d_name);
exec_command_sync(BBPATH "/rm", "-rf", buffer, NULL);
} else {
LOGI("New module: %s\n", entry->d_name);
}
}
}
closedir(dir);
cp_afc(SOURCE_TMP, TARGET_TMP);
// Unmount all loop devices
umount_image(SOURCE_TMP, s_loop);
umount_image(TARGET_TMP, t_loop);
rmdir(SOURCE_TMP);
rmdir(TARGET_TMP);
free(s_loop);
free(t_loop);
unlink(source);
return 0;
}
void trim_img(const char *img) {
int used, total, new_size;
get_img_size(img, &used, &total);
new_size = round_size(used);
if (new_size != total)
resize_img(img, new_size);
}

View File

@@ -5,6 +5,7 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
@@ -69,7 +70,7 @@ int file_to_vector(const char* filename, struct vector *v) {
size_t len = 0;
ssize_t read;
FILE *fp = fopen(filename, "r");
FILE *fp = xfopen(filename, "r");
if (fp == NULL)
return 1;
@@ -97,7 +98,7 @@ int vector_to_file(const char *filename, struct vector *v) {
}
/* Check if the string only contains digits */
int isNum(const char *s) {
static int is_num(const char *s) {
int len = strlen(s);
for (int i = 0; i < len; ++i)
if (s[i] < '0' || s[i] > '9')
@@ -129,11 +130,11 @@ void ps(void (*func)(int)) {
while ((entry = xreaddir(dir))) {
if (entry->d_type == DT_DIR) {
if (isNum(entry->d_name))
if (is_num(entry->d_name))
func(atoi(entry->d_name));
}
}
closedir(dir);
}
@@ -183,7 +184,7 @@ int create_links(const char *bin, const char *path) {
#define DEV_BLOCK "/dev/block"
void unlock_blocks() {
void unlock_blocks() {
char path[PATH_MAX];
DIR *dir;
struct dirent *entry;
@@ -223,8 +224,9 @@ void setup_sighandlers(void (*handler)(int)) {
fd == NULL -> Ignore output
*fd < 0 -> Open pipe and set *fd to the read end
*fd >= 0 -> STDOUT (or STDERR) will be redirected to *fd
*cb -> A callback function which runs after fork
*/
int run_command(int err, int *fd, const char *path, char *const argv[]) {
static int v_exec_command(int err, int *fd, void (*setupenv)(struct vector*), const char *argv0, va_list argv) {
int pipefd[2], writeEnd = -1;
if (fd) {
@@ -237,6 +239,26 @@ int run_command(int err, int *fd, const char *path, char *const argv[]) {
}
}
// Collect va_list into vector
struct vector args;
vec_init(&args);
vec_push_back(&args, strdup(argv0));
for (void *arg = va_arg(argv, void*); arg; arg = va_arg(argv, void*))
vec_push_back(&args, strdup(arg));
vec_push_back(&args, NULL);
// Setup environment
char *const *envp;
struct vector env;
vec_init(&env);
if (setupenv) {
setupenv(&env);
envp = (char **) vec_entry(&env);
} else {
extern char **environ;
envp = environ;
}
int pid = fork();
if (pid != 0) {
if (fd && *fd < 0) {
@@ -244,19 +266,44 @@ int run_command(int err, int *fd, const char *path, char *const argv[]) {
*fd = pipefd[0];
close(pipefd[1]);
}
vec_deep_destroy(&args);
vec_deep_destroy(&env);
return pid;
}
// Don't return to the daemon if anything goes wrong
err_handler = exit_proc;
if (fd) {
xdup2(writeEnd, STDOUT_FILENO);
if (err) xdup2(writeEnd, STDERR_FILENO);
}
execv(path, argv);
PLOGE("execv");
execvpe(argv0, (char **) vec_entry(&args), envp);
PLOGE("execvpe");
return -1;
}
int exec_command_sync(char *const argv0, ...) {
va_list argv;
va_start(argv, argv0);
int pid, status;
pid = v_exec_command(0, NULL, NULL, argv0, argv);
va_end(argv);
if (pid < 0)
return pid;
waitpid(pid, &status, 0);
return WEXITSTATUS(status);
}
int exec_command(int err, int *fd, void (*setupenv)(struct vector*), const char *argv0, ...) {
va_list argv;
va_start(argv, argv0);
int pid = v_exec_command(err, fd, setupenv, argv0, argv);
va_end(argv);
return pid;
}
int mkdir_p(const char *pathname, mode_t mode) {
char *path = strdup(pathname), *p;
errno = 0;
@@ -280,7 +327,7 @@ int mkdir_p(const char *pathname, mode_t mode) {
int bind_mount(const char *from, const char *to) {
int ret = xmount(from, to, NULL, MS_BIND, NULL);
#ifdef DEBUG
#ifdef MAGISK_DEBUG
LOGD("bind_mount: %s -> %s\n", from, to);
#else
LOGI("bind_mount: %s\n", to);
@@ -292,82 +339,60 @@ int open_new(const char *filename) {
return xopen(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
// file/link -> file/link only!!
int cp_afc(const char *source, const char *target) {
struct stat buf;
xlstat(source, &buf);
unlink(target);
char *con;
if (S_ISREG(buf.st_mode)) {
int sfd, tfd;
sfd = xopen(source, O_RDONLY);
tfd = xopen(target, O_WRONLY | O_CREAT | O_TRUNC);
xsendfile(tfd, sfd, NULL, buf.st_size);
fclone_attr(sfd, tfd);
close(sfd);
close(tfd);
} else if (S_ISLNK(buf.st_mode)) {
char buffer[PATH_MAX];
xreadlink(source, buffer, sizeof(buffer));
xsymlink(buffer, target);
lchown(target, buf.st_uid, buf.st_gid);
lgetfilecon(source, &con);
lsetfilecon(target, con);
free(con);
} else {
return 1;
}
return 0;
}
int clone_dir(const char *source, const char *target) {
DIR *dir;
struct dirent *entry;
char *s_path, *t_path;
if (S_ISDIR(buf.st_mode)) {
DIR *dir;
struct dirent *entry;
char *s_path, *t_path;
if (!(dir = xopendir(source)))
return 1;
if (!(dir = xopendir(source)))
return 1;
s_path = xmalloc(PATH_MAX);
t_path = xmalloc(PATH_MAX);
s_path = xmalloc(PATH_MAX);
t_path = xmalloc(PATH_MAX);
mkdir_p(target, 0755);
clone_attr(source, target);
mkdir_p(target, 0755);
clone_attr(source, target);
while ((entry = xreaddir(dir))) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(s_path, PATH_MAX, "%s/%s", source, entry->d_name);
snprintf(t_path, PATH_MAX, "%s/%s", target, entry->d_name);
switch (entry->d_type) {
case DT_DIR:
clone_dir(s_path, t_path);
break;
case DT_REG:
case DT_LNK:
while ((entry = xreaddir(dir))) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(s_path, PATH_MAX, "%s/%s", source, entry->d_name);
snprintf(t_path, PATH_MAX, "%s/%s", target, entry->d_name);
cp_afc(s_path, t_path);
break;
}
free(s_path);
free(t_path);
closedir(dir);
} else{
unlink(target);
if (S_ISREG(buf.st_mode)) {
int sfd, tfd;
sfd = xopen(source, O_RDONLY);
tfd = xopen(target, O_WRONLY | O_CREAT | O_TRUNC);
xsendfile(tfd, sfd, NULL, buf.st_size);
fclone_attr(sfd, tfd);
close(sfd);
close(tfd);
} else if (S_ISLNK(buf.st_mode)) {
char buffer[PATH_MAX];
xreadlink(source, buffer, sizeof(buffer));
xsymlink(buffer, target);
clone_attr(source, target);
} else {
return 1;
}
}
free(s_path);
free(t_path);
closedir(dir);
return 0;
}
int rm_rf(const char *target) {
if (access(target, F_OK) == -1)
return 0;
// Use external rm command, saves a lot of headache and issues
char command[PATH_MAX];
snprintf(command, sizeof(command), "rm -rf %s", target);
return system(command);
}
void clone_attr(const char *source, const char *target) {
struct stat buf;
xstat(source, &buf);
lstat(target, &buf);
chmod(target, buf.st_mode & 0777);
chown(target, buf.st_uid, buf.st_gid);
char *con;
@@ -383,7 +408,7 @@ void fclone_attr(const int sourcefd, const int targetfd) {
fchown(targetfd, buf.st_uid, buf.st_gid);
char *con;
fgetfilecon(sourcefd, &con);
fsetfilecon(sourcefd, con);
fsetfilecon(targetfd, con);
free(con);
}

View File

@@ -1,11 +1,11 @@
#!/sbin/sh
##########################################################################################
#
#
# Magisk Survival Script for ROMs with addon.d support
# by topjohnwu
#
#
# Inspired by 99-flashafterupdate.sh of osm0sis @ xda-developers
#
#
##########################################################################################
. /tmp/backuptool.functions
@@ -34,7 +34,7 @@ main() {
[ -f /system/build.prop ] || abort "! /system could not be mounted!"
ui_print "************************"
ui_print "* MAGISK_VERSION_STUB"
ui_print "* Magisk v$MAGISK_VER addon.d"
ui_print "************************"
api_level_arch_detect
@@ -91,7 +91,7 @@ case "$1" in
post-restore)
# Get the FD for ui_print
OUTFD=`ps | grep -v grep | grep -oE "update(.*)" | cut -d" " -f3`
# Run the main function in a parallel subshell
# Run the main function in a parallel subshell
(main) &
;;
esac

View File

@@ -3,11 +3,11 @@
#
# Magisk Boot Image Patcher
# by topjohnwu
#
#
# This script should be placed in a directory with the following files:
#
#
# File name type Description
#
#
# boot_patch.sh script A script to patch boot. Expect path to boot image as parameter.
# (this file) The script will use binaries and files in its same directory
# to complete the patching process
@@ -19,7 +19,7 @@
# All magisk entrypoints are defined here
# chromeos folder This folder should store all the utilities and keys to sign
# (optional) a chromeos device, used in the tablet Pixel C
#
#
# If the script is not running as root, then the input boot image should be a stock image
# or have a backup included in ramdisk internally, since we cannot access the stock boot
# image placed under /data we've created when previously installing
@@ -49,12 +49,15 @@ abort_wrap() {
# Pure bash dirname implementation
dirname_wrap() {
if echo $1 | grep "/" >/dev/null 2>&1; then
RES=${1%/*}
[ -z $RES ] && echo "/" || echo $RES
else
echo "."
fi
case "$1" in
*/*)
dir=${1%/*}
[ -z $dir ] && echo "/" || echo $dir
;;
*)
echo "."
;;
esac
}
# Pure bash basename implementation
@@ -91,35 +94,37 @@ cpio_mkdir() {
# Initialization
##########################################################################################
CWD=`pwd`
[ -z $1 ] && abort_wrap "This script requires a boot image as a parameter"
cwd=`pwd`
cd "`dirname_wrap $1`"
BOOTIMAGE="`pwd`/`basename_wrap $1`"
cd "$CWD"
cd $cwd
if [ -z "$BOOTIMAGE" ]; then
ui_print_wrap "This script requires a boot image as a parameter"
exit 1
fi
[ -e "$BOOTIMAGE" ] || abort_wrap "$BOOTIMAGE does not exist!"
# Presets
[ -z $KEEPVERITY ] && KEEPVERITY=false
[ -z $KEEPFORCEENCRYPT ] && KEEPFORCEENCRYPT=false
# Detect whether running as root
[ `id -u` -eq 0 ] && ROOT=true || ROOT=false
id | grep "uid=0" >/dev/null 2>&1 && ROOT=true || ROOT=false
# Switch to the location of the script file
[ -z $SOURCEDMODE ] && cd "`dirname_wrap "${BASH_SOURCE:-$0}"`"
chmod +x ./*
chmod -R 755 .
##########################################################################################
# Unpack
##########################################################################################
migrate_boot_backup
CHROMEOS=false
ui_print_wrap "- Unpacking boot image"
./magiskboot --unpack "$BOOTIMAGE"
CHROMEOS=false
case $? in
1 )
abort_wrap "! Unable to unpack boot image"
@@ -147,7 +152,7 @@ case $? in
0 ) # Stock boot
ui_print_wrap "- Stock boot image detected!"
ui_print_wrap "- Backing up stock boot image"
SHA1=`./magiskboot --sha1 "$BOOTIMAGE" | tail -n 1`
SHA1=`./magiskboot --sha1 "$BOOTIMAGE" 2>/dev/null`
STOCKDUMP=stock_boot_${SHA1}.img
dd if="$BOOTIMAGE" of=$STOCKDUMP
./magiskboot --compress $STOCKDUMP
@@ -156,12 +161,7 @@ case $? in
1 ) # Magisk patched
ui_print_wrap "- Magisk patched image detected!"
# Find SHA1 of stock boot image
if [ -z $SHA1 ]; then
./magiskboot --cpio-extract ramdisk.cpio init.magisk.rc init.magisk.rc.old
SHA1=`grep_prop "# STOCKSHA1" init.magisk.rc.old`
rm -f init.magisk.rc.old
fi
[ -z $SHA1 ] && SHA1=`./magiskboot --cpio-stocksha1 ramdisk.cpio 2>/dev/null`
OK=false
./magiskboot --cpio-restore ramdisk.cpio
if [ $? -eq 0 ]; then
@@ -238,15 +238,6 @@ ui_print_wrap "- Repacking boot image"
./magiskboot --repack "$BOOTIMAGE" || abort_wrap "! Unable to repack boot image!"
# Sign chromeos boot
if $CHROMEOS; then
echo > empty
./chromeos/futility vbutil_kernel --pack new-boot.img.signed \
--keyblock ./chromeos/kernel.keyblock --signprivate ./chromeos/kernel_data_key.vbprivk \
--version 1 --vmlinuz new-boot.img --config empty --arch arm --bootloader empty --flags 0x1
rm -f empty new-boot.img
mv new-boot.img.signed new-boot.img
fi
$CHROMEOS && sign_chromeos
./magiskboot --cleanup

View File

@@ -1,9 +1,9 @@
#!/sbin/sh
#MAGISK
##########################################################################################
#
# Magisk Flash Script
# by topjohnwu
#
#
# This script will detect, construct the environment for Magisk
# It will then call boot_patch.sh to patch the boot image
#
@@ -31,10 +31,6 @@ umask 022
OUTFD=$2
ZIP=$3
rm -rf $TMPDIR 2>/dev/null
mkdir -p $INSTALLER
unzip -o "$ZIP" -d $INSTALLER 2>/dev/null
if [ ! -d "$COMMONDIR" ]; then
echo "! Unable to extract zip file!"
exit 1
@@ -50,7 +46,7 @@ get_outfd
##########################################################################################
ui_print "************************"
ui_print "* MAGISK_VERSION_STUB"
ui_print "* Magisk v$MAGISK_VER Installer"
ui_print "************************"
ui_print "- Mounting /system, /vendor, /cache, /data"
@@ -79,95 +75,51 @@ ui_print "- Device platform: $ARCH"
BINDIR=$INSTALLER/$ARCH
chmod -R 755 $CHROMEDIR $BINDIR
find_boot_image
[ -z $BOOTIMAGE ] && abort "! Unable to detect boot image"
##########################################################################################
# Environment
##########################################################################################
ui_print "- Constructing environment"
is_mounted /data && MAGISKBIN=/data/magisk || MAGISKBIN=/cache/data_bin
if $BOOTMODE; then
# Cleanup binary mirrors
umount -l /dev/magisk/mirror/bin 2>/dev/null
rm -rf /dev/magisk/mirror/bin 2>/dev/null
fi
# Copy required files
rm -rf $MAGISKBIN 2>/dev/null
mkdir -p $MAGISKBIN
cp -af $BINDIR/. $COMMONDIR/. $MAGISKBIN
cp -af $CHROMEDIR $MAGISKBIN
cp -af $BINDIR/. $COMMONDIR/. $CHROMEDIR $TMPDIR/bin/busybox $MAGISKBIN
chmod -R 755 $MAGISKBIN
# addon.d
if [ -d /system/addon.d ]; then
ui_print "- Adding addon.d survival script"
mount -o rw,remount /system
cp $INSTALLER/addon.d/99-magisk.sh /system/addon.d/99-magisk.sh
cp -af $INSTALLER/addon.d/99-magisk.sh /system/addon.d/99-magisk.sh
chmod 755 /system/addon.d/99-magisk.sh
fi
##########################################################################################
# Magisk Image
##########################################################################################
$BOOTMODE || recovery_actions
# Fix SuperSU.....
$BOOTMODE && $MAGISKBIN/magisk magiskpolicy --live "allow fsck * * *"
if (is_mounted /data); then
IMG=/data/magisk.img
else
IMG=/cache/magisk.img
ui_print "- Data unavailable, use cache workaround"
fi
if [ -f $IMG ]; then
ui_print "- $IMG detected!"
else
ui_print "- Creating $IMG"
$MAGISKBIN/magisk --createimg $IMG 64M
fi
if ! is_mounted /magisk; then
ui_print "- Mounting $IMG to /magisk"
MAGISKLOOP=`$MAGISKBIN/magisk --mountimg $IMG /magisk`
fi
is_mounted /magisk || abort "! Magisk image mount failed..."
# Core folders
mkdir -p $COREDIR/props $COREDIR/post-fs-data.d $COREDIR/service.d 2>/dev/null
chmod 755 $COREDIR/post-fs-data.d $COREDIR/service.d
chown 0.0 $COREDIR/post-fs-data.d $COREDIR/service.d
# Legacy cleanup
mv $COREDIR/magiskhide/hidelist $COREDIR/hidelist 2>/dev/null
rm -rf $COREDIR/magiskhide $COREDIR/bin
$BOOTMODE && boot_actions || recovery_actions
##########################################################################################
# Unpack boot
# Boot patching
##########################################################################################
find_boot_image
[ -z $BOOTIMAGE ] && abort "! Unable to detect boot image"
ui_print "- Found Boot Image: $BOOTIMAGE"
# Update our previous backup to new format if exists
if [ -f /data/stock_boot.img ]; then
SHA1=`$MAGISKBIN/magiskboot --sha1 /data/stock_boot.img | tail -n 1`
STOCKDUMP=/data/stock_boot_${SHA1}.img
mv /data/stock_boot.img $STOCKDUMP
$MAGISKBIN/magiskboot --compress $STOCKDUMP
fi
SOURCEDMODE=true
cd $MAGISKBIN
# Source the boot patcher
. $COMMONDIR/boot_patch.sh "$BOOTIMAGE"
if [ -f stock_boot* ]; then
rm -f /data/stock_boot* 2>/dev/null
mv stock_boot* /data
fi
[ -f stock_boot* ] && rm -f /data/stock_boot* 2>/dev/null
ui_print "- Flashing new boot image"
if [ -L "$BOOTIMAGE" ]; then
@@ -179,11 +131,9 @@ rm -f new-boot.img
cd /
if ! $BOOTMODE; then
$MAGISKBIN/magisk --umountimg /magisk $MAGISKLOOP
rmdir /magisk
recovery_cleanup
fi
# Cleanups
$BOOTMODE || recovery_cleanup
rm -rf $TMPDIR
ui_print "- Done"
exit 0

View File

@@ -1,6 +1,7 @@
# Triggers
on post-fs
start logd
start magisk_pfs
wait /dev/.magisk.unblock 20

View File

@@ -3,17 +3,17 @@
#
# Magisk Uninstaller
# by topjohnwu
#
#
# This script can be placed in /cache/magisk_uninstaller.sh
# The Magisk main binary will pick up the script, and uninstall itself, following a reboot
# This script can also be used in flashable zip with the uninstaller_loader.sh
#
#
# This script will try to do restoration with the following:
# 1-1. Find and restore the original stock boot image dump (OTA proof)
# 1-2. If 1-1 fails, restore ramdisk from the internal backup
# (ramdisk fully restored, not OTA friendly)
# 1-3. If 1-2 fails, it will remove added files in ramdisk, however modified files
# are remained modified, because we have no backups. By doing so, Magisk will
# are remained modified, because we have no backups. By doing so, Magisk will
# not be started at boot, but this isn't actually 100% cleaned up
# 2. Remove all Magisk related files
# (The list is LARGE, most likely due to bad decision in early versions
@@ -21,36 +21,15 @@
#
##########################################################################################
# Call ui_print_wrap if exists, or else simply use echo
# Useful when wrapped in flashable zip
ui_print_wrap() {
type ui_print >/dev/null 2>&1 && ui_print "$1" || echo "$1"
}
# Call abort if exists, or else show error message and exit
# Essential when wrapped in flashable zip
abort_wrap() {
type abort >/dev/null 2>&1
if [ $? -ne 0 ]; then
ui_print_wrap "$1"
exit 1
else
abort "$1"
fi
}
if [ ! -d $MAGISKBIN -o ! -f $MAGISKBIN/magiskboot -o ! -f $MAGISKBIN/util_functions.sh ]; then
ui_print_wrap "! Cannot find $MAGISKBIN"
exit 1
fi
[ -z $BOOTMODE ] && BOOTMODE=false
MAGISKBIN=/data/magisk
CHROMEDIR=$MAGISKBIN/chromeos
# Default permissions
umask 022
if [ ! -f $MAGISKBIN/magiskboot -o ! -f $MAGISKBIN/util_functions.sh ]; then
echo "! Cannot find $MAGISKBIN"
exit 1
fi
# Load utility functions
. $MAGISKBIN/util_functions.sh
@@ -59,95 +38,77 @@ umask 022
find_boot_image
[ -z $BOOTIMAGE ] && abort "! Unable to detect boot image"
ui_print_wrap "- Found Boot Image: $BOOTIMAGE"
ui_print "- Found Boot Image: $BOOTIMAGE"
cd $MAGISKBIN
ui_print_wrap "- Unpacking boot image"
ui_print "- Unpacking boot image"
./magiskboot --unpack "$BOOTIMAGE"
CHROMEOS=false
case $? in
1 )
abort_wrap "! Unable to unpack boot image"
abort "! Unable to unpack boot image"
;;
2 )
CHROMEOS=true
;;
3 )
ui_print_wrap "! Sony ELF32 format detected"
abort_wrap "! Please use BootBridge from @AdrianDC to flash Magisk"
ui_print "! Sony ELF32 format detected"
abort "! Please use BootBridge from @AdrianDC to flash Magisk"
;;
4 )
ui_print_wrap "! Sony ELF64 format detected"
abort_wrap "! Stock kernel cannot be patched, please use a custom kernel"
ui_print "! Sony ELF64 format detected"
abort "! Stock kernel cannot be patched, please use a custom kernel"
esac
# Update our previous backup to new format if exists
if [ -f /data/stock_boot.img ]; then
SHA1=`./magiskboot --sha1 /data/stock_boot.img | tail -n 1`
STOCKDUMP=/data/stock_boot_${SHA1}.img
mv /data/stock_boot.img $STOCKDUMP
./magiskboot --compress $STOCKDUMP
fi
migrate_boot_backup
# Detect boot image state
./magiskboot --cpio-test ramdisk.cpio
case $? in
0 ) # Stock boot
ui_print_wrap "- Stock boot image detected!"
ui_print_wrap "! Magisk is not installed!"
exit
ui_print "- Stock boot image detected!"
abort "! Magisk is not installed!"
;;
1 ) # Magisk patched
ui_print_wrap "- Magisk patched image detected!"
ui_print "- Magisk patched image detected!"
# Find SHA1 of stock boot image
if [ -z $SHA1 ]; then
./magiskboot --cpio-extract ramdisk.cpio init.magisk.rc init.magisk.rc.old
SHA1=`grep_prop "# STOCKSHA1" init.magisk.rc.old`
rm -f init.magisk.rc.old
fi
[ -z $SHA1 ] && SHA1=`./magiskboot --cpio-stocksha1 ramdisk.cpio 2>/dev/null`
[ ! -z $SHA1 ] && STOCKDUMP=/data/stock_boot_${SHA1}.img
if [ -f ${STOCKDUMP}.gz ]; then
ui_print_wrap "- Boot image backup found!"
./magiskboot --decompress ${STOCKDUMP}.gz stock_boot.img
ui_print "- Boot image backup found!"
./magiskboot --decompress ${STOCKDUMP}.gz new-boot.img
else
ui_print_wrap "! Boot image backup unavailable"
ui_print_wrap "- Restoring ramdisk with backup"
ui_print "! Boot image backup unavailable"
ui_print "- Restoring ramdisk with internal backup"
./magiskboot --cpio-restore ramdisk.cpio
./magiskboot --repack $BOOTIMAGE stock_boot.img
./magiskboot --repack $BOOTIMAGE new-boot.img
fi
;;
2 ) # Other patched
ui_print_wrap "! Boot image patched by other programs!"
abort_wrap "! Cannot uninstall with this uninstaller"
ui_print "! Boot image patched by other programs!"
abort "! Cannot uninstall"
;;
esac
# Sign chromeos boot
if $CHROMEOS; then
echo > empty
$CHROMEOS && sign_chromeos
./chromeos/futility vbutil_kernel --pack stock_boot.img.signed \
--keyblock ./chromeos/kernel.keyblock --signprivate ./chromeos/kernel_data_key.vbprivk \
--version 1 --vmlinuz stock_boot.img --config empty --arch arm --bootloader empty --flags 0x1
rm -f empty stock_boot.img
mv stock_boot.img.signed stock_boot.img
fi
ui_print_wrap "- Flashing stock/reverted image"
ui_print "- Flashing stock/reverted image"
if [ -L "$BOOTIMAGE" ]; then
dd if=stock_boot.img of="$BOOTIMAGE" bs=4096
dd if=new-boot.img of="$BOOTIMAGE" bs=4096
else
cat stock_boot.img /dev/zero | dd of="$BOOTIMAGE" bs=4096 >/dev/null 2>&1
cat new-boot.img /dev/zero | dd of="$BOOTIMAGE" bs=4096 >/dev/null 2>&1
fi
rm -f stock_boot.img
rm -f new-boot.img
ui_print_wrap "- Removing Magisk files"
cd /
ui_print "- Removing Magisk files"
rm -rf /cache/magisk.log /cache/last_magisk.log /cache/magiskhide.log /cache/.disable_magisk \
/cache/magisk /cache/magisk_merge /cache/magisk_mount /cache/unblock /cache/magisk_uninstaller.sh \
/cache/magisk /cache/magisk_merge /cache/magisk_mount /cache/unblock /cache/magisk_uninstaller.sh \
/data/Magisk.apk /data/magisk.apk /data/magisk.img /data/magisk_merge.img /data/magisk_debug.log \
/data/busybox /data/magisk /data/custom_ramdisk_patch.sh /data/property/*magisk*
/data/busybox /data/magisk /data/custom_ramdisk_patch.sh /data/property/*magisk* \
/data/app/com.topjohnwu.magisk* /data/user/*/com.topjohnwu.magisk 2>/dev/null
$BOOTMODE && reboot

View File

@@ -3,7 +3,7 @@
#
# Magisk Uninstaller (used in recovery)
# by topjohnwu
#
#
# This script will load the real uninstaller in a flashable zip
#
##########################################################################################
@@ -13,17 +13,16 @@
##########################################################################################
BOOTMODE=false
INSTALLER=/tmp/uninstall
# This path should work in any cases
TMPDIR=/dev/tmp
INSTALLER=$TMPDIR/install
# Default permissions
umask 022
OUTFD=$2
ZIP=$3
rm -rf $INSTALLER 2>/dev/null
mkdir -p $INSTALLER
unzip -o "$ZIP" -d $INSTALLER 2>/dev/null
if [ ! -f $INSTALLER/util_functions.sh ]; then
echo "! Failed: Unable to extract zip file!"
exit 1
@@ -31,7 +30,6 @@ fi
# Load utility functions
. $INSTALLER/util_functions.sh
get_outfd
##########################################################################################
@@ -66,9 +64,7 @@ if is_mounted /data; then
# Copy the binaries to /data/magisk, in case they do not exist
rm -rf $MAGISKBIN 2>/dev/null
mkdir -p $MAGISKBIN
cp -af $BINDIR/. $MAGISKBIN
cp -af $CHROMEDIR $MAGISKBIN
cp -af $INSTALLER/util_functions.sh $MAGISKBIN
cp -af $BINDIR/. $CHROMEDIR $TMPDIR/bin/busybox $INSTALLER/util_functions.sh $MAGISKBIN
chmod -R 755 $MAGISKBIN
# Run the acttual uninstallation
recovery_actions

31
scripts/update_binary.sh Normal file
View File

@@ -0,0 +1,31 @@
# EX_ARM, EX_X86, BB_ARM, and BB_X86 should be generated in build.py
dirname_wrap() {
case "$1" in
*/*) dir=${1%/*}; [ -z $dir ] && echo "/" || echo $dir ;;
*) echo "." ;;
esac
}
[ "$1" = "indep" ] && INDEP=true || INDEP=false
$INDEP && TMPDIR="`cd "\`dirname_wrap "${BASH_SOURCE:-$0}"\`" && pwd`" || TMPDIR=/dev/tmp
INSTALLER=$TMPDIR/install; BBDIR=$TMPDIR/bin
EXBIN=$BBDIR/b64xz; BBBIN=$BBDIR/busybox
$INDEP || rm -rf $TMPDIR 2>/dev/null;
mkdir -p $BBDIR 2>/dev/null
touch $EXBIN $BBBIN; chmod 755 $EXBIN $BBBIN
echo -ne $EX_ARM > $EXBIN
if $EXBIN --test 2>/dev/null; then
echo $BB_ARM | $EXBIN > $BBBIN
else
echo -ne $EX_x86 > $EXBIN
echo $BB_x86 | $EXBIN > $BBBIN
fi
$BBBIN --install -s $TMPDIR/bin
export PATH=$BBDIR:$PATH
if $INDEP; then
shift
exec sh "$@"
else
mkdir -p $INSTALLER
unzip -o "$3" -d $INSTALLER
exec sh $INSTALLER/META-INF/com/google/android/updater-script $@
fi

View File

@@ -1,13 +1,14 @@
##########################################################################################
#
#
# Magisk General Utility Functions
# by topjohnwu
#
#
# Used in flash_script.sh, addon.d.sh, magisk module installers, and uninstaller
#
#
##########################################################################################
MAGISK_VERSION_STUB
SCRIPT_VERSION=$MAGISK_VER_CODE
get_outfd() {
readlink /proc/$$/fd/$OUTFD 2>/dev/null | grep /tmp >/dev/null
@@ -30,43 +31,75 @@ get_outfd() {
ui_print() {
if $BOOTMODE; then
echo "$1"
else
else
echo -n -e "ui_print $1\n" >> /proc/self/fd/$OUTFD
echo -n -e "ui_print\n" >> /proc/self/fd/$OUTFD
fi
}
grep_prop() {
REGEX="s/^$1=//p"
shift
FILES=$@
[ -z "$FILES" ] && FILES='/system/build.prop'
sed -n "$REGEX" $FILES 2>/dev/null | head -n 1
}
getvar() {
local VARNAME=$1
local VALUE=$(eval echo \$"$VARNAME");
for FILE in /dev/.magisk /data/.magisk /cache/.magisk /system/.magisk; do
if [ -z "$VALUE" ]; then
LINE=$(cat $FILE 2>/dev/null | grep "$VARNAME=")
if [ ! -z "$LINE" ]; then
VALUE=${LINE#*=}
fi
fi
local VALUE=$(eval echo \$$VARNAME)
[ ! -z $VALUE ] && return
for DIR in /dev /data /cache /system; do
VALUE=`grep_prop $VARNAME $DIR/.magisk`
[ ! -z $VALUE ] && break;
done
eval $VARNAME=\$VALUE
}
find_boot_image() {
if [ -z "$BOOTIMAGE" ]; then
for BLOCK in boot_a BOOT_A kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do
BOOTIMAGE=`ls /dev/block/by-name/$BLOCK || ls /dev/block/platform/*/by-name/$BLOCK || ls /dev/block/platform/*/*/by-name/$BLOCK` 2>/dev/null
for BLOCK in boot_a kern-a android_boot kernel boot lnx; do
BOOTIMAGE=`find /dev/block -iname $BLOCK | head -n 1` 2>/dev/null
[ ! -z $BOOTIMAGE ] && break
done
fi
# Recovery fallback
if [ -z "$BOOTIMAGE" ]; then
for FSTAB in /etc/*fstab*; do
BOOTIMAGE=`grep -E '\b/boot\b' $FSTAB | grep -v "#" | grep -oE '/dev/[a-zA-Z0-9_./-]*'`
BOOTIMAGE=`grep -v '#' $FSTAB | grep -E '\b/boot\b' | grep -oE '/dev/[a-zA-Z0-9_./-]*'`
[ ! -z $BOOTIMAGE ] && break
done
fi
[ -L "$BOOTIMAGE" ] && BOOTIMAGE=`readlink $BOOTIMAGE`
}
migrate_boot_backup() {
# Update the broken boot backup
if [ -f /data/stock_boot_.img.gz ]; then
./magiskboot --decompress /data/stock_boot_.img.gz
mv /data/stock_boot_.img /data/stock_boot.img
fi
# Update our previous backup to new format if exists
if [ -f /data/stock_boot.img ]; then
ui_print "- Migrating boot image backup"
SHA1=`./magiskboot --sha1 /data/stock_boot.img 2>/dev/null`
STOCKDUMP=/data/stock_boot_${SHA1}.img
mv /data/stock_boot.img $STOCKDUMP
./magiskboot --compress $STOCKDUMP
fi
}
sign_chromeos() {
echo > empty
./chromeos/futility vbutil_kernel --pack new-boot.img.signed \
--keyblock ./chromeos/kernel.keyblock --signprivate ./chromeos/kernel_data_key.vbprivk \
--version 1 --vmlinuz new-boot.img --config empty --arch arm --bootloader empty --flags 0x1
rm -f empty new-boot.img
mv new-boot.img.signed new-boot.img
}
is_mounted() {
if [ ! -z "$2" ]; then
cat /proc/mounts | grep $1 | grep $2, >/dev/null
@@ -76,16 +109,6 @@ is_mounted() {
return $?
}
grep_prop() {
REGEX="s/^$1=//p"
shift
FILES=$@
if [ -z "$FILES" ]; then
FILES='/system/build.prop'
fi
cat $FILES 2>/dev/null | sed -n "$REGEX" | head -n 1
}
remove_system_su() {
if [ -f /system/bin/su -o -f /system/xbin/su ] && [ ! -f /su/bin/su ]; then
ui_print "! System installed root detected, mount rw :("
@@ -125,58 +148,67 @@ api_level_arch_detect() {
if [ "$ABILONG" = "x86_64" ]; then ARCH=x64; IS64BIT=true; fi;
}
boot_actions() {
if [ ! -d /dev/magisk/mirror/bin ]; then
mkdir -p /dev/magisk/mirror/bin
mount -o bind $MAGISKBIN /dev/magisk/mirror/bin
fi
MAGISKBIN=/dev/magisk/mirror/bin
}
recovery_actions() {
# TWRP bug fix
mount -o bind /dev/urandom /dev/random
# Preserve environment varibles
OLD_PATH=$PATH
OLD_LD_PATH=$LD_LIBRARY_PATH
if [ ! -d $TMPDIR/bin ]; then
# Add busybox to PATH
mkdir -p $TMPDIR/bin
ln -s $MAGISKBIN/busybox $TMPDIR/bin/busybox
$MAGISKBIN/busybox --install -s $TMPDIR/bin
export PATH=$TMPDIR/bin:$PATH
fi
# Temporarily block out all custom recovery binaries/libs
mv /sbin /sbin_tmp
# Add all possible library paths
OLD_LD_PATH=$LD_LIBRARY_PATH
$IS64BIT && export LD_LIBRARY_PATH=/system/lib64:/system/vendor/lib64 || export LD_LIBRARY_PATH=/system/lib:/system/vendor/lib
}
recovery_cleanup() {
mv /sbin_tmp /sbin
# Clear LD_LIBRARY_PATH
mv /sbin_tmp /sbin 2>/dev/null
export LD_LIBRARY_PATH=$OLD_LD_PATH
[ -z $OLD_PATH ] || export PATH=$OLD_PATH
ui_print "- Unmounting partitions"
umount -l /system
umount -l /system 2>/dev/null
umount -l /vendor 2>/dev/null
umount -l /dev/random
umount -l /dev/random 2>/dev/null
}
abort() {
ui_print "$1"
mv /sbin_tmp /sbin 2>/dev/null
$BOOTMODE || recovery_cleanup
exit 1
}
set_perm() {
chown $2:$3 $1 || exit 1
chmod $4 $1 || exit 1
if [ ! -z $5 ]; then
chcon $5 $1 2>/dev/null
else
chcon 'u:object_r:system_file:s0' $1 2>/dev/null
fi
[ -z $5 ] && chcon 'u:object_r:system_file:s0' $1 || chcon $5 $1
}
set_perm_recursive() {
find $1 -type d 2>/dev/null | while read dir; do
set_perm $dir $2 $3 $4 $6
done
find $1 -type f 2>/dev/null | while read file; do
find $1 -type f -o -type l 2>/dev/null | while read file; do
set_perm $file $2 $3 $5 $6
done
}
mktouch() {
mkdir -p ${1%/*}
if [ -z "$2" ]; then
touch $1
else
echo $2 > $1
fi
mkdir -p ${1%/*} 2>/dev/null
[ -z $2 ] && touch $1 || echo $2 > $1
chmod 644 $1
}
@@ -185,6 +217,10 @@ request_size_check() {
reqSizeM=$((reqSizeM / 1024 + 1))
}
request_zip_size_check() {
reqSizeM=`unzip -l "$1" | tail -n 1 | awk '{ print int($1 / 1048567 + 1) }'`
}
image_size_check() {
SIZE="`$MAGISKBIN/magisk --imgsize $IMG`"
curUsedM=`echo "$SIZE" | cut -d" " -f1`

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,191 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* This is just a copy/paste/cut job from original SignAPK sources. This
* adaptation adds only the whole-file signature to a ZIP(jar,apk) file, and
* doesn't do any of the per-file signing, creating manifests, etc. This is
* useful when you've changed the structure itself of an existing (signed!)
* ZIP file, but the extracted contents are still identical. Using
* the normal SignAPK may re-arrange other things inside the ZIP, which may
* be unwanted behavior. This version only changes the ZIP's tail and keeps
* the rest the same - CF
*/
package eu.chainfire.minsignapk;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;
public class MinSignAPK {
/** Write a .RSA file with a digital signature. */
private static void writeSignatureBlock(Signature signature, X509Certificate publicKey, OutputStream out)
throws IOException, GeneralSecurityException {
SignerInfo signerInfo = new SignerInfo(new X500Name(publicKey.getIssuerX500Principal().getName()),
publicKey.getSerialNumber(), AlgorithmId.get("SHA1"), AlgorithmId.get("RSA"), signature.sign());
PKCS7 pkcs7 = new PKCS7(new AlgorithmId[] { AlgorithmId.get("SHA1") }, new ContentInfo(ContentInfo.DATA_OID,
null), new X509Certificate[] { publicKey }, new SignerInfo[] { signerInfo });
pkcs7.encodeSignedData(out);
}
private static void signWholeOutputFile(byte[] zipData, OutputStream outputStream, X509Certificate publicKey,
PrivateKey privateKey) throws IOException, GeneralSecurityException {
// For a zip with no archive comment, the
// end-of-central-directory record will be 22 bytes long, so
// we expect to find the EOCD marker 22 bytes from the end.
if (zipData[zipData.length - 22] != 0x50 || zipData[zipData.length - 21] != 0x4b
|| zipData[zipData.length - 20] != 0x05 || zipData[zipData.length - 19] != 0x06) {
throw new IllegalArgumentException("zip data already has an archive comment");
}
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
signature.update(zipData, 0, zipData.length - 2);
ByteArrayOutputStream temp = new ByteArrayOutputStream();
// put a readable message and a null char at the start of the
// archive comment, so that tools that display the comment
// (hopefully) show something sensible.
// TODO: anything more useful we can put in this message?
byte[] message = "signed by SignApk".getBytes("UTF-8");
temp.write(message);
temp.write(0);
writeSignatureBlock(signature, publicKey, temp);
int total_size = temp.size() + 6;
if (total_size > 0xffff) {
throw new IllegalArgumentException("signature is too big for ZIP file comment");
}
// signature starts this many bytes from the end of the file
int signature_start = total_size - message.length - 1;
temp.write(signature_start & 0xff);
temp.write((signature_start >> 8) & 0xff);
// Why the 0xff bytes? In a zip file with no archive comment,
// bytes [-6:-2] of the file are the little-endian offset from
// the start of the file to the central directory. So for the
// two high bytes to be 0xff 0xff, the archive would have to
// be nearly 4GB in side. So it's unlikely that a real
// commentless archive would have 0xffs here, and lets us tell
// an old signed archive from a new one.
temp.write(0xff);
temp.write(0xff);
temp.write(total_size & 0xff);
temp.write((total_size >> 8) & 0xff);
temp.flush();
// Signature verification checks that the EOCD header is the
// last such sequence in the file (to avoid minzip finding a
// fake EOCD appended after the signature in its scan). The
// odds of producing this sequence by chance are very low, but
// let's catch it here if it does.
byte[] b = temp.toByteArray();
for (int i = 0; i < b.length - 3; ++i) {
if (b[i] == 0x50 && b[i + 1] == 0x4b && b[i + 2] == 0x05 && b[i + 3] == 0x06) {
throw new IllegalArgumentException("found spurious EOCD header at " + i);
}
}
outputStream.write(zipData, 0, zipData.length - 2);
outputStream.write(total_size & 0xff);
outputStream.write((total_size >> 8) & 0xff);
temp.writeTo(outputStream);
}
private static PrivateKey readPrivateKey(File file)
throws IOException, GeneralSecurityException {
DataInputStream input = new DataInputStream(new FileInputStream(file));
try {
byte[] bytes = new byte[(int) file.length()];
input.read(bytes);
// dont support encrypted keys atm
//KeySpec spec = decryptPrivateKey(bytes, file);
//if (spec == null) {
KeySpec spec = new PKCS8EncodedKeySpec(bytes);
//}
try {
return KeyFactory.getInstance("RSA").generatePrivate(spec);
} catch (InvalidKeySpecException ex) {
return KeyFactory.getInstance("DSA").generatePrivate(spec);
}
} finally {
input.close();
}
}
private static X509Certificate readPublicKey(File file)
throws IOException, GeneralSecurityException {
FileInputStream input = new FileInputStream(file);
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(input);
} finally {
input.close();
}
}
public static void main(String[] args) {
if (args.length < 4) {
System.out.println("MinSignAPK pemfile pk8file inzip outzip");
System.out.println("- only adds whole-file signature to zip");
return;
}
String pemFile = args[0];
String pk8File = args[1];
String inFile = args[2];
String outFile = args[3];
try {
X509Certificate publicKey = readPublicKey(new File(pemFile));
PrivateKey privateKey = readPrivateKey(new File(pk8File));
InputStream fis = new FileInputStream(inFile);
byte[] buffer = new byte[(int)(new File(inFile)).length()];
fis.read(buffer);
fis.close();
OutputStream fos = new FileOutputStream(outFile, false);
signWholeOutputFile(buffer, fos, publicKey, privateKey);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

7
ziptools/zipsigner/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
*.iml
.gradle
/local.properties
.idea/
/build
*.hprof
app/.externalNativeBuild/

View File

@@ -0,0 +1,36 @@
group 'com.topjohnwu'
version '1.0.0'
apply plugin: 'java'
apply plugin: 'com.github.johnrengelman.shadow'
sourceCompatibility = 1.8
jar {
manifest {
attributes 'Main-Class': 'com.topjohnwu.ZipSigner'
}
}
shadowJar {
classifier = null
version = null
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
}
}
repositories {
mavenCentral()
}
dependencies {
compile 'org.bouncycastle:bcprov-jdk15on:1.57'
compile 'org.bouncycastle:bcpkix-jdk15on:1.57'
}

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Thu Aug 24 10:35:40 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-rc-2-bin.zip

172
ziptools/zipsigner/gradlew vendored Executable file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save ( ) {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
ziptools/zipsigner/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,4 @@
rootProject.name = 'zipsigner'
include 'apksigner'
rootProject.name = 'zipsigner'

View File

@@ -0,0 +1,567 @@
package com.topjohnwu;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.encoders.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
/*
* Modified from from AOSP(Marshmallow) SignAPK.java
* */
public class ZipSigner {
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
private static final String CERT_SIG_NAME = "META-INF/CERT.%s";
private static Provider sBouncyCastleProvider;
// bitmasks for which hash algorithms we need the manifest to include.
private static final int USE_SHA1 = 1;
private static final int USE_SHA256 = 2;
/**
* Return one of USE_SHA1 or USE_SHA256 according to the signature
* algorithm specified in the cert.
*/
private static int getDigestAlgorithm(X509Certificate cert) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
if ("SHA1WITHRSA".equals(sigAlg) ||
"MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above.
return USE_SHA1;
} else if (sigAlg.startsWith("SHA256WITH")) {
return USE_SHA256;
} else {
throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
"\" in cert [" + cert.getSubjectDN());
}
}
/** Returns the expected signature algorithm for this key type. */
private static String getSignatureAlgorithm(X509Certificate cert) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
if ("RSA".equalsIgnoreCase(keyType)) {
if (getDigestAlgorithm(cert) == USE_SHA256) {
return "SHA256withRSA";
} else {
return "SHA1withRSA";
}
} else if ("EC".equalsIgnoreCase(keyType)) {
return "SHA256withECDSA";
} else {
throw new IllegalArgumentException("unsupported key type: " + keyType);
}
}
// Files matching this pattern are not copied to the output.
private static Pattern stripPattern =
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
private static X509Certificate readPublicKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(input);
} finally {
input.close();
}
}
/** Read a PKCS#8 format private key. */
private static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
byte[] buffer = new byte[4096];
int size = input.read(buffer);
byte[] bytes = Arrays.copyOf(buffer, size);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
/**
* Add the hash(es) of every file to the manifest, creating it if
* necessary.
*/
private static Manifest addDigestsToManifest(JarFile jar, int hashes)
throws IOException, GeneralSecurityException {
Manifest input = jar.getManifest();
Manifest output = new Manifest();
Attributes main = output.getMainAttributes();
if (input != null) {
main.putAll(input.getMainAttributes());
} else {
main.putValue("Manifest-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
}
MessageDigest md_sha1 = null;
MessageDigest md_sha256 = null;
if ((hashes & USE_SHA1) != 0) {
md_sha1 = MessageDigest.getInstance("SHA1");
}
if ((hashes & USE_SHA256) != 0) {
md_sha256 = MessageDigest.getInstance("SHA256");
}
byte[] buffer = new byte[4096];
int num;
// We sort the input entries by name, and add them to the
// output manifest in sorted order. We expect that the output
// map will be deterministic.
TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
JarEntry entry = e.nextElement();
byName.put(entry.getName(), entry);
}
for (JarEntry entry: byName.values()) {
String name = entry.getName();
if (!entry.isDirectory() &&
(stripPattern == null || !stripPattern.matcher(name).matches())) {
InputStream data = jar.getInputStream(entry);
while ((num = data.read(buffer)) > 0) {
if (md_sha1 != null) md_sha1.update(buffer, 0, num);
if (md_sha256 != null) md_sha256.update(buffer, 0, num);
}
Attributes attr = null;
if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes();
if (md_sha1 != null) {
attr.putValue("SHA1-Digest",
new String(Base64.encode(md_sha1.digest()), "ASCII"));
}
if (md_sha256 != null) {
attr.putValue("SHA-256-Digest",
new String(Base64.encode(md_sha256.digest()), "ASCII"));
}
output.getEntries().put(name, attr);
}
}
return output;
}
/** Write to another stream and track how many bytes have been
* written.
*/
private static class CountOutputStream extends FilterOutputStream {
private int mCount;
public CountOutputStream(OutputStream out) {
super(out);
mCount = 0;
}
@Override
public void write(int b) throws IOException {
super.write(b);
mCount++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
mCount += len;
}
public int size() {
return mCount;
}
}
/** Write a .SF file with a digest of the specified manifest. */
private static void writeSignatureFile(Manifest manifest, OutputStream out,
int hash)
throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
Attributes main = sf.getMainAttributes();
main.putValue("Signature-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
MessageDigest md = MessageDigest.getInstance(
hash == USE_SHA256 ? "SHA256" : "SHA1");
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
true, "UTF-8");
// Digest of the entire manifest
manifest.write(print);
print.flush();
main.putValue(hash == USE_SHA256 ? "SHA-256-Digest-Manifest" : "SHA1-Digest-Manifest",
new String(Base64.encode(md.digest()), "ASCII"));
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
print.print("Name: " + entry.getKey() + "\r\n");
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
}
print.print("\r\n");
print.flush();
Attributes sfAttr = new Attributes();
sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest-Manifest",
new String(Base64.encode(md.digest()), "ASCII"));
sf.getEntries().put(entry.getKey(), sfAttr);
}
CountOutputStream cout = new CountOutputStream(out);
sf.write(cout);
// A bug in the java.util.jar implementation of Android platforms
// up to version 1.6 will cause a spurious IOException to be thrown
// if the length of the signature file is a multiple of 1024 bytes.
// As a workaround, add an extra CRLF in this case.
if ((cout.size() % 1024) == 0) {
cout.write('\r');
cout.write('\n');
}
}
/** Sign data and write the digital signature to 'out'. */
private static void writeSignatureBlock(
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
OutputStream out)
throws IOException,
CertificateEncodingException,
OperatorCreationException,
CMSException {
ArrayList<X509Certificate> certList = new ArrayList<>(1);
certList.add(publicKey);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))
.setProvider(sBouncyCastleProvider)
.build(privateKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder()
.setProvider(sBouncyCastleProvider)
.build())
.setDirectSignature(true)
.build(signer, publicKey));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(data, false);
ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
DEROutputStream dos = new DEROutputStream(out);
dos.writeObject(asn1.readObject());
}
/**
* Copy all the files in a manifest from input to output. We set
* the modification times in the output to a fixed time, so as to
* reduce variation in the output file and make incremental OTAs
* more efficient.
*/
private static void copyFiles(Manifest manifest, JarFile in, JarOutputStream out,
long timestamp, int alignment) throws IOException {
byte[] buffer = new byte[4096];
int num;
Map<String, Attributes> entries = manifest.getEntries();
ArrayList<String> names = new ArrayList<String>(entries.keySet());
Collections.sort(names);
boolean firstEntry = true;
long offset = 0L;
// We do the copy in two passes -- first copying all the
// entries that are STORED, then copying all the entries that
// have any other compression flag (which in practice means
// DEFLATED). This groups all the stored entries together at
// the start of the file and makes it easier to do alignment
// on them (since only stored entries are aligned).
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
JarEntry outEntry = null;
if (inEntry.getMethod() != JarEntry.STORED) continue;
// Preserve the STORED method of the input entry.
outEntry = new JarEntry(inEntry);
outEntry.setTime(timestamp);
// 'offset' is the offset into the file at which we expect
// the file data to begin. This is the value we need to
// make a multiple of 'alignement'.
offset += JarFile.LOCHDR + outEntry.getName().length();
if (firstEntry) {
// The first entry in a jar file has an extra field of
// four bytes that you can't get rid of; any extra
// data you specify in the JarEntry is appended to
// these forced four bytes. This is JAR_MAGIC in
// JarOutputStream; the bytes are 0xfeca0000.
offset += 4;
firstEntry = false;
}
if (alignment > 0 && (offset % alignment != 0)) {
// Set the "extra data" of the entry to between 1 and
// alignment-1 bytes, to make the file data begin at
// an aligned offset.
int needed = alignment - (int)(offset % alignment);
outEntry.setExtra(new byte[needed]);
offset += needed;
}
out.putNextEntry(outEntry);
InputStream data = in.getInputStream(inEntry);
while ((num = data.read(buffer)) > 0) {
out.write(buffer, 0, num);
offset += num;
}
out.flush();
}
// Copy all the non-STORED entries. We don't attempt to
// maintain the 'offset' variable past this point; we don't do
// alignment on these entries.
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
JarEntry outEntry = null;
if (inEntry.getMethod() == JarEntry.STORED) continue;
// Create a new entry so that the compressed len is recomputed.
outEntry = new JarEntry(name);
outEntry.setTime(timestamp);
out.putNextEntry(outEntry);
InputStream data = in.getInputStream(inEntry);
while ((num = data.read(buffer)) > 0) {
out.write(buffer, 0, num);
}
out.flush();
}
}
// This class is to provide a file's content, but trimming out the last two bytes
// Used for signWholeFile
private static class CMSProcessableFile implements CMSTypedData {
private File file;
private ASN1ObjectIdentifier type;
private byte[] buffer;
int bufferSize = 0;
CMSProcessableFile(File file) {
this.file = file;
type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
buffer = new byte[4096];
}
@Override
public ASN1ObjectIdentifier getContentType() {
return type;
}
@Override
public void write(OutputStream out) throws IOException, CMSException {
FileInputStream input = new FileInputStream(file);
long len = file.length() - 2;
while ((bufferSize = input.read(buffer)) > 0) {
if (len <= bufferSize) {
out.write(buffer, 0, (int) len);
break;
} else {
out.write(buffer, 0, bufferSize);
}
len -= bufferSize;
}
}
@Override
public Object getContent() {
return file;
}
byte[] getTail() {
return Arrays.copyOfRange(buffer, 0, bufferSize);
}
}
private static void signWholeFile(File input, X509Certificate publicKey,
PrivateKey privateKey, OutputStream outputStream)
throws Exception {
ByteArrayOutputStream temp = new ByteArrayOutputStream();
// put a readable message and a null char at the start of the
// archive comment, so that tools that display the comment
// (hopefully) show something sensible.
// TODO: anything more useful we can put in this message?
byte[] message = "signed by SignApk".getBytes("UTF-8");
temp.write(message);
temp.write(0);
CMSProcessableFile cmsFile = new CMSProcessableFile(input);
writeSignatureBlock(cmsFile, publicKey, privateKey, temp);
// For a zip with no archive comment, the
// end-of-central-directory record will be 22 bytes long, so
// we expect to find the EOCD marker 22 bytes from the end.
byte[] zipData = cmsFile.getTail();
if (zipData[zipData.length-22] != 0x50 ||
zipData[zipData.length-21] != 0x4b ||
zipData[zipData.length-20] != 0x05 ||
zipData[zipData.length-19] != 0x06) {
throw new IllegalArgumentException("zip data already has an archive comment");
}
int total_size = temp.size() + 6;
if (total_size > 0xffff) {
throw new IllegalArgumentException("signature is too big for ZIP file comment");
}
// signature starts this many bytes from the end of the file
int signature_start = total_size - message.length - 1;
temp.write(signature_start & 0xff);
temp.write((signature_start >> 8) & 0xff);
// Why the 0xff bytes? In a zip file with no archive comment,
// bytes [-6:-2] of the file are the little-endian offset from
// the start of the file to the central directory. So for the
// two high bytes to be 0xff 0xff, the archive would have to
// be nearly 4GB in size. So it's unlikely that a real
// commentless archive would have 0xffs here, and lets us tell
// an old signed archive from a new one.
temp.write(0xff);
temp.write(0xff);
temp.write(total_size & 0xff);
temp.write((total_size >> 8) & 0xff);
temp.flush();
// Signature verification checks that the EOCD header is the
// last such sequence in the file (to avoid minzip finding a
// fake EOCD appended after the signature in its scan). The
// odds of producing this sequence by chance are very low, but
// let's catch it here if it does.
byte[] b = temp.toByteArray();
for (int i = 0; i < b.length-3; ++i) {
if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) {
throw new IllegalArgumentException("found spurious EOCD header at " + i);
}
}
cmsFile.write(outputStream);
outputStream.write(total_size & 0xff);
outputStream.write((total_size >> 8) & 0xff);
temp.writeTo(outputStream);
}
private static void signFile(Manifest manifest, JarFile inputJar,
X509Certificate publicKey, PrivateKey privateKey,
JarOutputStream outputJar)
throws Exception {
// Assume the certificate is valid for at least an hour.
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
// MANIFEST.MF
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey));
byte[] signedData = baos.toByteArray();
outputJar.write(signedData);
// CERT.{EC,RSA} / CERT#.{EC,RSA}
final String keyType = publicKey.getPublicKey().getAlgorithm();
je = new JarEntry(String.format(CERT_SIG_NAME, keyType));
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData),
publicKey, privateKey, outputJar);
}
public static void main(String[] args) {
boolean minSign = false;
int argStart = 0;
if (args.length < 4) {
System.err.println("Usage: zipsigner [-m] publickey.x509[.pem] privatekey.pk8 input.jar output.jar");
System.exit(2);
}
if (args[0].equals("-m")) {
minSign = true;
argStart = 1;
}
sBouncyCastleProvider = new BouncyCastleProvider();
Security.insertProviderAt(sBouncyCastleProvider, 1);
File pubKey = new File(args[argStart]);
File privKey = new File(args[argStart + 1]);
File input = new File(args[argStart + 2]);
File output = new File(args[argStart + 3]);
int alignment = 4;
JarFile inputJar = null;
FileOutputStream outputFile = null;
int hashes = 0;
try {
X509Certificate publicKey = readPublicKey(new FileInputStream(pubKey));
hashes |= getDigestAlgorithm(publicKey);
// Set the ZIP file timestamp to the starting valid time
// of the 0th certificate plus one hour (to match what
// we've historically done).
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
PrivateKey privateKey = readPrivateKey(new FileInputStream(privKey));
outputFile = new FileOutputStream(output);
if (minSign) {
ZipSigner.signWholeFile(input, publicKey, privateKey, outputFile);
} else {
inputJar = new JarFile(input, false); // Don't verify.
JarOutputStream outputJar = new JarOutputStream(outputFile);
// For signing .apks, use the maximum compression to make
// them as small as possible (since they live forever on
// the system partition). For OTA packages, use the
// default compression level, which is much much faster
// and produces output that is only a tiny bit larger
// (~0.1% on full OTA packages I tested).
outputJar.setLevel(9);
Manifest manifest = addDigestsToManifest(inputJar, hashes);
copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
signFile(manifest, inputJar, publicKey, privateKey, outputJar);
outputJar.close();
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
} finally {
try {
if (inputJar != null) inputJar.close();
if (outputFile != null) outputFile.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}
}