mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 10:07:27 +00:00
Compare commits
69 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
44b93e7cc4 | ||
![]() |
0eb79e5acd | ||
![]() |
eceba26894 | ||
![]() |
0bf404f75e | ||
![]() |
cd8dd65a65 | ||
![]() |
50c56f8b50 | ||
![]() |
9e9f8ca8f3 | ||
![]() |
f63af0601c | ||
![]() |
189c671ce2 | ||
![]() |
bb39a01361 | ||
![]() |
764999704a | ||
![]() |
ecfa4aafc1 | ||
![]() |
a1e33c4d2f | ||
![]() |
7f8ba74dac | ||
![]() |
e3df62d812 | ||
![]() |
1913125881 | ||
![]() |
e8e58f3fed | ||
![]() |
1ca9ec384b | ||
![]() |
9522255e3a | ||
![]() |
2a22fa694e | ||
![]() |
1591f5a0ca | ||
![]() |
3bc4e9a724 | ||
![]() |
f7a6bb0723 | ||
![]() |
e9c17a3ef7 | ||
![]() |
29bb5840b5 | ||
![]() |
c9d8d860f6 | ||
![]() |
cc18096882 | ||
![]() |
15f2a664d1 | ||
![]() |
70b4f62ddc | ||
![]() |
e1023fdfaf | ||
![]() |
5e9648387a | ||
![]() |
2ba8b4df67 | ||
![]() |
3a084c5d7b | ||
![]() |
f7200e39c3 | ||
![]() |
a7dfc20967 | ||
![]() |
6eb7c0b5d6 | ||
![]() |
0b3c078aeb | ||
![]() |
750872cc37 | ||
![]() |
29895ff474 | ||
![]() |
44adccc147 | ||
![]() |
2a7e2c70b5 | ||
![]() |
8d431b6762 | ||
![]() |
273849c0c8 | ||
![]() |
5cc14405c7 | ||
![]() |
f0cfd60e62 | ||
![]() |
d6547f0701 | ||
![]() |
3b68905037 | ||
![]() |
eae611c54d | ||
![]() |
b37bad35c2 | ||
![]() |
5fab15fee5 | ||
![]() |
10c8ea17aa | ||
![]() |
7058c8ff5a | ||
![]() |
64e85da59f | ||
![]() |
f79fad64aa | ||
![]() |
cb70eebb08 | ||
![]() |
edaf8787d1 | ||
![]() |
24164c8580 | ||
![]() |
9fca7011aa | ||
![]() |
b13eb3fd40 | ||
![]() |
b7986a351c | ||
![]() |
ce87591c62 | ||
![]() |
25c289ad3e | ||
![]() |
8c5f11b7dd | ||
![]() |
7f7dda9ec2 | ||
![]() |
9c1005ff0c | ||
![]() |
5b36b4472c | ||
![]() |
a3fcc64aaa | ||
![]() |
f3078bc903 | ||
![]() |
6072744f7e |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -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
3
.gitmodules
vendored
@@ -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
|
||||
|
Submodule MagiskManager updated: 1a69b16d36...b362c0ef38
59
README.MD
59
README.MD
@@ -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
186
build.py
@@ -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
27
docs/README.MD
Normal 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
171
docs/applets.md
Normal 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
3
docs/changelog.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Changelog
|
||||
- 2017.8.16
|
||||
- Initial version for Magisk v13.5+
|
60
docs/details.md
Normal file
60
docs/details.md
Normal 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
76
docs/module_repo.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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
11
docs/procedures.html
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/repo_description.png
Normal file
BIN
docs/repo_description.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
6
docs/tips.md
Normal file
6
docs/tips.md
Normal 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`.
|
@@ -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
|
||||
|
@@ -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
83
jni/b64xz.c
Normal 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
1
jni/busybox
Submodule
Submodule jni/busybox added at 90c9c4fd96
@@ -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,40 +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)) {
|
||||
@@ -529,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");
|
||||
@@ -558,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) {
|
||||
@@ -678,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;
|
||||
}
|
||||
@@ -696,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) {
|
||||
@@ -738,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);
|
||||
}
|
||||
@@ -772,10 +741,9 @@ 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);
|
||||
close(debug_log_fd);
|
||||
#endif
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -26,12 +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
|
||||
system("logcat -c");
|
||||
exec_command_sync("logcat", "-c", NULL);
|
||||
}
|
||||
|
||||
// Should never be here, but well...
|
||||
|
@@ -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);
|
@@ -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 *
|
@@ -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__)
|
36
jni/include/magiskpolicy.h
Normal file
36
jni/include/magiskpolicy.h
Normal 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
|
@@ -12,8 +12,9 @@ int prop_exist(const char *name);
|
||||
int setprop(const char *name, const char *value);
|
||||
int setprop2(const char *name, const char *value, const int trigger);
|
||||
char *getprop(const char *name);
|
||||
int deleteprop(const char *name);
|
||||
int deleteprop(const char *name, const int trigger);
|
||||
int read_prop_file(const char* filename, const int trigger);
|
||||
void getprop_all(void (*cbk)(const char *name));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -83,7 +83,7 @@ int open_new(const char *filename) {
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
printf("Cleaning up...\n");
|
||||
fprintf(stderr, "Cleaning up...\n");
|
||||
char name[PATH_MAX];
|
||||
unlink(KERNEL_FILE);
|
||||
unlink(RAMDISK_FILE);
|
||||
|
@@ -26,11 +26,11 @@ static void restore_buf(int fd, const void *buf, size_t size) {
|
||||
}
|
||||
|
||||
static void print_info() {
|
||||
printf("KERNEL [%d] @ 0x%08x\n", hdr.kernel_size, hdr.kernel_addr);
|
||||
printf("RAMDISK [%d] @ 0x%08x\n", hdr.ramdisk_size, hdr.ramdisk_addr);
|
||||
printf("SECOND [%d] @ 0x%08x\n", hdr.second_size, hdr.second_addr);
|
||||
printf("DTB [%d] @ 0x%08x\n", hdr.dt_size, hdr.tags_addr);
|
||||
printf("PAGESIZE [%d]\n", hdr.page_size);
|
||||
fprintf(stderr, "KERNEL [%d] @ 0x%08x\n", hdr.kernel_size, hdr.kernel_addr);
|
||||
fprintf(stderr, "RAMDISK [%d] @ 0x%08x\n", hdr.ramdisk_size, hdr.ramdisk_addr);
|
||||
fprintf(stderr, "SECOND [%d] @ 0x%08x\n", hdr.second_size, hdr.second_addr);
|
||||
fprintf(stderr, "DTB [%d] @ 0x%08x\n", hdr.dt_size, hdr.tags_addr);
|
||||
fprintf(stderr, "PAGESIZE [%d]\n", hdr.page_size);
|
||||
if (hdr.os_version != 0) {
|
||||
int a,b,c,y,m = 0;
|
||||
int os_version, os_patch_level;
|
||||
@@ -40,38 +40,38 @@ static void print_info() {
|
||||
a = (os_version >> 14) & 0x7f;
|
||||
b = (os_version >> 7) & 0x7f;
|
||||
c = os_version & 0x7f;
|
||||
printf("OS_VERSION [%d.%d.%d]\n", a, b, c);
|
||||
fprintf(stderr, "OS_VERSION [%d.%d.%d]\n", a, b, c);
|
||||
|
||||
y = (os_patch_level >> 4) + 2000;
|
||||
m = os_patch_level & 0xf;
|
||||
printf("PATCH_LEVEL [%d-%02d]\n", y, m);
|
||||
fprintf(stderr, "PATCH_LEVEL [%d-%02d]\n", y, m);
|
||||
}
|
||||
printf("NAME [%s]\n", hdr.name);
|
||||
printf("CMDLINE [%s]\n", hdr.cmdline);
|
||||
fprintf(stderr, "NAME [%s]\n", hdr.name);
|
||||
fprintf(stderr, "CMDLINE [%s]\n", hdr.cmdline);
|
||||
|
||||
switch (ramdisk_type) {
|
||||
case GZIP:
|
||||
printf("COMPRESSION [%s]\n", "gzip");
|
||||
fprintf(stderr, "COMPRESSION [%s]\n", "gzip");
|
||||
break;
|
||||
case XZ:
|
||||
printf("COMPRESSION [%s]\n", "xz");
|
||||
fprintf(stderr, "COMPRESSION [%s]\n", "xz");
|
||||
break;
|
||||
case LZMA:
|
||||
printf("COMPRESSION [%s]\n", "lzma");
|
||||
fprintf(stderr, "COMPRESSION [%s]\n", "lzma");
|
||||
break;
|
||||
case BZIP2:
|
||||
printf("COMPRESSION [%s]\n", "bzip2");
|
||||
fprintf(stderr, "COMPRESSION [%s]\n", "bzip2");
|
||||
break;
|
||||
case LZ4:
|
||||
printf("COMPRESSION [%s]\n", "lz4");
|
||||
fprintf(stderr, "COMPRESSION [%s]\n", "lz4");
|
||||
break;
|
||||
case LZ4_LEGACY:
|
||||
printf("COMPRESSION [%s]\n", "lz4_legacy");
|
||||
fprintf(stderr, "COMPRESSION [%s]\n", "lz4_legacy");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown ramdisk format!\n");
|
||||
}
|
||||
printf("\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
int parse_img(unsigned char *orig, size_t size) {
|
||||
@@ -126,11 +126,11 @@ int parse_img(unsigned char *orig, size_t size) {
|
||||
|
||||
// Check MTK
|
||||
if (check_type(kernel) == MTK) {
|
||||
printf("MTK header found in kernel\n");
|
||||
fprintf(stderr, "MTK header found in kernel\n");
|
||||
mtk_kernel = 1;
|
||||
}
|
||||
if (ramdisk_type == MTK) {
|
||||
printf("MTK header found in ramdisk\n");
|
||||
fprintf(stderr, "MTK header found in ramdisk\n");
|
||||
mtk_ramdisk = 1;
|
||||
ramdisk_type = check_type(ramdisk + 512);
|
||||
}
|
||||
@@ -151,7 +151,7 @@ void unpack(const char* image) {
|
||||
mmap_ro(image, &orig, &size);
|
||||
|
||||
// Parse image
|
||||
printf("Parsing boot image: [%s]\n\n", image);
|
||||
fprintf(stderr, "Parsing boot image: [%s]\n\n", image);
|
||||
int ret = parse_img(orig, size);
|
||||
|
||||
// Dump kernel
|
||||
@@ -199,10 +199,10 @@ void repack(const char* orig_image, const char* out_image) {
|
||||
mmap_ro(orig_image, &orig, &size);
|
||||
|
||||
// Parse original image
|
||||
printf("Parsing boot image: [%s]\n\n", orig_image);
|
||||
fprintf(stderr, "Parsing boot image: [%s]\n\n", orig_image);
|
||||
parse_img(orig, size);
|
||||
|
||||
printf("Repack to boot image: [%s]\n\n", out_image);
|
||||
fprintf(stderr, "Repack to boot image: [%s]\n\n", out_image);
|
||||
|
||||
// Create new image
|
||||
int fd = open_new(out_image);
|
||||
|
@@ -22,10 +22,10 @@ static void write_file(const int fd, const void *buf, const size_t size, const c
|
||||
static void report(const int mode, const char* filename) {
|
||||
switch(mode) {
|
||||
case 0:
|
||||
printf("Decompressing to [%s]\n\n", filename);
|
||||
fprintf(stderr, "Decompressing to [%s]\n\n", filename);
|
||||
break;
|
||||
default:
|
||||
printf("Compressing to [%s]\n\n", filename);
|
||||
fprintf(stderr, "Compressing to [%s]\n\n", filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ static int cpio_compare(const void *a, const void *b) {
|
||||
|
||||
// Parse cpio file to a vector of cpio_file
|
||||
static void parse_cpio(const char *filename, struct vector *v) {
|
||||
printf("Loading cpio: [%s]\n\n", filename);
|
||||
fprintf(stderr, "Loading cpio: [%s]\n\n", filename);
|
||||
int fd = xopen(filename, O_RDONLY);
|
||||
cpio_newc_header header;
|
||||
cpio_file *f;
|
||||
@@ -105,7 +105,7 @@ static void parse_cpio(const char *filename, struct vector *v) {
|
||||
}
|
||||
|
||||
static void dump_cpio(const char *filename, struct vector *v) {
|
||||
printf("\nDump cpio: [%s]\n\n", filename);
|
||||
fprintf(stderr, "\nDump cpio: [%s]\n\n", filename);
|
||||
int fd = open_new(filename);
|
||||
unsigned inode = 300000;
|
||||
char header[111];
|
||||
@@ -158,7 +158,7 @@ static void cpio_rm(int recursive, const char *entry, struct vector *v) {
|
||||
if ((recursive && strncmp(f->filename, entry, strlen(entry)) == 0)
|
||||
|| (strcmp(f->filename, entry) == 0) ) {
|
||||
if (!f->remove) {
|
||||
printf("Remove [%s]\n", entry);
|
||||
fprintf(stderr, "Remove [%s]\n", entry);
|
||||
f->remove = 1;
|
||||
}
|
||||
if (!recursive) return;
|
||||
@@ -173,7 +173,7 @@ static void cpio_mkdir(mode_t mode, const char *entry, struct vector *v) {
|
||||
f->filename = xmalloc(f->namesize);
|
||||
memcpy(f->filename, entry, f->namesize);
|
||||
cpio_vec_insert(v, f);
|
||||
printf("Create directory [%s] (%04o)\n",entry, mode);
|
||||
fprintf(stderr, "Create directory [%s] (%04o)\n",entry, mode);
|
||||
}
|
||||
|
||||
static void cpio_add(mode_t mode, const char *entry, const char *filename, struct vector *v) {
|
||||
@@ -189,7 +189,7 @@ static void cpio_add(mode_t mode, const char *entry, const char *filename, struc
|
||||
xxread(fd, f->data, f->filesize);
|
||||
close(fd);
|
||||
cpio_vec_insert(v, f);
|
||||
printf("Add entry [%s] (%04o)\n", entry, mode);
|
||||
fprintf(stderr, "Add entry [%s] (%04o)\n", entry, mode);
|
||||
}
|
||||
|
||||
static void cpio_test(struct vector *v) {
|
||||
@@ -270,7 +270,7 @@ static void cpio_patch(struct vector *v, int keepverity, int keepforceencrypt) {
|
||||
if (injected)
|
||||
continue;
|
||||
// Inject magisk script as import
|
||||
printf("Inject new line [import /init.magisk.rc] in [init.rc]\n");
|
||||
fprintf(stderr, "Inject new line [import /init.magisk.rc] in [init.rc]\n");
|
||||
line = xcalloc(sizeof(*line), 1);
|
||||
line->line = strdup("import /init.magisk.rc");
|
||||
line->isNew = 1;
|
||||
@@ -279,7 +279,7 @@ static void cpio_patch(struct vector *v, int keepverity, int keepforceencrypt) {
|
||||
injected = 1;
|
||||
} else if (strstr(line->line, "selinux.reload_policy")) {
|
||||
// Remove this line
|
||||
printf("Remove line [%s] in [init.rc]\n", line->line);
|
||||
fprintf(stderr, "Remove line [%s] in [init.rc]\n", line->line);
|
||||
f->filesize -= strlen(line->line) + 1;
|
||||
__ = list_pop(&line->pos);
|
||||
free(line);
|
||||
@@ -296,14 +296,14 @@ static void cpio_patch(struct vector *v, int keepverity, int keepforceencrypt) {
|
||||
for (read = 0, write = 0; read < f->filesize; ++read, ++write) {
|
||||
skip = check_verity_pattern(f->data + read);
|
||||
if (skip > 0) {
|
||||
printf("Remove pattern [%.*s] in [%s]\n", skip, f->data + read, f->filename);
|
||||
fprintf(stderr, "Remove pattern [%.*s] in [%s]\n", skip, f->data + read, f->filename);
|
||||
read += skip;
|
||||
}
|
||||
f->data[write] = f->data[read];
|
||||
}
|
||||
f->filesize = write;
|
||||
} else if (strcmp(f->filename, "verity_key") == 0) {
|
||||
printf("Remove [verity_key]\n");
|
||||
fprintf(stderr, "Remove [verity_key]\n");
|
||||
f->remove = 1;
|
||||
}
|
||||
}
|
||||
@@ -313,7 +313,7 @@ static void cpio_patch(struct vector *v, int keepverity, int keepforceencrypt) {
|
||||
for (int i = 0 ; ENCRYPT_LIST[i]; ++i) {
|
||||
if (strncmp(f->data + read, ENCRYPT_LIST[i], strlen(ENCRYPT_LIST[i])) == 0) {
|
||||
memcpy(f->data + write, "encryptable", 11);
|
||||
printf("Replace [%s] with [%s] in [%s]\n", ENCRYPT_LIST[i], "encryptable", f->filename);
|
||||
fprintf(stderr, "Replace [%s] with [%s] in [%s]\n", ENCRYPT_LIST[i], "encryptable", f->filename);
|
||||
write += 11;
|
||||
read += strlen(ENCRYPT_LIST[i]);
|
||||
break;
|
||||
@@ -332,7 +332,7 @@ static void cpio_extract(const char *entry, const char *filename, struct vector
|
||||
cpio_file *f;
|
||||
vec_for_each(v, f) {
|
||||
if (strcmp(f->filename, entry) == 0 && S_ISREG(f->mode)) {
|
||||
printf("Extracting [%s] to [%s]\n\n", entry, filename);
|
||||
fprintf(stderr, "Extracting [%s] to [%s]\n\n", entry, filename);
|
||||
int fd = open_new(filename);
|
||||
xwrite(fd, f->data, f->filesize);
|
||||
fchmod(fd, f->mode);
|
||||
@@ -390,14 +390,14 @@ static void cpio_backup(const char *orig, struct vector *v) {
|
||||
// Something is missing in new ramdisk, backup!
|
||||
++i;
|
||||
doBak = 1;
|
||||
printf("Backup missing entry: ");
|
||||
fprintf(stderr, "Backup missing entry: ");
|
||||
} else if (res == 0) {
|
||||
++i; ++j;
|
||||
if (m->filesize == n->filesize && memcmp(m->data, n->data, m->filesize) == 0)
|
||||
continue;
|
||||
// Not the same!
|
||||
doBak = 1;
|
||||
printf("Backup mismatch entry: ");
|
||||
fprintf(stderr, "Backup mismatch entry: ");
|
||||
} else {
|
||||
// Someting new in ramdisk, record in rem
|
||||
++j;
|
||||
@@ -405,14 +405,14 @@ static void cpio_backup(const char *orig, struct vector *v) {
|
||||
rem->data = xrealloc(rem->data, rem->filesize + n->namesize);
|
||||
memcpy(rem->data + rem->filesize, n->filename, n->namesize);
|
||||
rem->filesize += n->namesize;
|
||||
printf("Record new entry: [%s] -> [.backup/.rmlist]\n", n->filename);
|
||||
fprintf(stderr, "Record new entry: [%s] -> [.backup/.rmlist]\n", n->filename);
|
||||
}
|
||||
if (doBak) {
|
||||
m->namesize += 8;
|
||||
m->filename = realloc(m->filename, m->namesize);
|
||||
strcpy(buf, m->filename);
|
||||
sprintf(m->filename, ".backup/%s", buf);
|
||||
printf("[%s] -> [%s]\n", buf, m->filename);
|
||||
fprintf(stderr, "[%s] -> [%s]\n", buf, m->filename);
|
||||
vec_push_back(&bak, m);
|
||||
// NULL the original entry, so it won't be freed
|
||||
vec_entry(o)[i - 1] = NULL;
|
||||
@@ -459,7 +459,7 @@ static int cpio_restore(struct vector *v) {
|
||||
n->data = xmalloc(n->filesize);
|
||||
memcpy(n->data, f->data, n->filesize);
|
||||
n->remove = 0;
|
||||
printf("Restoring [%s] -> [%s]\n", f->filename, n->filename);
|
||||
fprintf(stderr, "Restoring [%s] -> [%s]\n", f->filename, n->filename);
|
||||
cpio_vec_insert(v, n);
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
@@ -20,7 +20,7 @@ void hexpatch(const char *image, const char *from, const char *to) {
|
||||
hex2byte(to, patch);
|
||||
for (size_t i = 0; i < filesize - patternsize; ++i) {
|
||||
if (memcmp(file + i, pattern, patternsize) == 0) {
|
||||
printf("Pattern %s found!\nPatching to %s\n", from, to);
|
||||
fprintf(stderr, "Pattern %s found!\nPatching to %s\n", from, to);
|
||||
memset(file + i, 0, patternsize);
|
||||
memcpy(file + i, patch, patchsize);
|
||||
i += patternsize - 1;
|
||||
|
@@ -55,7 +55,8 @@ typedef enum {
|
||||
TEST,
|
||||
PATCH,
|
||||
BACKUP,
|
||||
RESTORE
|
||||
RESTORE,
|
||||
STOCKSHA1
|
||||
} command_t;
|
||||
|
||||
extern char *SUP_LIST[];
|
||||
|
@@ -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: "
|
||||
@@ -58,7 +59,7 @@ static void usage(char *arg0) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("MagiskBoot v" xstr(MAGISK_VERSION) "(" xstr(MAGISK_VER_CODE) ") (by topjohnwu) - Boot Image Modification Tool\n\n");
|
||||
fprintf(stderr, "MagiskBoot v" xstr(MAGISK_VERSION) "(" xstr(MAGISK_VER_CODE) ") (by topjohnwu) - Boot Image Modification Tool\n\n");
|
||||
|
||||
if (argc > 1 && strcmp(argv[1], "--cleanup") == 0) {
|
||||
cleanup();
|
||||
|
@@ -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[] =
|
||||
@@ -58,10 +58,21 @@ void hide_sensitive_props() {
|
||||
}
|
||||
}
|
||||
|
||||
static void rm_magisk_prop(const char *name) {
|
||||
if (strstr(name, "magisk")) {
|
||||
deleteprop(name, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void clean_magisk_props() {
|
||||
LOGD("hide_utils: Cleaning magisk props\n");
|
||||
getprop_all(rm_magisk_prop);
|
||||
}
|
||||
|
||||
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];
|
||||
@@ -87,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);
|
||||
@@ -129,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;
|
||||
}
|
||||
@@ -173,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;
|
||||
@@ -230,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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
@@ -55,10 +55,7 @@ void launch_magiskhide(int client) {
|
||||
hideEnabled = 1;
|
||||
LOGI("* Starting MagiskHide\n");
|
||||
|
||||
if (client > 0) {
|
||||
if (setprop(MAGISKHIDE_PROP, "1"))
|
||||
goto error;
|
||||
}
|
||||
deleteprop(MAGISKHIDE_PROP, 1);
|
||||
|
||||
hide_sensitive_props();
|
||||
|
||||
@@ -104,6 +101,8 @@ void stop_magiskhide(int client) {
|
||||
|
||||
hideEnabled = 0;
|
||||
setprop(MAGISKHIDE_PROP, "0");
|
||||
// Remove without actually removing persist props
|
||||
deleteprop(MAGISKHIDE_PROP, 0);
|
||||
pthread_kill(proc_monitor_thread, SIGUSR1);
|
||||
|
||||
write_int(client, DAEMON_SUCCESS);
|
||||
@@ -124,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);
|
||||
@@ -140,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;
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ void proc_monitor();
|
||||
void manage_selinux();
|
||||
void hide_sensitive_props();
|
||||
void relink_sbin();
|
||||
void clean_magisk_props();
|
||||
|
||||
// List managements
|
||||
int add_list(char *proc);
|
||||
|
@@ -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
|
||||
@@ -91,6 +91,7 @@ static void hide_daemon(int pid) {
|
||||
|
||||
manage_selinux();
|
||||
relink_sbin();
|
||||
clean_magisk_props();
|
||||
|
||||
if (switch_mnt_ns(pid))
|
||||
return;
|
||||
@@ -180,12 +181,11 @@ void proc_monitor() {
|
||||
|
||||
while (1) {
|
||||
// Clear previous logcat buffer
|
||||
system("logcat -b events -c");
|
||||
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;
|
||||
@@ -269,6 +269,5 @@ void proc_monitor() {
|
||||
kill(log_pid, SIGTERM);
|
||||
waitpid(log_pid, NULL, 0);
|
||||
close(log_fd);
|
||||
log_pid = 0;
|
||||
}
|
||||
}
|
||||
|
Submodule jni/magiskpolicy updated: c1c6f55f8f...3c6a170138
@@ -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(¤t->prop, 0, memory_order_release); // Add this line to nullify the prop entry
|
||||
* return to_prop_info(¤t->prop);
|
||||
* } else {
|
||||
*
|
||||
*
|
||||
* ....
|
||||
* }
|
||||
*
|
||||
*
|
||||
*
|
||||
* by patching just those functions directly, all other functions should be ok
|
||||
* as is.
|
||||
@@ -66,23 +66,25 @@
|
||||
|
||||
#define PRINT_D(...) { LOGD(__VA_ARGS__); if (verbose) printf(__VA_ARGS__); }
|
||||
#define PRINT_E(...) { LOGE(__VA_ARGS__); fprintf(stderr, __VA_ARGS__); }
|
||||
#define PERSISTENT_PROPERTY_DIR "/data/property"
|
||||
|
||||
static int verbose = 0;
|
||||
|
||||
static bool is_legal_property_name(const char* name, size_t namelen) {
|
||||
static bool is_legal_property_name(const char *name, size_t namelen) {
|
||||
|
||||
if (namelen < 1) return false;
|
||||
if (name[0] == '.') return false;
|
||||
if (name[namelen - 1] == '.') return false;
|
||||
|
||||
/* Only allow alphanumeric, plus '.', '-', or '_' */
|
||||
/* Only allow alphanumeric, plus '.', '-', '@', ':', or '_' */
|
||||
/* Don't allow ".." to appear in a property name */
|
||||
for (size_t i = 0; i < namelen; i++) {
|
||||
if (name[i] == '.') {
|
||||
// i=0 is guaranteed to never have a dot. See above.
|
||||
if (name[i - 1] == '.') return false;
|
||||
if (name[i-1] == '.') return false;
|
||||
continue;
|
||||
}
|
||||
if (name[i] == '_' || name[i] == '-') continue;
|
||||
if (name[i] == '_' || name[i] == '-' || name[i] == '@' || name[i] == ':') continue;
|
||||
if (name[i] >= 'a' && name[i] <= 'z') continue;
|
||||
if (name[i] >= 'A' && name[i] <= 'Z') continue;
|
||||
if (name[i] >= '0' && name[i] <= '9') continue;
|
||||
@@ -96,13 +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"
|
||||
" -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;
|
||||
}
|
||||
@@ -138,6 +143,22 @@ char *getprop(const char *name) {
|
||||
return strdup(value);
|
||||
}
|
||||
|
||||
static void (*cb)(const char *);
|
||||
|
||||
static void run_actual_cb(void* cookie, const char *name, const char *value, uint32_t serial) {
|
||||
cb(name);
|
||||
}
|
||||
|
||||
static void prop_foreach_cb(const prop_info* pi, void* cookie) {
|
||||
__system_property_read_callback2(pi, run_actual_cb, NULL);
|
||||
}
|
||||
|
||||
void getprop_all(void (*cbk)(const char *name)) {
|
||||
if (init_resetprop()) return;
|
||||
cb = cbk;
|
||||
__system_property_foreach2(prop_foreach_cb, NULL);
|
||||
}
|
||||
|
||||
int setprop(const char *name, const char *value) {
|
||||
return setprop2(name, value, 1);
|
||||
}
|
||||
@@ -145,11 +166,11 @@ 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) {
|
||||
if (!strncmp(name, "ro.", 3)) deleteprop(name);
|
||||
if (!strncmp(name, "ro.", 3)) deleteprop(name, trigger);
|
||||
ret = __system_property_set2(name, value);
|
||||
} else {
|
||||
ret = __system_property_update2(pi, value, strlen(value));
|
||||
@@ -172,13 +193,18 @@ int setprop2(const char *name, const char *value, const int trigger) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int deleteprop(const char *name) {
|
||||
int deleteprop(const char *name, const int trigger) {
|
||||
if (init_resetprop()) return -1;
|
||||
PRINT_D("resetprop: deleteprop [%s]\n", name);
|
||||
if (__system_property_del(name)) {
|
||||
PRINT_E("resetprop: delete prop: [%s] error\n", name);
|
||||
PRINT_D("resetprop: delete prop: [%s] error\n", name);
|
||||
return -1;
|
||||
}
|
||||
if (trigger && strstr(name, "persist.")) {
|
||||
char buffer[PATH_MAX];
|
||||
snprintf(buffer, sizeof(buffer), "%s/%s", PERSISTENT_PROPERTY_DIR, name);
|
||||
unlink(buffer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -226,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;
|
||||
|
||||
@@ -273,7 +299,7 @@ int resetprop_main(int argc, char *argv[]) {
|
||||
if (file) {
|
||||
return read_prop_file(filename, trigger);
|
||||
} else if (del) {
|
||||
return deleteprop(name);
|
||||
return deleteprop(name, trigger);
|
||||
} else {
|
||||
return setprop2(name, value, trigger);
|
||||
}
|
||||
|
2
jni/su
2
jni/su
Submodule jni/su updated: e2821025ef...c66632227b
@@ -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);
|
||||
}
|
||||
|
173
jni/utils/misc.c
173
jni/utils/misc.c
@@ -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) {
|
||||
@@ -232,29 +234,76 @@ int run_command(int err, int *fd, const char *path, char *const argv[]) {
|
||||
if (xpipe2(pipefd, O_CLOEXEC) == -1)
|
||||
return -1;
|
||||
writeEnd = pipefd[1];
|
||||
// Give the read end of the pipe
|
||||
*fd = pipefd[0];
|
||||
} else {
|
||||
writeEnd = *fd;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
close(writeEnd);
|
||||
if (fd && *fd < 0) {
|
||||
// Give the read end and close write end
|
||||
*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;
|
||||
@@ -278,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);
|
||||
@@ -290,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;
|
||||
@@ -381,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);
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# Triggers
|
||||
|
||||
on post-fs
|
||||
start logd
|
||||
start magisk_pfs
|
||||
wait /dev/.magisk.unblock 20
|
||||
|
||||
|
@@ -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,79 +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"
|
||||
[ $? -ne 0 ] && abort_wrap "! Unable to unpack boot image"
|
||||
CHROMEOS=false
|
||||
case $? in
|
||||
1 )
|
||||
abort "! Unable to unpack boot image"
|
||||
;;
|
||||
2 )
|
||||
CHROMEOS=true
|
||||
;;
|
||||
3 )
|
||||
ui_print "! Sony ELF32 format detected"
|
||||
abort "! Please use BootBridge from @AdrianDC to flash Magisk"
|
||||
;;
|
||||
4 )
|
||||
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 [ -f chromeos ]; then
|
||||
echo > empty
|
||||
$CHROMEOS && sign_chromeos
|
||||
|
||||
LD_LIBRARY_PATH=$SYSTEMLIB $CHROMEDIR/futility vbutil_kernel --pack stock_boot.img.signed \
|
||||
--keyblock $CHROMEDIR/kernel.keyblock --signprivate $CHROMEDIR/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 2>/dev/null
|
||||
/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
|
||||
|
@@ -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
31
scripts/update_binary.sh
Normal 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
|
@@ -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.
@@ -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
7
ziptools/zipsigner/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.idea/
|
||||
/build
|
||||
*.hprof
|
||||
app/.externalNativeBuild/
|
36
ziptools/zipsigner/build.gradle
Normal file
36
ziptools/zipsigner/build.gradle
Normal 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'
|
||||
}
|
BIN
ziptools/zipsigner/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
ziptools/zipsigner/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
ziptools/zipsigner/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
ziptools/zipsigner/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
172
ziptools/zipsigner/gradlew
vendored
Executable 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
84
ziptools/zipsigner/gradlew.bat
vendored
Normal 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
|
4
ziptools/zipsigner/settings.gradle
Normal file
4
ziptools/zipsigner/settings.gradle
Normal file
@@ -0,0 +1,4 @@
|
||||
rootProject.name = 'zipsigner'
|
||||
include 'apksigner'
|
||||
rootProject.name = 'zipsigner'
|
||||
|
567
ziptools/zipsigner/src/main/java/com/topjohnwu/ZipSigner.java
Normal file
567
ziptools/zipsigner/src/main/java/com/topjohnwu/ZipSigner.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user