├── ReforceApk
├── app
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ ├── layout
│ │ │ │ │ ├── activity_main.xml
│ │ │ │ │ └── activity_sub.xml
│ │ │ │ ├── drawable-v24
│ │ │ │ │ └── ic_launcher_foreground.xml
│ │ │ │ └── drawable
│ │ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── herbwen
│ │ │ │ └── reforceapk
│ │ │ │ ├── RefInvoke.java
│ │ │ │ └── ProxyApplication.java
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── herbwen
│ │ │ │ └── reforceapk
│ │ │ │ └── ExampleUnitTest.java
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── herbwen
│ │ │ └── reforceapk
│ │ │ └── ExampleInstrumentedTest.java
│ ├── proguard-rules.pro
│ └── build.gradle
├── settings.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── .gitignore
├── .idea
│ ├── misc.xml
│ ├── runConfigurations.xml
│ ├── gradle.xml
│ └── codeStyles
│ │ └── Project.xml
├── build.gradle
├── gradle.properties
├── gradlew.bat
└── gradlew
├── source_app
├── app
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ ├── layout
│ │ │ │ │ ├── activity_main.xml
│ │ │ │ │ └── activity_sub.xml
│ │ │ │ ├── drawable-v24
│ │ │ │ │ └── ic_launcher_foreground.xml
│ │ │ │ └── drawable
│ │ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── herbwen
│ │ │ │ │ └── unshell
│ │ │ │ │ ├── MyApplication.java
│ │ │ │ │ ├── SubActivity.java
│ │ │ │ │ └── MainActivity.java
│ │ │ └── AndroidManifest.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── herbwen
│ │ │ │ └── unshell
│ │ │ │ └── ExampleUnitTest.java
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── herbwen
│ │ │ └── unshell
│ │ │ └── ExampleInstrumentedTest.java
│ ├── proguard-rules.pro
│ └── build.gradle
├── settings.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── .gitignore
├── .idea
│ ├── misc.xml
│ ├── runConfigurations.xml
│ ├── gradle.xml
│ └── codeStyles
│ │ └── Project.xml
├── build.gradle
├── gradle.properties
├── gradlew.bat
└── gradlew
├── reforce_final.apk
├── DexShell
├── force
│ ├── classes.dex
│ ├── unshell.dex
│ └── source_app.apk
├── bin
│ └── com
│ │ └── example
│ │ └── reforceapk
│ │ └── mymain.class
├── .vscode
│ ├── settings.json
│ └── launch.json
├── .classpath
├── .project
├── .settings
│ └── org.eclipse.jdt.core.prefs
└── src
│ └── com
│ └── example
│ └── reforceapk
│ └── mymain.java
└── README.md
/ReforceApk/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/source_app/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/ReforceApk/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/source_app/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/reforce_final.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/reforce_final.apk
--------------------------------------------------------------------------------
/DexShell/force/classes.dex:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/DexShell/force/classes.dex
--------------------------------------------------------------------------------
/DexShell/force/unshell.dex:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/DexShell/force/unshell.dex
--------------------------------------------------------------------------------
/DexShell/force/source_app.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/DexShell/force/source_app.apk
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | unshell
3 |
4 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | unshell
3 |
4 |
--------------------------------------------------------------------------------
/ReforceApk/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/source_app/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/DexShell/bin/com/example/reforceapk/mymain.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/DexShell/bin/com/example/reforceapk/mymain.class
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/ReforceApk/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Herrrb/DexShell/HEAD/source_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/DexShell/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/.classpath": true,
4 | "**/.project": true,
5 | "**/.settings": true,
6 | "**/.factorypath": true
7 | }
8 | }
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/ReforceApk/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 |
--------------------------------------------------------------------------------
/source_app/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 |
--------------------------------------------------------------------------------
/ReforceApk/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 17 11:17:18 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/source_app/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 17 10:51:49 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/DexShell/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/DexShell/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "type": "java",
5 | "name": "CodeLens (Launch) - mymain",
6 | "request": "launch",
7 | "mainClass": "com.example.reforceapk.mymain",
8 | "projectName": "DexShell"
9 | }
10 | ]
11 | }
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/source_app/app/src/main/java/com/herbwen/unshell/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.unshell;
2 |
3 | import android.app.Application;
4 | import android.util.Log;
5 |
6 | public class MyApplication extends Application {
7 | @Override
8 | public void onCreate() {
9 | super.onCreate();
10 | Log.i("Herrrb", "source apk onCreate:"+this);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DexShell/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | DexShell
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/source_app/app/src/test/java/com/herbwen/unshell/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.unshell;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/ReforceApk/app/src/test/java/com/herbwen/reforceapk/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.reforceapk;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/ReforceApk/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/source_app/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ReforceApk/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/source_app/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ReforceApk/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/source_app/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/DexShell/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.8
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
11 | org.eclipse.jdt.core.compiler.source=1.8
12 |
--------------------------------------------------------------------------------
/ReforceApk/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.3.2'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 |
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/source_app/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.3.2'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 |
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/ReforceApk/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/source_app/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ReforceApk/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/source_app/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/source_app/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/source_app/app/src/androidTest/java/com/herbwen/unshell/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.unshell;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.herbwen.unshell", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/androidTest/java/com/herbwen/reforceapk/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.reforceapk;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.herbwen.reforceapk", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/layout/activity_sub.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/layout/activity_sub.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/source_app/app/src/main/java/com/herbwen/unshell/SubActivity.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.unshell;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.util.Log;
7 | import android.view.View;
8 | import android.widget.Button;
9 | import android.widget.Toast;
10 |
11 | public class SubActivity extends AppCompatActivity {
12 |
13 | @Override
14 | protected void onCreate(@Nullable Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | setContentView(R.layout.activity_sub);
17 |
18 | Button button = findViewById(R.id.button);
19 | button.setOnClickListener(new View.OnClickListener() {
20 | @Override
21 | public void onClick(View v) {
22 | Toast.makeText(SubActivity.this, "唉,难受", Toast.LENGTH_SHORT).show();
23 | }
24 | });
25 |
26 | Log.i("Herrrb", "App:" + getApplicationContext());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/source_app/app/src/main/java/com/herbwen/unshell/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.unshell;
2 |
3 | import android.content.Intent;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.os.Bundle;
6 | import android.util.Log;
7 | import android.view.View;
8 | import android.widget.TextView;
9 |
10 | public class MainActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_main);
16 |
17 | TextView content = new TextView(this);
18 | content.setText("I am Source Apk");
19 | content.setOnClickListener(new View.OnClickListener() {
20 | @Override
21 | public void onClick(View v) {
22 | Intent intent = new Intent(MainActivity.this, SubActivity.class);
23 | startActivity(intent);
24 | }
25 | });
26 | setContentView(content);
27 |
28 | Log.i("Herrrb", "app:"+getApplicationContext());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/source_app/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.herbwen.unshell"
7 | minSdkVersion 24
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | multiDexEnabled false
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(dir: 'libs', include: ['*.jar'])
24 | implementation 'com.android.support:appcompat-v7:28.0.0'
25 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
26 | testImplementation 'junit:junit:4.12'
27 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
28 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
29 | }
30 |
--------------------------------------------------------------------------------
/ReforceApk/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.herbwen.reforceapk"
7 | minSdkVersion 24
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | multiDexEnabled false
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(dir: 'libs', include: ['*.jar'])
24 | implementation 'com.android.support:appcompat-v7:28.0.0'
25 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
26 | testImplementation 'junit:junit:4.12'
27 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
28 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
29 | }
30 |
--------------------------------------------------------------------------------
/ReforceApk/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/source_app/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/ReforceApk/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/source_app/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/DexShell/src/com/example/reforceapk/mymain.java:
--------------------------------------------------------------------------------
1 | package com.example.reforceapk;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.File;
5 | import java.io.FileInputStream;
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.security.MessageDigest;
9 | import java.security.NoSuchAlgorithmException;
10 | import java.util.zip.Adler32;
11 |
12 |
13 | public class mymain {
14 | /**
15 | * @param args
16 | */
17 |
18 | public static void main(String[] args) {
19 | // TODO Auto-generated method stub
20 | try {
21 | File payloadSrcFile = new File("force/source_app.apk"); //需要加壳的程序
22 | System.out.println("apk size:"+payloadSrcFile.length());
23 | File unShellDexFile = new File("force/unshell.dex"); //解客dex
24 | byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作
25 | byte[] unShellDexArray = readFileBytes(unShellDexFile);//以二进制形式读出dex
26 | int payloadLen = payloadArray.length;
27 | int unShellDexLen = unShellDexArray.length;
28 | int totalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。
29 | byte[] newdex = new byte[totalLen]; // 申请了新的长度
30 | //添加解壳代码
31 | System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷贝dex内容
32 | //添加加密后的解壳数据
33 | System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex内容后面拷贝apk的内容
34 | //添加解壳数据长度
35 | System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度
36 | //修改DEX file size文件头
37 | fixFileSizeHeader(newdex);
38 | //修改DEX SHA1 文件头
39 | fixSHA1Header(newdex);
40 | //修改DEX CheckSum文件头
41 | fixCheckSumHeader(newdex);
42 |
43 | String str = "force/classes.dex";
44 | File file = new File(str);
45 | if (!file.exists()) {
46 | file.createNewFile();
47 | }
48 |
49 | FileOutputStream localFileOutputStream = new FileOutputStream(str);
50 | localFileOutputStream.write(newdex);
51 | localFileOutputStream.flush();
52 | localFileOutputStream.close();
53 |
54 |
55 | } catch (Exception e) {
56 | e.printStackTrace();
57 | }
58 | }
59 |
60 | //直接返回数据,读者可以添加自己加密方法
61 | private static byte[] encrpt(byte[] srcdata){
62 | for(int i = 0;i= 0; i--) {
98 | b[i] = (byte) (number % 256);
99 | number >>= 8;
100 | }
101 | return b;
102 | }
103 |
104 | /**
105 | * 修改dex头 sha1值
106 | * @param dexBytes
107 | * @throws NoSuchAlgorithmException
108 | */
109 | private static void fixSHA1Header(byte[] dexBytes)
110 | throws NoSuchAlgorithmException {
111 | MessageDigest md = MessageDigest.getInstance("SHA-1");
112 | md.update(dexBytes, 32, dexBytes.length - 32);//从32为到结束计算sha--1
113 | byte[] newdt = md.digest();
114 | System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31)
115 | //输出sha-1值,可有可无
116 | String hexstr = "";
117 | for (int i = 0; i < newdt.length; i++) {
118 | hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)
119 | .substring(1);
120 | }
121 | System.out.println(hexstr);
122 | }
123 |
124 | /**
125 | * 修改dex头 file_size值
126 | * @param dexBytes
127 | */
128 | private static void fixFileSizeHeader(byte[] dexBytes) {
129 | //新文件长度
130 | byte[] newfs = intToByte(dexBytes.length);
131 | System.out.println(Integer.toHexString(dexBytes.length));
132 | byte[] refs = new byte[4];
133 | //高位在前,低位在前掉个个
134 | for (int i = 0; i < 4; i++) {
135 | refs[i] = newfs[newfs.length - 1 - i];
136 | System.out.println(Integer.toHexString(newfs[i]));
137 | }
138 | System.arraycopy(refs, 0, dexBytes, 32, 4);//修改(32-35)
139 | }
140 |
141 |
142 | /**
143 | * 以二进制读出文件内容
144 | * @param file
145 | * @return
146 | * @throws IOException
147 | */
148 | private static byte[] readFileBytes(File file) throws IOException {
149 | byte[] arrayOfByte = new byte[1024];
150 | ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
151 | FileInputStream fis = new FileInputStream(file);
152 | while (true) {
153 | int i = fis.read(arrayOfByte);
154 | if (i != -1) {
155 | localByteArrayOutputStream.write(arrayOfByte, 0, i);
156 | } else {
157 | return localByteArrayOutputStream.toByteArray();
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/java/com/herbwen/reforceapk/RefInvoke.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.reforceapk;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 |
7 | public class RefInvoke {
8 |
9 | public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareType, Object[] pareValues){
10 | try {
11 | Class obj_class = Class.forName(class_name);
12 | Method method = obj_class.getMethod(method_name, pareType);
13 | return method.invoke(null, pareValues);
14 | } catch (SecurityException e){
15 | e.printStackTrace();
16 | } catch (IllegalArgumentException e){
17 | e.printStackTrace();
18 | } catch (IllegalAccessException e){
19 | e.printStackTrace();
20 | } catch (NoSuchMethodException e){
21 | e.printStackTrace();
22 | } catch (InvocationTargetException e){
23 | e.printStackTrace();
24 | } catch (ClassNotFoundException e){
25 | e.printStackTrace();
26 | }
27 | return null;
28 | }
29 |
30 | public static Object invokeMethod(String class_name, String method_name, Object obj, Class[] pareType, Object[] pareValues){
31 | try {
32 | Class obj_class = Class.forName(class_name);
33 | Method method = obj_class.getMethod(method_name, pareType);
34 | return method.invoke(obj, pareValues);
35 | } catch (SecurityException e){
36 | e.printStackTrace();
37 | } catch (IllegalArgumentException e){
38 | e.printStackTrace();
39 | } catch (IllegalAccessException e){
40 | e.printStackTrace();
41 | } catch (NoSuchMethodException e){
42 | e.printStackTrace();
43 | } catch (InvocationTargetException e){
44 | e.printStackTrace();
45 | } catch (ClassNotFoundException e){
46 | e.printStackTrace();
47 | }
48 | return null;
49 | }
50 |
51 | public static Object getFieldObject(String class_name, Object obj, String fieldName){
52 | try {
53 | Class obj_class = Class.forName(class_name);
54 | Field field = obj_class.getDeclaredField(fieldName);
55 | field.setAccessible(true);
56 | return field.get(obj);
57 | } catch (SecurityException e){
58 | e.printStackTrace();
59 | } catch (IllegalArgumentException e){
60 | e.printStackTrace();
61 | } catch (IllegalAccessException e){
62 | e.printStackTrace();
63 | } catch (ClassNotFoundException e){
64 | e.printStackTrace();
65 | } catch (NoSuchFieldException e){
66 | e.printStackTrace();
67 | }
68 | return null;
69 | }
70 |
71 | public static Object getStaticFieldObject(String class_name, String fieldName){
72 | try {
73 | Class obj_class = Class.forName(class_name);
74 | Field field = obj_class.getDeclaredField(fieldName);
75 | field.setAccessible(true);
76 | return field.get(null);
77 | } catch (SecurityException e){
78 | e.printStackTrace();
79 | } catch (IllegalArgumentException e){
80 | e.printStackTrace();
81 | } catch (IllegalAccessException e){
82 | e.printStackTrace();
83 | } catch (ClassNotFoundException e){
84 | e.printStackTrace();
85 | } catch (NoSuchFieldException e){
86 | e.printStackTrace();
87 | }
88 | return null;
89 | }
90 |
91 | public static void setFieldObject(String classname, String fieldName, Object obj, Object fieldValue){
92 | try {
93 | Class obj_class = Class.forName(classname);
94 | Field field = obj_class.getDeclaredField(fieldName);
95 | field.setAccessible(true);
96 | field.set(obj, fieldValue);
97 | }catch (SecurityException e){
98 | e.printStackTrace();
99 | }catch (NoSuchFieldException e){
100 | e.printStackTrace();
101 | }catch (IllegalArgumentException e){
102 | e.printStackTrace();
103 | }catch (IllegalAccessException e){
104 | e.printStackTrace();
105 | }catch (ClassNotFoundException e){
106 | e.printStackTrace();
107 | }
108 | }
109 |
110 | public static void setStaticObject(String class_name, String fieldName, Object fieldValue){
111 | try {
112 | Class obj_class = Class.forName(class_name);
113 | Field field = obj_class.getDeclaredField(fieldName);
114 | field.setAccessible(true);
115 | field.set(null, fieldValue);
116 | }catch (SecurityException e){
117 | e.printStackTrace();
118 | }catch (NoSuchFieldException e){
119 | e.printStackTrace();
120 | }catch (IllegalArgumentException e){
121 | e.printStackTrace();
122 | }catch (IllegalAccessException e){
123 | e.printStackTrace();
124 | }catch (ClassNotFoundException e){
125 | e.printStackTrace();
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/ReforceApk/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/source_app/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/source_app/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/ReforceApk/app/src/main/java/com/herbwen/reforceapk/ProxyApplication.java:
--------------------------------------------------------------------------------
1 | package com.herbwen.reforceapk;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.ByteArrayInputStream;
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.DataInputStream;
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.lang.ref.WeakReference;
12 | import java.lang.reflect.Method;
13 | import java.util.ArrayList;
14 | import java.util.Iterator;
15 | import java.util.zip.ZipEntry;
16 | import java.util.zip.ZipInputStream;
17 |
18 | import android.app.Application;
19 | import android.app.Instrumentation;
20 | import android.content.Context;
21 | import android.content.pm.ApplicationInfo;
22 | import android.content.pm.PackageManager;
23 | import android.content.pm.PackageManager.NameNotFoundException;
24 | import android.content.res.AssetManager;
25 | import android.content.res.Resources;
26 | import android.content.res.Resources.Theme;
27 | import android.os.Bundle;
28 | import android.util.ArrayMap;
29 | import android.util.Log;
30 | import dalvik.system.DexClassLoader;
31 |
32 | public class ProxyApplication extends Application{
33 | private static final String appkey = "APPLICATION_CLASS_NAME";
34 | private String apkFileName;
35 | private String odexPath;
36 | private String libPath;
37 |
38 | @Override
39 | protected void attachBaseContext(Context base) {
40 | super.attachBaseContext(base);
41 | try {
42 | File odex = this.getDir("payload_odex", MODE_PRIVATE);
43 | File libs = this.getDir("payload_lib", MODE_PRIVATE);
44 | odexPath = odex.getAbsolutePath();
45 | libPath = libs.getAbsolutePath();
46 | apkFileName = odex.getAbsolutePath() + "/payload.apk";
47 | File dexFile = new File(apkFileName);
48 | Log.i("demo", "apk size:"+dexFile.length());
49 | if (!dexFile.exists())
50 | {
51 | dexFile.createNewFile();
52 | byte[] dexdata = this.readDexFileFromApk();
53 | this.splitPayLoadFromDex(dexdata);
54 | }
55 | Object currentActivityThread = RefInvoke.invokeStaticMethod(
56 | "android.app.ActivityThread", "currentActivityThread",
57 | new Class[] {}, new Object[] {});
58 | String packageName = this.getPackageName();
59 |
60 | ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldObject(
61 | "android.app.ActivityThread", currentActivityThread,
62 | "mPackages");
63 | WeakReference wr = (WeakReference) mPackages.get(packageName);
64 |
65 | DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
66 | libPath, (ClassLoader) RefInvoke.getFieldObject(
67 | "android.app.LoadedApk", wr.get(), "mClassLoader"));
68 |
69 | RefInvoke.setFieldObject("android.app.LoadedApk", "mClassLoader",
70 | wr.get(), dLoader);
71 |
72 | Log.i("demo","classloader:"+dLoader);
73 |
74 | try{
75 | Object actObj = dLoader.loadClass("com.herbwen.unshell.MainActivity");
76 | Log.i("demo", "actObj:"+actObj);
77 | }catch(Exception e){
78 | Log.i("demo", "activity:"+Log.getStackTraceString(e));
79 | }
80 | } catch (Exception e) {
81 | Log.i("demo", "error:"+Log.getStackTraceString(e));
82 | e.printStackTrace();
83 | }
84 | }
85 |
86 | @Override
87 | public void onCreate() {
88 | {
89 | loadResources(apkFileName);
90 |
91 | Log.i("demo", "onCreate");
92 |
93 | String appClassName = null;
94 | try {
95 | ApplicationInfo ai = this.getPackageManager()
96 | .getApplicationInfo(this.getPackageName(),
97 | PackageManager.GET_META_DATA);
98 | Bundle bundle = ai.metaData;
99 | if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
100 | appClassName = bundle.getString("APPLICATION_CLASS_NAME");
101 | } else {
102 | Log.i("demo", "have no application class name");
103 | return;
104 | }
105 | } catch (NameNotFoundException e) {
106 | Log.i("demo", "error:"+Log.getStackTraceString(e));
107 | e.printStackTrace();
108 | }
109 |
110 | Object currentActivityThread = RefInvoke.invokeStaticMethod(
111 | "android.app.ActivityThread", "currentActivityThread",
112 | new Class[] {}, new Object[] {});
113 | Log.i("Herrrb", "currentActivityThread:"+currentActivityThread);
114 | Object mBoundApplication = RefInvoke.getFieldObject(
115 | "android.app.ActivityThread", currentActivityThread,
116 | "mBoundApplication");
117 | Log.i("Herrrb", "mBoundApplication:"+mBoundApplication);
118 | Object loadedApkInfo = RefInvoke.getFieldObject(
119 | "android.app.ActivityThread$AppBindData",
120 | mBoundApplication, "info");
121 | Log.i("Herrrb", "loadedApkinfo:"+loadedApkInfo);
122 | RefInvoke.setFieldObject("android.app.LoadedApk", "mApplication",
123 | loadedApkInfo, null);
124 | Object oldApplication = RefInvoke.getFieldObject(
125 | "android.app.ActivityThread", currentActivityThread,
126 | "mInitialApplication");
127 |
128 | ArrayList mAllApplications = (ArrayList) RefInvoke
129 | .getFieldObject("android.app.ActivityThread",
130 | currentActivityThread, "mAllApplications");
131 | mAllApplications.remove(oldApplication);
132 |
133 | ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
134 | .getFieldObject("android.app.LoadedApk", loadedApkInfo,
135 | "mApplicationInfo");
136 | ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
137 | .getFieldObject("android.app.ActivityThread$AppBindData",
138 | mBoundApplication, "appInfo");
139 | appinfo_In_LoadedApk.className = appClassName;
140 | appinfo_In_AppBindData.className = appClassName;
141 | Log.i("Herrrb", "appClassName:"+appClassName);
142 | Application app = (Application) RefInvoke.invokeMethod(
143 | "android.app.LoadedApk", "makeApplication", loadedApkInfo,
144 | new Class[] { boolean.class, Instrumentation.class },
145 | new Object[] { false, null });
146 | Log.i("Herrrb", "app:"+app);
147 | RefInvoke.setFieldObject("android.app.ActivityThread",
148 | "mInitialApplication", currentActivityThread, app);
149 |
150 | ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldObject(
151 | "android.app.ActivityThread", currentActivityThread,
152 | "mProviderMap");
153 | Iterator it = mProviderMap.values().iterator();
154 | while (it.hasNext()) {
155 | Object providerClientRecord = it.next();
156 | Object localProvider = RefInvoke.getFieldObject(
157 | "android.app.ActivityThread$ProviderClientRecord",
158 | providerClientRecord, "mLocalProvider");
159 | RefInvoke.setFieldObject("android.content.ContentProvider",
160 | "mContext", localProvider, app);
161 | }
162 |
163 | Log.i("demo", "app:"+app);
164 |
165 | app.onCreate();
166 | }
167 | }
168 | private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
169 | int ablen = apkdata.length;
170 |
171 | byte[] dexlen = new byte[4];
172 | System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
173 | ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
174 | DataInputStream in = new DataInputStream(bais);
175 | int readInt = in.readInt();
176 | System.out.println(Integer.toHexString(readInt));
177 | byte[] newdex = new byte[readInt];
178 |
179 | System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
180 |
181 | newdex = decrypt(newdex);
182 |
183 | File file = new File(apkFileName);
184 | try {
185 | FileOutputStream localFileOutputStream = new FileOutputStream(file);
186 | localFileOutputStream.write(newdex);
187 | localFileOutputStream.close();
188 | } catch (IOException localIOException) {
189 | throw new RuntimeException(localIOException);
190 | }
191 |
192 | ZipInputStream localZipInputStream = new ZipInputStream(
193 | new BufferedInputStream(new FileInputStream(file)));
194 | while (true) {
195 | ZipEntry localZipEntry = localZipInputStream.getNextEntry();
196 | if (localZipEntry == null) {
197 | localZipInputStream.close();
198 | break;
199 | }
200 |
201 | String name = localZipEntry.getName();
202 | if (name.startsWith("lib/") && name.endsWith(".so")) {
203 | File storeFile = new File(libPath + "/"
204 | + name.substring(name.lastIndexOf('/')));
205 | storeFile.createNewFile();
206 | FileOutputStream fos = new FileOutputStream(storeFile);
207 | byte[] arrayOfByte = new byte[1024];
208 | while (true) {
209 | int i = localZipInputStream.read(arrayOfByte);
210 | if (i == -1)
211 | break;
212 | fos.write(arrayOfByte, 0, i);
213 | }
214 | fos.flush();
215 | fos.close();
216 | }
217 | localZipInputStream.closeEntry();
218 | }
219 | localZipInputStream.close();
220 | }
221 |
222 | private byte[] readDexFileFromApk() throws IOException {
223 | ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
224 | ZipInputStream localZipInputStream = new ZipInputStream(
225 | new BufferedInputStream(new FileInputStream(
226 | this.getApplicationInfo().sourceDir)));
227 | while (true) {
228 | ZipEntry localZipEntry = localZipInputStream.getNextEntry();
229 | if (localZipEntry == null) {
230 | localZipInputStream.close();
231 | break;
232 | }
233 | if (localZipEntry.getName().equals("classes.dex")) {
234 | byte[] arrayOfByte = new byte[1024];
235 | while (true) {
236 | int i = localZipInputStream.read(arrayOfByte);
237 | if (i == -1)
238 | break;
239 | dexByteArrayOutputStream.write(arrayOfByte, 0, i);
240 | }
241 | }
242 | localZipInputStream.closeEntry();
243 | }
244 | localZipInputStream.close();
245 | return dexByteArrayOutputStream.toByteArray();
246 | }
247 |
248 | private byte[] decrypt(byte[] srcdata) {
249 | for(int i=0;i 最让我没有想到的是,这种远古方法居然成功了
2 | # 前言
3 | 因为最近要准备一些东西,所以决心把这个远古方法的加固给完成了,借此来了解一下基本的加固原理与实现
4 |
5 | GitHub:https://github.com/Herrrb/DexShell
6 |
7 | # 环境
8 | * 真机:Zuk Z2 Pro, Android8.0
9 | * Android Studio
10 | ![p1][1]
11 | * 环境:
12 | ![p2][2]
13 |
14 | 注意避免dex分包:
15 |
16 | multiDexEnabled false
17 |
18 | # 原理分析
19 | 需要的三个对象:
20 | 1. 需要加固的apk(源apk)
21 | 2. 壳程序apk,即上图中的脱壳Dex的出处
22 | 3. 加密工具(将源Apk进行加密和壳Dex合并成新的Dex)
23 |
24 | 主要步骤:首先需要写一个源Apk,即需要被加壳的apk,只需要一个实现简单跳转和Toast功能的demo即可;然后是一个脱壳apk,这里理解了很久为什么是个脱壳apk,因为我们需要把源apk打进脱壳apk的classes.dex里,然后安装的时候是安装的这个脱壳apk,既然安装的是脱壳apk,那么如何执行源apk的功能呢?这里就涉及到脱壳apk的功能,就是脱壳,将自身dex载入,再将打进去的apk拿出来执行,这样就执行了原来的apk;
25 |
26 | 最后就是加密工具,即将源apk与脱壳dex合并的工具,这里工具的输出是一个新的classes.dex;
27 |
28 | 工具操作的主体是dex,因为dex结构,加壳的时候需要注意,checksum、signature、filesize三个字段,即当apk和apk大小(4字节)附加到dex之后,这三个字段需要进行修正;
29 |
30 | # 源APK
31 | 这边定义包名为:com.herbwen.unshell
32 | 结构如下
33 | ![p3][3]
34 |
35 | ## MainActivity
36 | 即正常的功能,设定TextView和Intent;
37 |
38 | package com.herbwen.unshell;
39 | import android.content.Intent;
40 | import android.support.v7.app.AppCompatActivity;
41 | import android.os.Bundle;
42 | import android.util.Log;
43 | import android.view.View;
44 | import android.widget.TextView;
45 | public class MainActivity extends AppCompatActivity {
46 | @Override
47 | protected void onCreate(Bundle savedInstanceState) {
48 | super.onCreate(savedInstanceState);
49 | setContentView(R.layout.activity_main);
50 | TextView content = new TextView(this);
51 | content.setText("I am Source Apk");
52 | content.setOnClickListener(new View.OnClickListener() {
53 | @Override
54 | public void onClick(View v) {
55 | Intent intent = new Intent(MainActivity.this, SubActivity.class);
56 | startActivity(intent);
57 | }
58 | });
59 | setContentView(content);
60 | Log.i("Herrrb", "app:"+getApplicationContext());
61 | }
62 | }
63 |
64 | ## SubActivity
65 | 即Intent关联的Activity
66 |
67 | package com.herbwen.unshell;
68 | import android.os.Bundle;
69 | import android.support.annotation.Nullable;
70 | import android.support.v7.app.AppCompatActivity;
71 | import android.util.Log;
72 | import android.view.View;
73 | import android.widget.Button;
74 | import android.widget.Toast;
75 | public class SubActivity extends AppCompatActivity {
76 | @Override
77 | protected void onCreate(@Nullable Bundle savedInstanceState) {
78 | super.onCreate(savedInstanceState);
79 | setContentView(R.layout.activity_sub);
80 | Button button = findViewById(R.id.button);
81 | button.setOnClickListener(new View.OnClickListener() {
82 | @Override
83 | public void onClick(View v) {
84 | Toast.makeText(SubActivity.this, "唉,难受", Toast.LENGTH_SHORT).show();
85 | }
86 | });
87 | Log.i("Herrrb", "App:" + getApplicationContext());
88 | }
89 | }
90 |
91 | # 加壳程序
92 | 首先来看加壳程序才能知道脱壳apk为什么要这样写;
93 | 因为总要上传到GitHub上,这里就分段来写
94 | 首先是导入
95 |
96 | package com.example.reforceapk;
97 | import java.io.ByteArrayOutputStream;
98 | import java.io.File;
99 | import java.io.FileInputStream;
100 | import java.io.FileOutputStream;
101 | import java.io.IOException;
102 | import java.security.MessageDigest;
103 | import java.security.NoSuchAlgorithmException;
104 | import java.util.zip.Adler32;
105 |
106 |
107 | 类下有一下几个方法
108 | 1. main:主要执行载入以及加壳功能;
109 | 2. encrypt:对apk文件进行加密;
110 | 3. fixCheckSumHeader:修正更改后的dex头checksum字段;
111 | 4. intToByte:字面意思,int转byte
112 | 5. fixSHA1Header:修正signature字段;
113 | 6. fixFileSizeHeader:修正filesize字段;
114 | 7. readFileBytes:以二进制读出文件内容;
115 |
116 | ## main
117 |
118 | public static void main(String[] args) {
119 | // TODO Auto-generated method stub
120 | try {
121 | File payloadSrcFile = new File("force/source_app.apk");
122 | // 获取到需要加壳的程序
123 | System.out.println("apk size:"+payloadSrcFile.length());
124 | // 打印需要加壳的apk的大小,即长度
125 | File unShellDexFile = new File("force/unshell.dex");
126 | // 载入解客dex
127 | byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
128 | // 以二进制形式读出apk,并进行加密处理,后面可以看到这里采用的是异或加密
129 |
130 | byte[] unShellDexArray = readFileBytes(unShellDexFile);
131 | // 以二进制形式读出dex
132 | int payloadLen = payloadArray.length;
133 | // 加密后源apk大小
134 | int unShellDexLen = unShellDexArray.length;
135 | int totalLen = payloadLen + unShellDexLen +4;
136 | // 总长度,多出4字节是存放长度的。
137 | byte[] newdex = new byte[totalLen];
138 | // 申请了新的长度的数组
139 |
140 | // 添加解壳代码
141 | System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);
142 | // 先拷贝dex内容
143 |
144 | // 添加加密后的解壳数据
145 | System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);
146 | //再在dex内容后面拷贝apk的内容
147 |
148 | //添加解壳数据长度
149 | System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);
150 | //最后4为长度
151 |
152 | //修改DEX file size文件头
153 | fixFileSizeHeader(newdex);
154 | //修改DEX SHA1 文件头
155 | fixSHA1Header(newdex);
156 | //修改DEX CheckSum文件头
157 | fixCheckSumHeader(newdex);
158 | String str = "force/classes.dex";
159 | File file = new File(str);
160 | if (!file.exists()) {
161 | file.createNewFile();
162 | }
163 |
164 | FileOutputStream localFileOutputStream = new FileOutputStream(str);
165 | localFileOutputStream.write(newdex);
166 | localFileOutputStream.flush();
167 | localFileOutputStream.close();
168 | } catch (Exception e) {
169 | e.printStackTrace();
170 | }
171 | }
172 |
173 | 这里可以总结出一个Java的文件写入流程
174 |
175 | String str = "file_name";
176 | File file = new File(str);
177 | if (!file.exists()){
178 | file.createNewFile();
179 | }
180 |
181 | FileOutputStream localFileOutputStream = new FileOutputStream(str);
182 | localFileOutputStream.write(content_needed_to_be_added);
183 | localFileOutputStream.flush();
184 | localFileOutputStream.close();
185 |
186 | 抓报错流程
187 |
188 | try {
189 | ...
190 | }catch (Exception e) {
191 | Log.i("Herrrb", Log.getStackTraceString(e));
192 | e.printStackTrace();
193 | }
194 |
195 | 主要实现了:
196 | 1. 读apk;
197 | 2. 读dex;
198 | 3. 转化成byte[]合并;
199 | 4. 修正几个头部属性;
200 | 5. 写入classes.dex;
201 | 6. 完成
202 |
203 | ## encrypt
204 |
205 | private static byte[] encrpt(byte[] srcdata){
206 | for(int i = 0;i=0; i--){
246 | b[i] = (byte) (number % 256)
247 | number >>= 8;
248 | }
249 | return b;
250 | }
251 |
252 | 大意就是转字节,4字节一单位
253 | 例如:1096
254 | 二进制:100 01001000
255 | byte[]: 0 0 4 72
256 |
257 | ## fixSHA1Header
258 | 修改dex头,sha1值
259 |
260 | private static void fixSHA1Header(byte[] dexBytes) throws NoSuchAlgorithmException {
261 | MessageDigest md = MessageDigest.getInstance("SHA-1");
262 | md.update(dexBytes, 32, dexBytes.length - 32);//从32为到结束计算sha--1
263 | byte[] newdt = md.digest();
264 | System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31)
265 | //输出sha-1值,可有可无
266 | String hexstr = "";
267 | for (int i = 0; i < newdt.length; i++) {
268 | hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16).substring(1);
269 | }
270 | System.out.println(hexstr);
271 | }
272 |
273 | 首先是调用`import java.security.MessageDigest;`,拿到sha1方法的对象
274 | 类似python的用法去update来传输数据,digest方法得到加密后的结果,因为计算的输入是出去magic、checksum、signature三部分,故需要从32开始;
275 |
276 | 保存在newdt中,从arraycopy中也可以看出这个签名是20字节的;到这就结束了,后面的输出就可有可无了;
277 |
278 |
279 | ## fixFileSizeHeader
280 | 修改dex头,file_size值
281 |
282 | private static void fixFileSizeHeader(byte[] dexBytes) {
283 | //新文件长度
284 | byte[] newfs = intToByte(dexBytes.length);
285 | System.out.println(Integer.toHexString(dexBytes.length));
286 | byte[] refs = new byte[4];
287 |
288 | for (int i = 0; i < 4; i++) {
289 | refs[i] = newfs[newfs.length - 1 - i];
290 | System.out.println(Integer.toHexString(newfs[i]));
291 | }
292 | System.arraycopy(refs, 0, dexBytes, 32, 4);//修改(32-35)
293 | }
294 |
295 | 得到文件长度的byte[],低位高位颠倒再存入dexBytes,修改32-35四个字节
296 |
297 | ## readFileBytes
298 | 以二进制读出文件
299 |
300 | private static byte[] readFileBytes(File file) throws IOException {
301 | byte[] arrayOfByte = new byte[1024];
302 | ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
303 | FileInputStream fis = new FileInputStream(file);
304 | while (true) {
305 | int i = fis.read(arrayOfByte);
306 | if (i != -1) {
307 | localByteArrayOutputStream.write(arrayOfByte, 0, i);
308 | } else {
309 | return localByteArrayOutputStream.toByteArray();
310 | }
311 | }
312 | }
313 |
314 | 最后返回的是ByteArrayOutputStream类型的值,首先FileInputStream,将file传入,一次read1024字节,然后写入ByteArrayOutputStream,调用toByteArray方法返回;
315 |
316 | 这样加密工具分析就结束了;
317 |
318 | 正式使用时可在eclipse中
319 | ![p5][4]
320 |
321 | 总结起来就是,结合源apk(加密后)与脱壳程序dex,dex在前apk在后,并修正dex文件头的三个属性;
322 |
323 | # 脱壳APK
324 | 个人觉得这种方法脱壳的核心就在于动态加载,
325 | LoadedApk.java,负责加载一个apk程序,类内部有一个mClassLoader变量,负责加载apk,只要获取这个类加载器并替换为解密出的apk的dexclassloader,该dexclassloader一方面加载了源程序、另一方面以原mClassLoader为父节点,保证了即加载了源程序又没有放弃原先加载的资源和系统代码;
326 |
327 | 然后是找到源程序的Application,通过反射建立并运行,这是会Log出一条消息;
328 | 单纯地载入是不会运行的,所以需要找到Application类(也就是apk的全局类),运行其onCreate方法,这样源apk才开始它的生命周期;
329 |
330 | 接下来看一下具体的步骤:(还是不贴完整代码,详情请见GitHub
331 | 自定义类ProxyApplication,继承自Application类
332 |
333 | 首先是定义全局变量:
334 |
335 | private static final String appkey = "APPLICATION_CLASS_NAME";
336 | private String apkFileName;
337 | private String odexPath;
338 | private String libPath;
339 |
340 | ## RefInvoke
341 | 具体解释见 https://herbwen.com/index.php/archives/57/#%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BDActivity
342 |
343 | ## attachBaseContext
344 | 然后是override一下attachBaseContext方法,因为这个方法在onCreate之前执行,
345 | 在这个方法内,需要提取出源apk,解密,
346 |
347 | File odex = this.getDir("payload_odex", MODE_PRIVATE);
348 | File libs = this.getDir("payload_lib", MODE_PRIVATE);
349 | odexPath = odex.getAbsolutePath();
350 | libPath = libs.getAbsolutePath();
351 | apkFileName = odex.getAbsolutePath() + "/payload.apk";
352 | File dexFile = new File(apkFileName);
353 | Log.i("demo", "apk size:"+dexFile.length()); // 第一次的时候输出0
354 | if (!dexFile.exists())
355 | {
356 | dexFile.createNewFile();
357 | // 从apk中读取dex,具体方法放放入readDexFileFromApk中
358 | byte[] dexdata = this.readDexFileFromApk();
359 | // 再从dex中取出源apk,放入dexdata
360 | this.splitPayLoadFromDex(dexdata);
361 | }
362 |
363 |
364 | 然后替换LoadedApk中的mClassloader变量,将原mClassLoader设置为父类,DexClassLoader加载了。
365 |
366 | Object currentActivityThread = RefInvoke.invokeStaticMethod(
367 | "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {});
368 |
369 | String packageName = this.getPackageName();
370 | // 得到当前的包名,配合mPackage得到与LoadedApk的映射关系
371 |
372 | ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldObject(
373 | "android.app.ActivityThread", currentActivityThread, "mPackages");
374 | // 存放的是apk包名和LoadedApk类的映射关系
375 |
376 | WeakReference wr = (WeakReference) mPackages.get(packageName);
377 | // 得到LoadedApk
378 |
379 | DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldObject("android.app.LoadedApk", wr.get(), "mClassLoader"));
380 | // DexClassLoader,将得到的apk载入,父类设置为脱壳apk包名对应的LoadedApk的mClassLoader变量,这样才符合前面说的,既加载源程序,又保持现有程序
381 |
382 | RefInvoke.setFieldObject("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader);
383 | // 将脱壳apk包名对应的LoadedApk的mClassLoader替换为dLoader,即上面的DexClassLoader
384 |
385 | 这里就涉及动态加载的东西了,具体解释见上面代码的注释
386 |
387 | 接着就是调用loadClass看能不能找到MainActivity,也不需要做什么
388 |
389 | try{
390 | Object actObj = dLoader.loadClass("com.herbwen.unshell.MainActivity");
391 | Log.i("demo", "actObj:"+actObj);
392 | }catch(Exception e){
393 | Log.i("demo", "activity:"+Log.getStackTraceString(e));
394 | }
395 |
396 | ## onCreate
397 | 首先是获得ApplicationInfo和Bundle,并将类名赋给appClassName变量
398 |
399 | String appClassName = null;
400 | try {
401 | ApplicationInfo ai = this.getPackageManager().getApplicationInfo(this.getPackageName(),
402 | PackageManager.GET_META_DATA);
403 | Bundle bundle = ai.metaData;
404 | if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
405 | appClassName = bundle.getString("APPLICATION_CLASS_NAME");
406 | } else {
407 | Log.i("demo", "have no application class name");
408 | return;
409 | }
410 | } catch (NameNotFoundException e) {
411 | Log.i("demo", "error:"+Log.getStackTraceString(e));
412 | e.printStackTrace();
413 | }
414 |
415 | 这个APPLICATION_CLASS_NAME和metaData在manifest.xml中有定义,如下所示
416 | ![p4][5]
417 |
418 | 接下来就是一系列的ActivityThread层面的调用与赋值;
419 |
420 | Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {});Log.i("Herrrb", "currentActivityThread:"+currentActivityThread);Object mBoundApplication = RefInvoke.getFieldObject( "android.app.ActivityThread", currentActivityThread, "mBoundApplication");Log.i("Herrrb", "mBoundApplication:"+mBoundApplication);Object loadedApkInfo = RefInvoke.getFieldObject("android.app.ActivityThread$AppBindData", mBoundApplication, "info");Log.i("Herrrb", "loadedApkinfo:"+loadedApkInfo);RefInvoke.setFieldObject("android.app.LoadedApk", "mApplication", loadedApkInfo, null);
421 |
422 | mApplication设置为null
423 |
424 |
425 | Object oldApplication = RefInvoke.getFieldObject(
426 | "android.app.ActivityThread", currentActivityThread,
427 | "mInitialApplication");
428 |
429 | ArrayList mAllApplications = (ArrayList) RefInvoke
430 | .getFieldObject("android.app.ActivityThread",
431 | currentActivityThread, "mAllApplications");
432 |
433 | mAllApplications.remove(oldApplication);
434 |
435 | remove掉oldApplication
436 |
437 |
438 | ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
439 | .getFieldObject("android.app.LoadedApk", loadedApkInfo,
440 | "mApplicationInfo");
441 |
442 | ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
443 | .getFieldObject("android.app.ActivityThread$AppBindData",
444 | mBoundApplication, "appInfo");
445 |
446 | appinfo_In_LoadedApk.className = appClassName;
447 | appinfo_In_AppBindData.className = appClassName;
448 | Log.i("Herrrb", "appClassName:"+appClassName);
449 |
450 | Application app = (Application) RefInvoke.invokeMethod(
451 | "android.app.LoadedApk", "makeApplication", loadedApkInfo,
452 | new Class[] { boolean.class, Instrumentation.class },
453 | new Object[] { false, null });
454 |
455 | 拿到LoadedApk中的appinfo与appBindData中的appinfo,并将其类名设置为源程序的类名,再调用LoadedApk中loadedApkInfo对应的makeApplication方法,参数类型Boolean和Instrumentation,传入参数false和null,然后会返回一个Application类型的实例 app
456 |
457 | 再将ActivityThread中currentActivityThread方法mInitialApplication变量替换为app;
458 | 这里先记一下mContentProvider,ActivityThread的mProviderMap会缓存已经获取的ContentProvider接口或定义在自己进程内的ContentProvider接口;然后这里使用迭代生成的方法,遍历所有接口,将所有接口的localProvider属性对应的mContext变量全部设置为app,这样应该就接管了content provider了
459 |
460 | RefInvoke.setFieldObject("android.app.ActivityThread",
461 | "mInitialApplication", currentActivityThread, app);
462 | ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldObject(
463 | "android.app.ActivityThread", currentActivityThread,
464 | "mProviderMap");
465 | Iterator it = mProviderMap.values().iterator();
466 | while (it.hasNext()) {
467 | Object providerClientRecord = it.next();
468 | Object localProvider = RefInvoke.getFieldObject(
469 | "android.app.ActivityThread$ProviderClientRecord",
470 | providerClientRecord, "mLocalProvider");
471 | RefInvoke.setFieldObject("android.content.ContentProvider",
472 | "mContext", localProvider, app);
473 | }
474 |
475 | 然后执行
476 |
477 | app.onCreate();
478 |
479 | 就开始了源apk的生命周期;
480 |
481 | ## splitPayLoadFromDex
482 | System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
483 | 1. src:源数组;
484 | 2. srcPos:源数组要复制的起始位置
485 | 3. dest:目标数组
486 | 4. destPos:目标数组放置的起始位置
487 | 5. length:复制的长度
488 |
489 |
490 | 拿到长度,并新建apk的byte[]序列(`arraycopy -> ByteArrayInputStream -> DataInputStream -> readInt`
491 |
492 | int ablen = apkdata.length;
493 | byte[] dexlen = new byte[4];
494 | System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
495 | ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
496 | DataInputStream in = new DataInputStream(bais);
497 | int readInt = in.readInt();
498 | System.out.println(Integer.toHexString(readInt));
499 | byte[] newdex = new byte[readInt];
500 |
501 | 然后进行解密
502 |
503 | newdex = decrypt(newdex);
504 |
505 | 写入备份文件,即上面的payload.apk
506 |
507 | File file = new File(apkFileName);
508 | try {
509 | FileOutputStream localFileOutputStream = new FileOutputStream(file);
510 | localFileOutputStream.write(newdex);
511 | localFileOutputStream.close();
512 | } catch (IOException localIOException) {
513 | throw new RuntimeException(localIOException);
514 | }
515 |
516 | 然后找入口,加载so文件,
517 |
518 | ZipInputStream localZipInputStream = new ZipInputStream(
519 | new BufferedInputStream(new FileInputStream(file)));
520 | while (true) {
521 | ZipEntry localZipEntry = localZipInputStream.getNextEntry();
522 | if (localZipEntry == null) {
523 | localZipInputStream.close();
524 | break;
525 | }
526 | String name = localZipEntry.getName();
527 | if (name.startsWith("lib/") && name.endsWith(".so")) {
528 | File storeFile = new File(libPath + "/"
529 | + name.substring(name.lastIndexOf('/')));
530 | storeFile.createNewFile();
531 | FileOutputStream fos = new FileOutputStream(storeFile);
532 | byte[] arrayOfByte = new byte[1024];
533 | while (true) {
534 | int i = localZipInputStream.read(arrayOfByte);
535 | if (i == -1)
536 | break;
537 | fos.write(arrayOfByte, 0, i);
538 | }
539 | fos.flush();
540 | fos.close();
541 | }
542 | localZipInputStream.closeEntry();
543 | }
544 | localZipInputStream.close();
545 |
546 | ## decrypt
547 | 因为是异或,所以解密和加密一样
548 |
549 | private byte[] decrypt(byte[] srcdata) {
550 | for(int i=0;i