diff --git a/.gitignore b/.gitignore index a04707f..bc195fc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ build captures .idea gen +repo +.cxx \ No newline at end of file diff --git a/CommonTool/build.gradle b/CommonTool/build.gradle index 4971862..50ca3d1 100644 --- a/CommonTool/build.gradle +++ b/CommonTool/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' -apply plugin: 'me.tatarka.retrolambda' -apply plugin: 'android-apt' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' android { compileSdkVersion rootProject.ext.compileSdkVersion as Integer @@ -20,10 +21,18 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + androidTestImplementation "androidx.test:runner:1.1.0" + androidTestImplementation "androidx.test.espresso:espresso-core:3.1.0" //单元测试,local unit test, simple dependencies on Android framework testCompile 'junit:junit:4.12' @@ -41,10 +50,6 @@ dependencies { // compile majorFrame.Gson // // //依赖注入 - // complie majorFrame.ButterKnife - // apt majorFrame.aptButterKnifeCompiler - // compile majorFrame.Dagger2 - // apt majorFrame.aptDagger2Compiler // compile 'org.glassfish:javax.annotation:10.0-b28' // // //网络 @@ -84,19 +89,22 @@ dependencies { compile rootProject.ext.support.appcompat compile rootProject.ext.support.recyclerView compile rootProject.ext.support.design + compile rootProject.ext.support.constraintlayout compile majorFrame.Gson - compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha8' //依赖注入 def majorFrame = rootProject.ext.majorFrame - compile majorFrame.ButterKnife - apt majorFrame.aptButterKnifeCompiler compile majorFrame.Dagger2 - apt majorFrame.aptDagger2Compiler + annotationProcessor majorFrame.aptDagger2Compiler + kapt majorFrame.aptDagger2Compiler compile 'org.glassfish:javax.annotation:10.0-b28' + implementation majorFrame.ButterKnife + annotationProcessor majorFrame.aptButterKnifeProcessor + kapt majorFrame.kaptButterKnifeProcessor + //网络 compile majorFrame.okHttp compile majorFrame.loggingInterceptor diff --git a/CommonTool/src/androidTest/java/com/baiiu/commontool/ExampleInstrumentedTest.java b/CommonTool/src/androidTest/java/com/baiiu/commontool/ExampleInstrumentedTest.java index bc20323..b4beeb5 100644 --- a/CommonTool/src/androidTest/java/com/baiiu/commontool/ExampleInstrumentedTest.java +++ b/CommonTool/src/androidTest/java/com/baiiu/commontool/ExampleInstrumentedTest.java @@ -1,13 +1,14 @@ package com.baiiu.commontool; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * Instrumentation test, which will execute on an Android device. diff --git a/CommonTool/src/main/java/com/baiiu/commontool/app/UIUtil.java b/CommonTool/src/main/java/com/baiiu/commontool/app/UIUtil.java index dc58b17..45d4322 100755 --- a/CommonTool/src/main/java/com/baiiu/commontool/app/UIUtil.java +++ b/CommonTool/src/main/java/com/baiiu/commontool/app/UIUtil.java @@ -3,11 +3,13 @@ import android.content.Context; import android.os.Handler; import android.os.Looper; -import android.support.annotation.LayoutRes; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; + +import androidx.annotation.LayoutRes; + import com.squareup.leakcanary.RefWatcher; /** diff --git a/CommonTool/src/main/java/com/baiiu/commontool/base/BaseActivity.java b/CommonTool/src/main/java/com/baiiu/commontool/base/BaseActivity.java index fdb880c..155043a 100755 --- a/CommonTool/src/main/java/com/baiiu/commontool/base/BaseActivity.java +++ b/CommonTool/src/main/java/com/baiiu/commontool/base/BaseActivity.java @@ -1,16 +1,18 @@ package com.baiiu.commontool.base; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + import butterknife.BindView; import butterknife.ButterKnife; import com.baiiu.commontool.R; diff --git a/CommonTool/src/main/java/com/baiiu/commontool/base/BaseFragment.java b/CommonTool/src/main/java/com/baiiu/commontool/base/BaseFragment.java index d825b0a..5f42bbb 100755 --- a/CommonTool/src/main/java/com/baiiu/commontool/base/BaseFragment.java +++ b/CommonTool/src/main/java/com/baiiu/commontool/base/BaseFragment.java @@ -1,6 +1,7 @@ package com.baiiu.commontool.base; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; + import com.baiiu.commontool.app.UIUtil; /** diff --git a/CommonTool/src/main/java/com/baiiu/commontool/base/BaseListAdapter.java b/CommonTool/src/main/java/com/baiiu/commontool/base/BaseListAdapter.java index 8d5f4c9..fba5213 100755 --- a/CommonTool/src/main/java/com/baiiu/commontool/base/BaseListAdapter.java +++ b/CommonTool/src/main/java/com/baiiu/commontool/base/BaseListAdapter.java @@ -1,9 +1,12 @@ package com.baiiu.commontool.base; import android.content.Context; -import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + import com.baiiu.commontool.util.CommonUtil; + import java.util.ArrayList; import java.util.List; diff --git a/CommonTool/src/main/java/com/baiiu/commontool/base/BasePagerAdapter.java b/CommonTool/src/main/java/com/baiiu/commontool/base/BasePagerAdapter.java index dbf2be3..257941f 100755 --- a/CommonTool/src/main/java/com/baiiu/commontool/base/BasePagerAdapter.java +++ b/CommonTool/src/main/java/com/baiiu/commontool/base/BasePagerAdapter.java @@ -1,11 +1,12 @@ package com.baiiu.commontool.base; import android.content.Context; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; import android.view.View; import android.view.ViewGroup; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + import java.util.ArrayList; import java.util.List; diff --git a/CommonTool/src/main/java/com/baiiu/commontool/base/BaseViewHolder.java b/CommonTool/src/main/java/com/baiiu/commontool/base/BaseViewHolder.java index d92481e..beb45c0 100755 --- a/CommonTool/src/main/java/com/baiiu/commontool/base/BaseViewHolder.java +++ b/CommonTool/src/main/java/com/baiiu/commontool/base/BaseViewHolder.java @@ -1,14 +1,17 @@ package com.baiiu.commontool.base; import android.content.Context; -import android.support.annotation.LayoutRes; -import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import butterknife.ButterKnife; + +import androidx.annotation.LayoutRes; +import androidx.recyclerview.widget.RecyclerView; + import com.baiiu.commontool.app.UIUtil; +import butterknife.ButterKnife; + /** * Created by baiiu on 15/11/18. * BaseViewHolder diff --git a/CommonTool/src/main/java/com/baiiu/commontool/view/LoadFrameLayout.java b/CommonTool/src/main/java/com/baiiu/commontool/view/LoadFrameLayout.java index 381d608..8cc3fa0 100755 --- a/CommonTool/src/main/java/com/baiiu/commontool/view/LoadFrameLayout.java +++ b/CommonTool/src/main/java/com/baiiu/commontool/view/LoadFrameLayout.java @@ -2,11 +2,13 @@ import android.content.Context; import android.content.res.TypedArray; -import android.support.annotation.LayoutRes; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; + +import androidx.annotation.LayoutRes; + import com.baiiu.commontool.R; /** diff --git a/CommonTool/src/main/java/com/baiiu/commontool/view/SwipeBackLayout.java b/CommonTool/src/main/java/com/baiiu/commontool/view/SwipeBackLayout.java index 3343ecc..2714ba3 100755 --- a/CommonTool/src/main/java/com/baiiu/commontool/view/SwipeBackLayout.java +++ b/CommonTool/src/main/java/com/baiiu/commontool/view/SwipeBackLayout.java @@ -18,9 +18,6 @@ import android.app.Activity; import android.content.Context; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPager; -import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -28,6 +25,11 @@ import android.webkit.WebView; import android.widget.AbsListView; import android.widget.ScrollView; + +import androidx.core.view.ViewCompat; +import androidx.customview.widget.ViewDragHelper; +import androidx.viewpager.widget.ViewPager; + import com.baiiu.commontool.app.UIUtil; /** diff --git a/RxJava2Study/.gitignore b/JavaLib/.gitignore similarity index 100% rename from RxJava2Study/.gitignore rename to JavaLib/.gitignore diff --git a/JavaLib/build.gradle b/JavaLib/build.gradle new file mode 100644 index 0000000..23d62d3 --- /dev/null +++ b/JavaLib/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'java-library' +apply plugin: 'kotlin' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + // https://mvnrepository.com/artifact/org.ow2.asm/asm + // compile group: 'org.ow2.asm', name: 'asm', version: '7.2' + compile "org.ow2.asm:asm:7.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" +} + +buildscript { + ext.kotlin_version = '1.3.60' + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} +repositories { + mavenCentral() +} +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} diff --git a/JavaLib/src/main/java/com/baiiu/java/Base.java b/JavaLib/src/main/java/com/baiiu/java/Base.java new file mode 100644 index 0000000..296159e --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/Base.java @@ -0,0 +1,9 @@ +package com.baiiu.java; + +public class Base { + public void process() { + System.out.println("start"); + System.out.println("process"); + System.out.println("end"); + } +} \ No newline at end of file diff --git a/JavaLib/src/main/java/com/baiiu/java/Child.java b/JavaLib/src/main/java/com/baiiu/java/Child.java new file mode 100644 index 0000000..8e58243 --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/Child.java @@ -0,0 +1,38 @@ +package com.baiiu.java; + +/** + * author: zhuzhe + * time: 2020-02-21 + * description: + */ +public class Child extends Parent { + + public static final int A = 10; + + public static int b = 10; + + public int c = 10; + private final Object object; + + static { + System.out.println("Child静态代码块 ==== " + b++); + } + + { + System.out.println("Child构造代码块 ==== " + b++ + ", " + c++); + } + + public Child() { + System.out.println("Child构造函数 ==== " + b++ + ", " + c++); + object = "123"; + } + + @Override + public void function() { + System.out.println("Child方法 ==== " + b++ + ", " + c++); + } + + public static void functionStatic() { + System.out.println("Child static方法 ==== " + b++); + } +} diff --git a/JavaLib/src/main/java/com/baiiu/java/MyClass.java b/JavaLib/src/main/java/com/baiiu/java/MyClass.java new file mode 100644 index 0000000..bc96291 --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/MyClass.java @@ -0,0 +1,36 @@ +package com.baiiu.java; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +public class MyClass { + + public static void main(String[] args) throws IOException { + System.out.println("=================================================="); + + //读取 + ClassReader classReader = new ClassReader("com/baiiu/java/Base"); + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + //处理 + ClassVisitor classVisitor = new MyClassVisitor(classWriter); + classReader.accept(classVisitor, ClassReader.SKIP_DEBUG); + byte[] data = classWriter.toByteArray(); + //输出 + + File f = new File("JavaLib/build/classes/java/main/com/baiiu/java/Base.class"); + FileOutputStream fout = new FileOutputStream(f); + fout.write(data); + fout.close(); + System.out.println("now generator cc success!!!!!"); + + + System.out.println("=================================================="); + + new Base().process(); + } + +} diff --git a/JavaLib/src/main/java/com/baiiu/java/MyClassVisitor.java b/JavaLib/src/main/java/com/baiiu/java/MyClassVisitor.java new file mode 100644 index 0000000..db6743a --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/MyClassVisitor.java @@ -0,0 +1,53 @@ +package com.baiiu.java; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class MyClassVisitor extends ClassVisitor implements Opcodes { + MyClassVisitor(ClassVisitor cv) { + super(ASM5, cv); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + cv.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + + //Base类中有两个方法:无参构造以及process方法,这里不增强构造方法 + if (!name.equals("") && mv != null) { + mv = new MyMethodVisitor(mv); + } + return mv; + } + + class MyMethodVisitor extends MethodVisitor implements Opcodes { + MyMethodVisitor(MethodVisitor mv) { + super(Opcodes.ASM5, mv); + } + + @Override + public void visitCode() { + super.visitCode(); + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitLdcInsn("start"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + } + + @Override + public void visitInsn(int opcode) { + if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) + || opcode == Opcodes.ATHROW) { + //方法在返回之前,打印"end" + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitLdcInsn("end"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + } + mv.visitInsn(opcode); + } + } +} \ No newline at end of file diff --git a/JavaLib/src/main/java/com/baiiu/java/Parent.java b/JavaLib/src/main/java/com/baiiu/java/Parent.java new file mode 100644 index 0000000..ac3cb32 --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/Parent.java @@ -0,0 +1,36 @@ +package com.baiiu.java; + +/** + * author: zhuzhe + * time: 2020-02-21 + * description: + */ +public class Parent { + + public static final int A = 10; + + public static int b = 11; + + public int c = 10; + + static { + System.out.println("Parent静态代码块 ==== " + b++); + } + + { + System.out.println("Parent构造代码块 ==== " + b++ + ", " + c++); + } + + + public Parent() { + System.out.println("Parent构造函数 ==== " + b++ + ", " + c++); + } + + public void function() { + System.out.println("Parent方法 ==== " + b++ + ", " + c++); + } + + public static void functionStatic() { + System.out.println("Parent static方法 ==== " + b++); + } +} diff --git a/JavaLib/src/main/java/com/baiiu/java/Test.java b/JavaLib/src/main/java/com/baiiu/java/Test.java new file mode 100644 index 0000000..835687d --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/Test.java @@ -0,0 +1,21 @@ +package com.baiiu.java; + +/** + * author: zhuzhe + * time: 2020-02-21 + * description: + */ +public class Test { + + public static void main(String[] args) { + /* + 父类静态变量、静态代码块 + 子类静态变量、静态代码块 + + 父类成员变量、构造代码块、构造函数 + 子类成员变量、构造代码块、构造函数 + */ + + new Child().function(); + } +} diff --git a/JavaLib/src/main/java/com/baiiu/java/TestLazy.kt b/JavaLib/src/main/java/com/baiiu/java/TestLazy.kt new file mode 100644 index 0000000..76f6341 --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/TestLazy.kt @@ -0,0 +1,30 @@ +package com.baiiu.java + +open class ParentA { + init { + println("ParentA构造") + aaaaa() + } + open fun aaaaa() { + } +} +class ChildA : ParentA() { + private val a by lazy { 1 } + init { + println("ChildA构造") + } + override fun aaaaa() { + println(a) + } +} +class TeastSafe { + private val a by lazy { 1 } + + init { + println(a) + } +} +fun main(args: Array) { + TeastSafe() + ChildA() +} \ No newline at end of file diff --git a/JavaLib/src/main/java/com/baiiu/java/testEnum/EnumTest.java b/JavaLib/src/main/java/com/baiiu/java/testEnum/EnumTest.java new file mode 100644 index 0000000..e360e25 --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/testEnum/EnumTest.java @@ -0,0 +1,33 @@ +package com.baiiu.java.testEnum; + +/** + * Created by zhuzhe.zz on 2024/6/28 + * + * @author zhuzhe.zz@bytedance.com + */ +public class EnumTest { + public State state = State.ONE; + public State2 state2 = State2.ONE2; + public State3 state3 = State3.ONE3; + + public enum State { + ONE, TWO, Three, Four, Five + } + + public enum State2 { + ONE2, TWO2, Three2, Four2, Five2 + } + + public enum State3 { + ONE3, TWO3, Three3, Four3, Five3 + } + + @Override + public String toString() { + return "EnumTest{" + + "state=" + state + + ", state2=" + state2 + + ", state3=" + state3 + + '}'; + } +} diff --git a/JavaLib/src/main/java/com/baiiu/java/testEnum/Test.java b/JavaLib/src/main/java/com/baiiu/java/testEnum/Test.java new file mode 100644 index 0000000..86167e2 --- /dev/null +++ b/JavaLib/src/main/java/com/baiiu/java/testEnum/Test.java @@ -0,0 +1,27 @@ +package com.baiiu.java.testEnum; + +/** + * Created by zhuzhe.zz on 2024/6/28 + * + * @author zhuzhe.zz@bytedance.com + */ +public class Test { + + public static void main(String[] args) { + EnumTest enumTest = new EnumTest(); + EnumTest enumTest1 = new EnumTest(); + EnumTest enumTest2 = new EnumTest(); + + System.out.println("Test::: " + enumTest + ", " + enumTest1 + ", " + enumTest2); + System.out.println("Test::: " + (enumTest.state == enumTest1.state) + ", " + (enumTest1.state == enumTest2.state)); + + int i; + for (i = 0; i < 10; ++i) { + if (i == 5){ + break; + } + } + System.out.println("i = " + i); + } + +} diff --git a/README.md b/README.md index e47085f..588f355 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,24 @@ # LearnTechDemo This project is mean to learn something in Android, now there are: +- hook +尝试各种hook方案,打开testapk -- gradle语言,gradle基础 +- plugin +学习gradle语言,gradle基础 +gradle插件 + +- majjorFrame: +学习主流三方框架 -- 三方框架: - liteOrm 数据库框架 - retrofit 使用和封装 - RxJava 使用和常用操作符封装 -- unitTest 单元测试 - -- someApplications:MyApplication中 - - - RecyclerView FastScroll - RecyclerView快速滑动 - - - test Ultra-Pull-to-Refresh - 上下拉刷新封装,使用LoadFrameLayout - - - 使用ItemDecoration给RecyclerView添加水印 +- unitTest +单元测试 - - Java communication with Js each other +- someApplications +写些demo +研究些东西 \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4df82d2..67ebf34 100644 --- a/build.gradle +++ b/build.gradle @@ -2,13 +2,15 @@ apply from: 'buildSystem/dependencies.gradle' buildscript { + ext.kotlin_version = '1.3.61' repositories { jcenter() + google() } + dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' - classpath 'me.tatarka:gradle-retrolambda:3.2.5' - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -17,6 +19,7 @@ buildscript { allprojects { repositories { jcenter() + google() maven { url "https://jitpack.io" } } } diff --git a/buildSystem/dependencies.gradle b/buildSystem/dependencies.gradle index 17af203..21283c4 100644 --- a/buildSystem/dependencies.gradle +++ b/buildSystem/dependencies.gradle @@ -2,25 +2,24 @@ allprojects { repositories { jcenter() mavenCentral() + google() maven { url "https://jitpack.io" } } } ext { - compileSdkVersion = 25 + compileSdkVersion = 28 buildToolsVersion = '25.0.2' - supportLibVersion = '25.3.1' - targetSdkVersion = 25 - minSdkVersion = 15 + supportLibVersion = '1.0.0' + targetSdkVersion = 28 + minSdkVersion = 19 versionCode = releaseTime() versionName = "2.6" javaSourceCompatibility = JavaVersion.VERSION_1_8 javaTargetCompatibility = JavaVersion.VERSION_1_8 - - retrofitVersion = '2.2.0' okHttpVersion = '3.6.0' okHttpUtilsVersion = '2.6.2' @@ -30,9 +29,9 @@ ext { rxLifeCycleVersion = '1.0' rxBindingVersion = '1.0.1' - GsonVersion = '2.8.0' - GlideVersion = '3.7.0' - ButterKnifeVersion = '8.4.0' + GsonVersion = '2.8.5' + GlideVersion = '4.9.0' + ButterKnifeVersion = '10.1.0' Dagger2Version = '2.0' @@ -45,10 +44,19 @@ ext { //dependencies support = [ - appcompat : "com.android.support:appcompat-v7:${supportLibVersion}", - design : "com.android.support:design:${supportLibVersion}", - recyclerView: "com.android.support:recyclerview-v7:${supportLibVersion}", - cardView : "com.android.support:cardview-v7:${supportLibVersion}", + appcompat : 'androidx.appcompat:appcompat:1.1.0', + annotation : "androidx.annotation:annotation:1.1.0", + corektx : "androidx.core:core-ktx:1.1.0", + + design : "com.google.android.material:material:1.0.0-rc01", + recyclerView : 'androidx.recyclerview:recyclerview:1.0.0', + constraintlayout : 'androidx.constraintlayout:constraintlayout:1.1.3', + cardView : "androidx.cardview:cardview:1.0.0", + lifecycle : "androidx.lifecycle:lifecycle-extensions:2.0.0", + lifecycleProcessor: "androidx.lifecycle:lifecycle-compiler:2.0.0", + legacy : "androidx.legacy:legacy-support-v4:1.0.0", + room : "androidx.room:room-runtime:2.1.0", + roomProcessor : "androidx.room:room-compiler:2.1.0" ] @@ -75,31 +83,33 @@ ext { //Realm, LiteOrm etc ... //json - Gson : "com.google.code.gson:gson:${GsonVersion}", + Gson : "com.google.code.gson:gson:${GsonVersion}", //okHttp - okHttp : "com.squareup.okhttp3:okhttp:${okHttpVersion}", - loggingInterceptor : "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}", - okHttpUtils : "com.zhy:okhttputils:${okHttpUtilsVersion}", + okHttp : "com.squareup.okhttp3:okhttp:${okHttpVersion}", + loggingInterceptor : "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}", + okHttpUtils : "com.zhy:okhttputils:${okHttpUtilsVersion}", //retrofit - retrofit : "com.squareup.retrofit2:retrofit:${retrofitVersion}", - converterGson : "com.squareup.retrofit2:converter-gson:${retrofitVersion}", - adapterRxJava : "com.squareup.retrofit2:adapter-rxjava:${retrofitVersion}", + retrofit : "com.squareup.retrofit2:retrofit:${retrofitVersion}", + converterGson : "com.squareup.retrofit2:converter-gson:${retrofitVersion}", + adapterRxJava : "com.squareup.retrofit2:adapter-rxjava:${retrofitVersion}", //Glide - Glide : "com.github.bumptech.glide:glide:${GlideVersion}", + Glide : "com.github.bumptech.glide:glide:${GlideVersion}", //ButterKnife - ButterKnife : "com.jakewharton:butterknife:${ButterKnifeVersion}", - aptButterKnifeCompiler: "com.jakewharton:butterknife-compiler:${ButterKnifeVersion}", + //ButterKnife + ButterKnife : "com.jakewharton:butterknife:${ButterKnifeVersion}", + aptButterKnifeProcessor : "com.jakewharton:butterknife-compiler:${ButterKnifeVersion}", + kaptButterKnifeProcessor: "com.jakewharton:butterknife-compiler:${ButterKnifeVersion}", //Dagger2 - Dagger2 : "com.google.dagger:dagger:${Dagger2Version}", - aptDagger2Compiler : "com.google.dagger:dagger-compiler:${Dagger2Version}", + Dagger2 : "com.google.dagger:dagger:${Dagger2Version}", + aptDagger2Compiler : "com.google.dagger:dagger-compiler:${Dagger2Version}", //EventBus - EventBus : "org.greenrobot:eventbus:${EventBusVersion}" + EventBus : "org.greenrobot:eventbus:${EventBusVersion}" ] @@ -125,31 +135,10 @@ ext { ] - - - // } - - - - - - - - - - - - - - - - - - // 工具方法 import java.text.DateFormat diff --git a/gradle.properties b/gradle.properties index 1d3591c..ef8dd4b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,18 @@ # Project-wide Gradle settings. - # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. - # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +android.useAndroidX=true +android.enableJetifier=true + +MATRIX_VERSION=0.6.5 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bcdd751..521e2f7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Aug 17 09:57:40 CST 2016 +#Fri Jan 03 12:19:49 CST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/hook/README.md b/hook/README.md new file mode 100644 index 0000000..f9be582 --- /dev/null +++ b/hook/README.md @@ -0,0 +1,4 @@ +## hook + + +http://weishu.me/2016/01/28/understand-plugin-framework-overview/ \ No newline at end of file diff --git a/lib_component/StethoRelease/.gitignore b/hook/hookapp/.gitignore similarity index 100% rename from lib_component/StethoRelease/.gitignore rename to hook/hookapp/.gitignore diff --git a/lib_component/StethoRelease/build.gradle b/hook/hookapp/build.gradle similarity index 55% rename from lib_component/StethoRelease/build.gradle rename to hook/hookapp/build.gradle index c191c24..b6fc5e6 100644 --- a/lib_component/StethoRelease/build.gradle +++ b/hook/hookapp/build.gradle @@ -1,25 +1,30 @@ -apply plugin: 'com.android.library' +apply plugin: 'com.android.application' android { compileSdkVersion rootProject.ext.compileSdkVersion as Integer - buildToolsVersion rootProject.ext.buildToolsVersion as String defaultConfig { minSdkVersion rootProject.ext.minSdkVersion as int targetSdkVersion rootProject.ext.targetSdkVersion as int versionCode rootProject.ext.versionCode as int versionName rootProject.ext.versionName as String + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), + 'proguard-rules.pro' } } } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile rootProject.ext.majorFrame.okHttp + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation rootProject.ext.support.appcompat + + implementation project(":lib_component:LogUtil") + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } diff --git a/hook/hookapp/proguard-rules.pro b/hook/hookapp/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/hook/hookapp/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/hook/hookapp/src/androidTest/java/com/baiiu/hookapp/ExampleInstrumentedTest.java b/hook/hookapp/src/androidTest/java/com/baiiu/hookapp/ExampleInstrumentedTest.java new file mode 100644 index 0000000..f3b4e82 --- /dev/null +++ b/hook/hookapp/src/androidTest/java/com/baiiu/hookapp/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.baiiu.hookapp; + +import android.content.Context; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation() + .getTargetContext(); + + assertEquals("com.baiiu.hookapp", appContext.getPackageName()); + } +} diff --git a/hook/hookapp/src/main/AndroidManifest.xml b/hook/hookapp/src/main/AndroidManifest.xml new file mode 100644 index 0000000..166842a --- /dev/null +++ b/hook/hookapp/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hook/hookapp/src/main/assets/app-debug.apk b/hook/hookapp/src/main/assets/app-debug.apk new file mode 100644 index 0000000..9ba300c Binary files /dev/null and b/hook/hookapp/src/main/assets/app-debug.apk differ diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/MainActivity.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/MainActivity.java new file mode 100644 index 0000000..0fda748 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/MainActivity.java @@ -0,0 +1,108 @@ +package com.baiiu.hookapp; + +import android.app.Activity; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.view.View; + +import androidx.appcompat.app.AppCompatActivity; + +import com.baiiu.hookapp.binderHook.BinderHook2; +import com.baiiu.hookapp.loadedApkHook.CreateClassLoaderHook; +import com.baiiu.hookapp.msHook.AMSHook; +import com.baiiu.hookapp.msHook.PMSHook; +import com.baiiu.hookapp.pathClassLoaderHook.PathClassLoaderHook; +import com.baiiu.hookapp.pathClassLoaderHook2.PathClassLoaderHook2; +import com.baiiu.hookapp.pathClassLoaderHook3.PathClassLoaderHook3; +import com.baiiu.hookapp.startActivityHook.StartActivityHook; +import com.baiiu.hookapp.startStubActivity.StubHook; +import com.baiiu.hookapp.startStubActivity.TargetActivity; +import com.baiiu.library.LogUtil; + +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_CLASS; +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_PACKAGE; + +public class MainActivity extends Activity implements View.OnClickListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LogUtil.d("MainActivity#onCreate"); + + setContentView(R.layout.activity_main); + + findViewById(R.id.btn_startAct).setOnClickListener(this); + findViewById(R.id.btn_clipboard).setOnClickListener(this); + findViewById(R.id.btn_pmshook).setOnClickListener(this); + findViewById(R.id.btn_startStubActInDex).setOnClickListener(this); + findViewById(R.id.btn_startStubActOtherDex_hook_pathClassLoader).setOnClickListener(this); + findViewById(R.id.btn_startStubActOtherDex_hook_loadedApk).setOnClickListener(this); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_startAct: + StartActivityHook.hook(MainActivity.this); + startActivity( + new Intent(MainActivity.this, SecondActivity.class) + ); + break; + case R.id.btn_clipboard: + BinderHook2.hook(); + + ClipboardManager clipboardManager = + (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + LogUtil.e("btn_clipboard: " + + clipboardManager + + ", " + + clipboardManager.getPrimaryClip() + + ", " + + clipboardManager.hasPrimaryClip()); + break; + case R.id.btn_amshook: + AMSHook.hook(); + break; + case R.id.btn_pmshook: + PMSHook.hook(this); + LogUtil.e(MyApplication.getContext() + ", " + this); + LogUtil.e(MyApplication.getContext().getPackageManager() + ", " + this.getPackageManager()); + getPackageManager().getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES); + break; + case R.id.btn_startStubActInDex: + // 打开未在manifest文件中注册的本dex下的activity + StubHook.hook(); + startActivity(new Intent(this, TargetActivity.class)); + break; + case R.id.btn_startStubActOtherDex_hook_pathClassLoader: + // hookPathClassLoader打开别的dex下的activity + + /* + 合并资源方案,资源冲突覆盖 + */ +// PathClassLoaderHook.hook(); + + /* + * 单个资源方案,目前只能加载 插件非AppCompat Theme的apk + */ +// PathClassLoaderHook2.hook(); + + /* + 只 hook classLoader做跳转 + */ + PathClassLoaderHook3.hook(); + + startActivity(new Intent().setClassName(NAME_PACKAGE, NAME_CLASS)); + break; + case R.id.btn_startStubActOtherDex_hook_loadedApk: + // hookLoadedApk打开别的dex下的activity + CreateClassLoaderHook.hook(); + startActivity(new Intent().setClassName(NAME_PACKAGE, NAME_CLASS)); + break; + } + } +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/MyApplication.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/MyApplication.java new file mode 100644 index 0000000..96afeee --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/MyApplication.java @@ -0,0 +1,30 @@ +package com.baiiu.hookapp; + +import android.app.Application; +import android.content.Context; + +import java.lang.reflect.Field; + +/** + * author: zhuzhe + * time: 2020-01-15 + * description: + */ +public class MyApplication extends Application { + private static Context sContext; + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + } + + @Override + public void onCreate() { + super.onCreate(); + sContext = this; + } + + public static Context getContext() { + return sContext; + } +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/SecondActivity.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/SecondActivity.java new file mode 100644 index 0000000..d582e1e --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/SecondActivity.java @@ -0,0 +1,13 @@ +package com.baiiu.hookapp; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +public class SecondActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/binderHook/BinderHook.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/binderHook/BinderHook.java new file mode 100644 index 0000000..a1d3980 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/binderHook/BinderHook.java @@ -0,0 +1,139 @@ +package com.baiiu.hookapp.binderHook; + +import android.annotation.SuppressLint; +import android.content.ClipData; +import android.content.Context; +import android.os.IBinder; +import android.os.IInterface; + +import com.baiiu.library.LogUtil; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; + +/** + * author: zhuzhe + * time: 2020-01-16 + * description: + */ +public class BinderHook { + + /** + * ClipboardManager + *

+ * IClipboard.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE)); + *

+ * hook binder + */ + public static void hook() { + try { + Class serviceManager = Class.forName("android.os.ServiceManager"); + Method getService = serviceManager.getDeclaredMethod("getService", String.class); + getService.setAccessible(true); + final IBinder rawBinder = (IBinder) getService.invoke(null, Context.CLIPBOARD_SERVICE); + + Object hookedBinder = + Proxy.newProxyInstance(serviceManager.getClassLoader(), + new Class[]{IBinder.class}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + LogUtil.e("BinderHook#hook: invoke: " + method); + + if ("queryLocalInterface".equals(method.getName())) { + + // 返回一个伪造的inn + return hookClipboard(rawBinder); + } + + return method.invoke(rawBinder, args); + } + }); + + Field cacheField = serviceManager.getDeclaredField("sCache"); + cacheField.setAccessible(true); + Map cache = (Map) cacheField.get(null); + cache.put(Context.CLIPBOARD_SERVICE, hookedBinder); + } catch (Exception e) { + LogUtil.e("BinderHook#hook: " + e.toString()); + } + + } + + /** + * https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/ClipboardManager.java;bpv=1;bpt=1;l=90?q=ClipboardManager&ss=android%2Fplatform%2Fsuperproject + *

+ * hook clipboard + */ + private static Object hookClipboard(final Object rawBinder) throws Exception { + final Class stub = Class.forName("android.content.IClipboard$Stub"); + final Class iinterface = Class.forName("android.content.IClipboard"); + + Method asInterface = stub.getDeclaredMethod("asInterface", IBinder.class); + asInterface.setAccessible(true); + final Object iClipboard = asInterface.invoke(null, rawBinder); + + return Proxy.newProxyInstance(stub.getClassLoader(), + new Class[]{iinterface}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if ("hasPrimaryClip".equals(method.getName())) { + LogUtil.e("BinderHook#hookClipboard: hasPrimaryClip"); + return true; + } + + if ("getPrimaryClip".equals(method.getName())) { + LogUtil.e("BinderHook#hookClipboard: getPrimaryClip"); + return ClipData.newPlainText(null, "you are hooked"); + } + + + return method.invoke(iClipboard, args); + } + }); + } + + + private static void hookGetSystemService() { + + /* + hook serviceFetcher + */ + try { + Class systemClass = Class.forName("android.app.SystemServiceRegistry"); + Field system_service_fetchers = systemClass.getDeclaredField("SYSTEM_SERVICE_FETCHERS"); + system_service_fetchers.setAccessible(true); + HashMap map = (HashMap) system_service_fetchers.get(null); + + final Object rawClipboardFetcher = map.get(Context.CLIPBOARD_SERVICE); + + + Object serviceFetcher = + Proxy.newProxyInstance(systemClass.getClassLoader(), new Class[]{ + Class.forName("android.app.SystemServiceRegistry$ServiceFetcher") + }, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, + Object[] args) throws Throwable { + + //return hookServiceManager(); + + return method.invoke(rawClipboardFetcher, args); + //return null; + } + }); + + + map.put(Context.CLIPBOARD_SERVICE, serviceFetcher); + } catch (Exception e) { + LogUtil.e("BinderHook#hookGetSystemService: " + e.toString()); + } + } + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/binderHook/BinderHook2.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/binderHook/BinderHook2.java new file mode 100644 index 0000000..78df342 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/binderHook/BinderHook2.java @@ -0,0 +1,101 @@ +package com.baiiu.hookapp.binderHook; + +import android.app.Application; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.IBinder; + +import com.baiiu.hookapp.MyApplication; +import com.baiiu.library.LogUtil; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; + +/** + * author: zhuzhe + * time: 2020-01-16 + * description: + */ +public class BinderHook2 { + + /** + * {@link android.content.ClipboardManager} + * {@link android.os.ServiceManager} + *

+ * https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/ClipboardManager.java + * https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/ServiceManager.java + */ + public static void hook() { + try { + + /* + 1. 获取到原始到clipboard + */ + Class serviceManager = Class.forName("android.os.ServiceManager"); + Method getService = serviceManager.getDeclaredMethod("getService", String.class); + getService.setAccessible(true); + final Object rawBinder = getService.invoke(null, Context.CLIPBOARD_SERVICE); + + + Class stub = Class.forName("android.content.IClipboard$Stub"); + Method asInterface = stub.getDeclaredMethod("asInterface", IBinder.class); + final Object rawClipborad = asInterface.invoke(null, rawBinder); + + + final Object clipboradProxy = Proxy.newProxyInstance(IBinder.class.getClassLoader(), + new Class[]{Class.forName("android.content.IClipboard")}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("hasPrimaryClip".equals(method.getName())) { + LogUtil.e("BinderHook#hookClipboard: hasPrimaryClip"); + return true; + } + + if ("getPrimaryClip".equals(method.getName())) { + LogUtil.e("BinderHook#hookClipboard: getPrimaryClip"); + return ClipData.newPlainText(null, "you are hooked"); + } + + return method.invoke(rawClipborad, args); + } + }); + + + + /* + 2. hook binder queryLocalInterface + */ + Object binderPorxy = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{IBinder.class}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("queryLocalInterface".equals(method.getName())) { + return clipboradProxy; + } + + return method.invoke(rawBinder, args); + } + }); + + /* + 3. 替换serviceManager里的成hook过的binder + */ + Field sCache = serviceManager.getDeclaredField("sCache"); + sCache.setAccessible(true); + Map map = (Map) sCache.get(null); + map.put(Context.CLIPBOARD_SERVICE, binderPorxy); + + + } catch (Exception e) { + LogUtil.e("BinderHook#hook: " + e.toString()); + } + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/CreateClassLoaderHook.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/CreateClassLoaderHook.java new file mode 100644 index 0000000..6641497 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/CreateClassLoaderHook.java @@ -0,0 +1,269 @@ +package com.baiiu.hookapp.loadedApkHook; + +import android.app.ActivityManager; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; + +import com.baiiu.library.LogUtil; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_CLASS; +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_PACKAGE; + +/** + * author: zhuzhe + * time: 2020-01-21 + * description: + */ +public class CreateClassLoaderHook { + private static Map sLoadedApk = new HashMap(); + private static ApplicationInfo sApplicationInfo; + + public static void hook() { + /* + 1. hook classLoader,让mInstrumentation.newActivity()的classLoader里找到目标act + */ + hookLoadedApk(); + hookPM(); + + /* + 2. hook amn,让ams走过权限校验 + */ + hookAMN(); + + + /* + 3. hook mH,接收端,打开目标act + */ + hookMH(); + } + + private static void hookLoadedApk() { + try { + // 先获取到当前的ActivityThread对象 + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); + currentActivityThreadMethod.setAccessible(true); + Object currentActivityThread = currentActivityThreadMethod.invoke(null); + + // 获取到 mPackages 这个静态成员变量, 这里缓存了dex包的信息 + Field mPackagesField = activityThreadClass.getDeclaredField("mPackages"); + mPackagesField.setAccessible(true); + Map mPackages = (Map) mPackagesField.get(currentActivityThread); + LogUtil.e("CreateClassLoaderHook#hookLoadedApk: before: " + mPackages); + + File apkFile = Util.copyFromAssets("app-debug.apk"); + + Class compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo"); + sApplicationInfo = getApplicationInfo(apkFile); + Method getPackageInfoNoCheck = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass); + Object loadedApk = getPackageInfoNoCheck.invoke(currentActivityThread, sApplicationInfo, compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO").get(null)); + + + String odexPath = Util.getPluginOptDexDir(sApplicationInfo.packageName).getPath(); + String libDir = Util.getPluginLibDir(sApplicationInfo.packageName).getPath(); + ClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader()); + Field mClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader"); + mClassLoaderField.setAccessible(true); + mClassLoaderField.set(loadedApk, classLoader); + sLoadedApk.put(sApplicationInfo.packageName, loadedApk); + mPackages.put(sApplicationInfo.packageName, new WeakReference(loadedApk)); + + LogUtil.e("CreateClassLoaderHook#hookLoadedApk: after: " + mPackages); + + } catch (Exception e) { + LogUtil.e("CreateClassLoaderHook#hookLoadedApk: " + e.toString()); + } + } + + private static ApplicationInfo getApplicationInfo(File apkFile) throws Exception { + + Class packageParserClass = Class.forName("android.content.pm.PackageParser"); + Class mPackageClass = Class.forName("android.content.pm.PackageParser$Package"); + Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); + + // 构造Package对象 + Object packageParser = packageParserClass.newInstance(); + Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); + + Object mPackage = parsePackageMethod.invoke(packageParser, apkFile, 0); + LogUtil.e("CreateClassLoaderHook#getApplicationInfo: " + mPackage); + + // 构造packageUserState对象 + Object packageUserState = packageUserStateClass.newInstance(); + + Method generateApplicationInfo = packageParserClass.getDeclaredMethod("generateApplicationInfo", mPackageClass, int.class, packageUserStateClass); + ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfo.invoke(null, mPackage, 0, packageUserState); + String apkPath = apkFile.getPath(); + applicationInfo.sourceDir = apkPath; + applicationInfo.publicSourceDir = apkPath; + Field credentialProtectedDataDir = ApplicationInfo.class.getDeclaredField("credentialProtectedDataDir"); + credentialProtectedDataDir.setAccessible(true); + credentialProtectedDataDir.set(applicationInfo, "/data/user/0/" + applicationInfo.packageName); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + applicationInfo.deviceProtectedDataDir = "/data/user_de/0/" + applicationInfo.packageName; + } + applicationInfo.dataDir = "/data/user/0/" + applicationInfo.packageName; + + return applicationInfo; + } + + private static void hookPM() { + try { + Class activityThread = Class.forName("android.app.ActivityThread"); + Method currentActivityThreadM = activityThread.getDeclaredMethod("currentActivityThread"); + Object currentActivityThread = currentActivityThreadM.invoke(null); + + Field sPackageManager = activityThread.getDeclaredField("sPackageManager"); + sPackageManager.setAccessible(true); + final Object rawPackageManager = sPackageManager.get(currentActivityThread); + + Object proxyPackgeManager = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{Class.forName("android.content.pm.IPackageManager")}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if ("getPackageInfo".equals(method.getName()) && args[0].equals(NAME_PACKAGE)) { + LogUtil.e("CreateClassLoaderHook#hookPM: " + method + args); + PackageInfo packageInfo = new PackageInfo(); + packageInfo.applicationInfo = sApplicationInfo; + packageInfo.packageName = NAME_PACKAGE; + + return packageInfo; + } + + return method.invoke(rawPackageManager, args); + } + }); + + sPackageManager.set(currentActivityThread, proxyPackgeManager); + } catch (Exception e) { + LogUtil.e("CreateClassLoaderHook#hookPM: " + e); + } + + } + + private static boolean isAMSHooked = false; + + private static void hookAMN() { + if (isAMSHooked) { + return; + } + + try { + Field iActivityManagerSingleton = ActivityManager.class.getDeclaredField("IActivityManagerSingleton"); + iActivityManagerSingleton.setAccessible(true); + Object singleton = iActivityManagerSingleton.get(null); + + Field mInstance = Class.forName("android.util.Singleton").getDeclaredField("mInstance"); + mInstance.setAccessible(true); + final Object IActivityManager = mInstance.get(singleton); + + Object IActivityManagerProxy = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{Class.forName("android.app.IActivityManager")}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if ("startActivity".equals(method.getName())) { + int index = 0; + for (; index < args.length; index++) { + if (args[index] instanceof Intent) { + break; + } + } + + LogUtil.e("CreateClassLoaderHook#hookAMS: " + args[index]); + + Intent rawIntent = (Intent) args[index]; + if (NAME_CLASS.equals(rawIntent.getComponent().getClassName())) { + rawIntent.setClassName("com.baiiu.hookapp", "com.baiiu.hookapp.StubActivity"); + } + } + + return method.invoke(IActivityManager, args); + } + }); + + mInstance.set(singleton, IActivityManagerProxy); + isAMSHooked = true; + + } catch (Exception e) { + LogUtil.e("CreateClassLoaderHook#hookAMS: " + e.toString()); + } + } + + private static void hookMH() { + try { + Class activityThread = Class.forName("android.app.ActivityThread"); + Method currentActivityThreadM = activityThread.getDeclaredMethod("currentActivityThread"); + Object currentActivityThread = currentActivityThreadM.invoke(null); + + Field mHF = activityThread.getDeclaredField("mH"); + mHF.setAccessible(true); + Object mH = mHF.get(currentActivityThread); + + Handler.Callback callback = new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case 159: + Object obj = msg.obj; + try { + Class transaction = Class.forName("android.app.servertransaction.ClientTransaction"); + Field mActivityCallbacksF = transaction.getDeclaredField("mActivityCallbacks"); + mActivityCallbacksF.setAccessible(true); + List mActivityCallbacks = (List) mActivityCallbacksF.get(obj); + Object item = mActivityCallbacks.get(0); + + Class launchActivityItemClass = Class.forName("android.app.servertransaction.LaunchActivityItem"); + if (item.getClass().isAssignableFrom(launchActivityItemClass)) { + Field mIntentF = launchActivityItemClass.getDeclaredField("mIntent"); + mIntentF.setAccessible(true); + Intent intent = (Intent) mIntentF.get(item); + if (intent.getComponent().getClassName().equals("com.baiiu.hookapp.StubActivity")) { + mIntentF.set(item, new Intent().setClassName(NAME_PACKAGE, NAME_CLASS)); + } + + Field mInfoF = launchActivityItemClass.getDeclaredField("mInfo"); + mInfoF.setAccessible(true); + ActivityInfo mInfo = (ActivityInfo) mInfoF.get(item); + mInfo.packageName = NAME_PACKAGE; + mInfo.name = NAME_CLASS; + mInfo.applicationInfo = sApplicationInfo; + } + } catch (Exception e) { + LogUtil.e("CreateClassLoaderHook#hookMH: " + e.toString()); + } + + + break; + } + + return false; + } + }; + Field callbackF = Handler.class.getDeclaredField("mCallback"); + callbackF.setAccessible(true); + callbackF.set(mH, callback); + + } catch (Exception e) { + LogUtil.e("CreateClassLoaderHook#hookMH: " + e.toString()); + } + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/CustomClassLoader.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/CustomClassLoader.java new file mode 100644 index 0000000..54d6819 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/CustomClassLoader.java @@ -0,0 +1,16 @@ +package com.baiiu.hookapp.loadedApkHook; + +import dalvik.system.DexClassLoader; + +/** + * author: zhuzhe + * time: 2020-02-03 + * description: + */ +public class CustomClassLoader extends DexClassLoader { + + public CustomClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { + super(dexPath, optimizedDirectory, libraryPath, parent); + } + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/Util.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/Util.java new file mode 100644 index 0000000..073891d --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/loadedApkHook/Util.java @@ -0,0 +1,77 @@ +package com.baiiu.hookapp.loadedApkHook; + +import android.content.res.AssetManager; + +import com.baiiu.hookapp.MyApplication; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +/** + * author: zhuzhe + * time: 2020-02-03 + * description: + */ +public class Util { + +// public static final String NAME_PACKAGE = "com.baiiu.zhihudaily"; + public static final String NAME_PACKAGE = "com.baiiu.testapk"; +// public static final String NAME_CLASS = "com.baiiu.zhihudaily.NewsListActivity"; + public static final String NAME_CLASS = "com.baiiu.testapk.TestActivity"; + + static File copyFromAssets(String sourceName) { + AssetManager am = MyApplication.getContext().getAssets(); + + File outputFile = MyApplication.getContext().getFileStreamPath(sourceName); + + try { + InputStream is = am.open(sourceName); + + FileOutputStream fos = new FileOutputStream(outputFile); + byte[] buffer = new byte[1024]; + int count = 0; + while ((count = is.read(buffer)) > 0) { + fos.write(buffer, 0, count); + } + fos.flush(); + + is.close(); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return outputFile; + } + + + private static File sBaseDir; + + static File getPluginOptDexDir(String packageName) { + return enforceDirExists(new File(getPluginBaseDir(packageName), "odex")); + } + + static File getPluginLibDir(String packageName) { + return enforceDirExists(new File(getPluginBaseDir(packageName), "lib")); + } + + // 需要加载得插件得基本目录 /data/data//files/plugin/ + private static File getPluginBaseDir(String packageName) { + if (sBaseDir == null) { + sBaseDir = MyApplication.getContext().getFileStreamPath("plugin"); + enforceDirExists(sBaseDir); + } + return enforceDirExists(new File(sBaseDir, packageName)); + } + + private static synchronized File enforceDirExists(File sBaseDir) { + if (!sBaseDir.exists()) { + boolean ret = sBaseDir.mkdir(); + if (!ret) { + throw new RuntimeException("create dir " + sBaseDir + "failed"); + } + } + return sBaseDir; + } +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/msHook/AMSHook.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/msHook/AMSHook.java new file mode 100644 index 0000000..c9eda1d --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/msHook/AMSHook.java @@ -0,0 +1,56 @@ +package com.baiiu.hookapp.msHook; + +import android.app.ActivityManager; +import android.os.IBinder; + +import com.baiiu.library.LogUtil; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * author: zhuzhe + * time: 2020-01-17 + * description: + *

+ */ +public class AMSHook { + + /** + * {@link android.app.ActivityManager#IActivityManagerSingleton} + * {@link android.util.Singleton} + *

+ * https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityManager.java + * https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/util/Singleton.java + */ + public static void hook() { + try { + Field singleton = ActivityManager.class.getDeclaredField("IActivityManagerSingleton"); + singleton.setAccessible(true); + Object singleObj = singleton.get(null); + + Class singtonClass = Class.forName("android.util.Singleton"); + Field sInstance = singtonClass.getDeclaredField("mInstance"); + sInstance.setAccessible(true); + + final Object rawAMS = sInstance.get(singleObj); + + Object proxyAMS = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{Class.forName("android.app.IActivityManager")}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + LogUtil.e("AMSHook#hook: " + method + ", " + args); + + return method.invoke(rawAMS, args); + } + }); + sInstance.set(singleObj, proxyAMS); + + + } catch (Exception e) { + LogUtil.e("AMSHook#hook: " + e.toString()); + } + } + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/msHook/PMSHook.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/msHook/PMSHook.java new file mode 100644 index 0000000..b56736f --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/msHook/PMSHook.java @@ -0,0 +1,58 @@ +package com.baiiu.hookapp.msHook; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.IBinder; + +import com.baiiu.library.LogUtil; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * author: zhuzhe + * time: 2020-01-17 + * description: + */ +public class PMSHook { + + /** + * {@link android.app.ActivityThread#sPackageManager} + */ + public static void hook(Context context) { + try { + Class activityThread = Class.forName("android.app.ActivityThread"); + Method currentActivityThreadM = activityThread.getDeclaredMethod("currentActivityThread"); + Object currentActivityThread = currentActivityThreadM.invoke(null); + + Field sPackageManager = activityThread.getDeclaredField("sPackageManager"); + sPackageManager.setAccessible(true); + final Object rawPackageManager = sPackageManager.get(currentActivityThread); + + Object proxyPackgeManager = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{Class.forName("android.content.pm.IPackageManager")}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + LogUtil.e("PMSHook#hook: " + method + args); + + + return method.invoke(rawPackageManager, args); + } + }); + + sPackageManager.set(currentActivityThread, proxyPackgeManager); + + PackageManager packageManager = context.getPackageManager(); + Field mPackageManager = packageManager.getClass().getDeclaredField("mPM"); + mPackageManager.setAccessible(true); + mPackageManager.set(packageManager, proxyPackgeManager); + + } catch (Exception e) { + LogUtil.e("PMSHook#hook: " + e.toString()); + } + + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook/PathClassLoaderHook.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook/PathClassLoaderHook.java new file mode 100644 index 0000000..461c5bf --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook/PathClassLoaderHook.java @@ -0,0 +1,310 @@ +package com.baiiu.hookapp.pathClassLoaderHook; + +import android.app.ActivityManager; +import android.content.Intent; +import android.content.res.AssetManager; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; + +import com.baiiu.hookapp.MainActivity; +import com.baiiu.hookapp.MyApplication; +import com.baiiu.library.LogUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import dalvik.system.DexClassLoader; +import dalvik.system.DexFile; + +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_CLASS; +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_PACKAGE; + +/** + * author: zhuzhe + * time: 2020-01-19 + * description: + */ +public class PathClassLoaderHook { + private static boolean isAMSHooked = false; + + /** + * hook PathClassLoader打开别的dex下的activity + * 资源合并用ResourceHook,但资源id会冲突 + */ + public static void hook() { + /* + 1. hook classLoader,让mInstrumentation.newActivity()的classLoader里找到目标act + */ + hookPathClassLoader(); + + /* + 2. hook amn,让ams走过权限校验 + */ + hookAMN(); + + + /* + 3. hook mH,接收端,打开目标act + */ + hookMH(); + } + + + private static void hookPathClassLoader() { + try { + Class dexPathList = Class.forName("dalvik.system.DexPathList"); + Field dexElementsF = dexPathList.getDeclaredField("dexElements"); + dexElementsF.setAccessible(true); + + + Field pathListF = DexClassLoader.class.getSuperclass().getDeclaredField("pathList"); + pathListF.setAccessible(true); + Object pathList = pathListF.get(MainActivity.class.getClassLoader()); + + Object[] dexElements = (Object[]) dexElementsF.get(pathList); + LogUtil.d("dexElements: " + dexElements); + + // 构造elements + File apkFile = copyFromAssets("app-debug.apk"); + List list = extractAPK(apkFile); + + /* + 不hook的话,如果资源资源id刚好有,则不会崩溃;否则会崩溃 + android.content.res.Resources$NotFoundException: String resource ID #0x7f0c0028 + */ + ResourceHook.hook(MyApplication.getContext(), apkFile); // 资源hook + + Method makeDexElements = Class.forName("dalvik.system.DexPathList").getDeclaredMethod("makeDexElements", List.class, File.class, List.class, ClassLoader.class); + makeDexElements.setAccessible(true); + Object[] toAddElementArray = (Object[]) makeDexElements.invoke(null, list, list.get(0).getParentFile(), new ArrayList<>(), dexPathList.getClassLoader()); + +// Class elementClass = dexElements.getClass().getComponentType(); +// Constructor constructor = elementClass.getConstructor(DexFile.class, File.class); +// Object element = constructor.newInstance(DexFile.loadDex(apkFile.getCanonicalPath(), dexFile.getAbsolutePath(), 0), apkFile); +// LogUtil.e("element: " + element); + +// // 添加 + Class elementClass = dexElements.getClass().getComponentType(); + Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + toAddElementArray.length); + System.arraycopy(dexElements, 0, newElements, 0, dexElements.length); + System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length); + + // 替换 + dexElementsF.set(pathList, newElements); + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook#hookPathClassLoader: " + e.toString()); + } + } + + private static List extractAPK(File apkFile) { + List list = new ArrayList<>(); + + File parentFile = apkFile.getParentFile(); + + // 解压文件目录 + File dexDir = new File(parentFile, "secondary-dexes"); + if (!dexDir.exists()) { + dexDir.mkdir(); + } + + try { + final ZipFile apk = new ZipFile(apkFile); + + ZipEntry dexFile = apk.getEntry("classes.dex"); + int secondaryNumber = 1; + while (dexFile != null) { + File tempFile = new File(dexDir, dexFile.getName()); + tempFile.createNewFile(); + + list.add(tempFile); + + InputStream is = apk.getInputStream(dexFile); + FileOutputStream fos = new FileOutputStream(tempFile); + int len; + byte[] buf = new byte[1024]; + while ((len = is.read(buf)) != -1) { + fos.write(buf, 0, len); + } + fos.close(); + is.close(); + + + ++secondaryNumber; + dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); + } + + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook#extractAPK: Exception:" + e.toString()); + } + + LogUtil.d("PathClassLoaderHook#extractAPK: " + list); + return list; + + } + + private static void hookAMN() { + if (isAMSHooked) { + return; + } + + try { + Field iActivityManagerSingleton = ActivityManager.class.getDeclaredField("IActivityManagerSingleton"); + iActivityManagerSingleton.setAccessible(true); + Object singleton = iActivityManagerSingleton.get(null); + + Field mInstance = Class.forName("android.util.Singleton").getDeclaredField("mInstance"); + mInstance.setAccessible(true); + final Object IActivityManager = mInstance.get(singleton); + + Object IActivityManagerProxy = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{Class.forName("android.app.IActivityManager")}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if ("startActivity".equals(method.getName())) { + int index = 0; + for (; index < args.length; index++) { + if (args[index] instanceof Intent) { + break; + } + } + + LogUtil.d("PathClassLoaderHook#hookAMS: " + args[index]); + + Intent rawIntent = (Intent) args[index]; + if (NAME_CLASS.equals(rawIntent.getComponent().getClassName())) { + rawIntent.setClassName("com.baiiu.hookapp", "com.baiiu.hookapp.StubActivity"); + } + } + + return method.invoke(IActivityManager, args); + } + }); + + mInstance.set(singleton, IActivityManagerProxy); + isAMSHooked = true; + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook#hookAMS: " + e.toString()); + } + } + + private static void hookMH() { + try { + Class activityThread = Class.forName("android.app.ActivityThread"); + Method currentActivityThreadM = activityThread.getDeclaredMethod("currentActivityThread"); + Object currentActivityThread = currentActivityThreadM.invoke(null); + + Field mHF = activityThread.getDeclaredField("mH"); + mHF.setAccessible(true); + Object mH = mHF.get(currentActivityThread); + + Handler.Callback callback = new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case 159: + Object obj = msg.obj; + try { + Class transaction = Class.forName("android.app.servertransaction.ClientTransaction"); + Field mActivityCallbacksF = transaction.getDeclaredField("mActivityCallbacks"); + mActivityCallbacksF.setAccessible(true); + List mActivityCallbacks = (List) mActivityCallbacksF.get(obj); + if (mActivityCallbacks == null || mActivityCallbacks.size() == 0) { + break; + } + Object item = mActivityCallbacks.get(0); + if (!Class.forName("android.app.servertransaction.LaunchActivityItem").isInstance(item)) { + break; + } + + Field mIntentF = Class.forName("android.app.servertransaction.LaunchActivityItem").getDeclaredField("mIntent"); + mIntentF.setAccessible(true); + Intent intent = (Intent) mIntentF.get(item); + + if (intent.getComponent().getClassName().equals("com.baiiu.hookapp.StubActivity")) { + mIntentF.set(item, new Intent().setClassName(NAME_PACKAGE, NAME_CLASS)); + } + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook#hookMH: " + e.toString()); + } + + + break; + } + + return false; + } + }; + Field callbackF = Handler.class.getDeclaredField("mCallback"); + callbackF.setAccessible(true); + callbackF.set(mH, callback); + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook#hookMH: " + e.toString()); + } + } + + private static File copyFromAssets(String sourceName) { + AssetManager am = MyApplication.getContext().getAssets(); + + File outputFile = MyApplication.getContext().getFileStreamPath(sourceName); + deleteFile(outputFile); + + try { + InputStream is = am.open(sourceName); + + FileOutputStream fos = new FileOutputStream(outputFile); + byte[] buffer = new byte[1024]; + int count = 0; + while ((count = is.read(buffer)) > 0) { + fos.write(buffer, 0, count); + } + fos.flush(); + + is.close(); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return outputFile; + } + + private static void deleteFile(File file) { + if (!file.exists()) { + return; + } + if (file.isDirectory()) { + File[] files = file.listFiles(); + + for (File f : files) { + if (f.isDirectory()) { + deleteFile(f); + } else { + f.delete(); + } + } + + } else { + file.delete(); + } + + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook/ResourceHook.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook/ResourceHook.java new file mode 100644 index 0000000..89fe11f --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook/ResourceHook.java @@ -0,0 +1,138 @@ +package com.baiiu.hookapp.pathClassLoaderHook; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; +import android.os.Build; + +import com.baiiu.library.LogUtil; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * author: zhuzhe + * time: 2020-04-16 + * description: + */ +class ResourceHook { + + /* + 合并资源方案: + 将插件资源添加到宿主的Resource中,资源id会冲突,需解决 + */ + static void hook(Context context, File apkFile) { + String newAssetPath = apkFile.getAbsolutePath(); + ApplicationInfo info = context.getApplicationInfo(); + + // 1. 将newAssetPath插入info.splitSourceDirs + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + info.splitSourceDirs = append(info.splitSourceDirs, newAssetPath); + } + + try { + // 2.将newAssetPath插入所有LoadedApk的splitSourceDirs + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread"); + Object activityThread = currentActivityThread.invoke(null); + + Field mPackages = activityThreadClass.getDeclaredField("mPackages"); + mPackages.setAccessible(true); + + Field mResourcePackages = activityThreadClass.getDeclaredField("mResourcePackages"); + mResourcePackages.setAccessible(true); + + Field mSplitResDirs = Class.forName("android.app.LoadedApk").getDeclaredField("mSplitResDirs"); + mSplitResDirs.setAccessible(true); + + for (Field field : new Field[]{mPackages, mResourcePackages}) { + Object value = field.get(activityThread); + + for (Map.Entry> entry : ((Map>) value).entrySet()) { + Object loadedApk = entry.getValue().get(); + + mSplitResDirs.set(loadedApk, append((String[]) mSplitResDirs.get(loadedApk), newAssetPath)); + } + } + + + // 构造插件的assetManager + Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); + addAssetPath.setAccessible(true); + AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance(); + addAssetPath.invoke(newAssetManager, newAssetPath); + + + // 4. 添加资源asset + Class resourcesManagerClass = Class.forName("android.app.ResourcesManager"); + Object resourcesManager = resourcesManagerClass.getDeclaredMethod("getInstance").invoke(null); + + // tinker +// Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences"); +// mResourceReferences.setAccessible(true); +// List> list = (List>) mResourceReferences.get(resourcesManager); +// Field mResourcesImplClass = Resources.class.getDeclaredField("mResourcesImpl"); +// mResourcesImplClass.setAccessible(true); +// +// for (WeakReference reference : list) { +// Resources resources = reference.get(); +// +// Object resourcesImpl = mResourcesImplClass.get(resources); +// +// Field implAssets = resourcesImpl.getClass().getDeclaredField("mAssets"); +// implAssets.setAccessible(true); +// +// AssetManager assetManager = (AssetManager) implAssets.get(resourcesImpl); +// addAssetPath.invoke(assetManager, newAssetPath); +// +//// Field modifersField = Field.class.getDeclaredField("accessFlags"); +//// modifersField.setAccessible(true); +//// modifersField.setInt(implAssets, implAssets.getModifiers() & ~Modifier.FINAL); +//// implAssets.set(resourcesImpl, newAssetManager); +// +// resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); +// } + + Field mResourceImpls = resourcesManagerClass.getDeclaredField("mResourceImpls"); + mResourceImpls.setAccessible(true); + Map> map = (Map) mResourceImpls.get(resourcesManager); + for (Map.Entry> entry : map.entrySet()) { + Object resourcesImpl = entry.getValue().get(); + + Field implAssets = resourcesImpl.getClass().getDeclaredField("mAssets"); + implAssets.setAccessible(true); + + AssetManager assetManager = (AssetManager) implAssets.get(resourcesImpl); + addAssetPath.invoke(assetManager, newAssetPath); +// Field modifersField = Field.class.getDeclaredField("accessFlags"); +// modifersField.setAccessible(true); +// modifersField.setInt(implAssets, implAssets.getModifiers() & ~Modifier.FINAL); +// implAssets.set(resourcesImpl, newAssetManager); + } + + Method appendPath = resourcesManagerClass.getDeclaredMethod("appendLibAssetForMainAssetPath", String.class, String.class); + appendPath.setAccessible(true); + appendPath.invoke(resourcesManager, info.publicSourceDir, "com.baiiu.zhihudaily.vastub"); + + + } catch (Exception e) { + LogUtil.e("ResourceHook#hook: " + e.toString()); + } + } + + private static String[] append(String[] paths, String newPath) { + if (paths == null) { + return null; + } + String[] newPaths = new String[paths.length + 1]; + System.arraycopy(paths, 0, newPaths, 0, paths.length); + newPaths[newPaths.length - 1] = newPath; + + return newPaths; + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook2/PathClassLoaderHook2.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook2/PathClassLoaderHook2.java new file mode 100644 index 0000000..173b05a --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook2/PathClassLoaderHook2.java @@ -0,0 +1,438 @@ +package com.baiiu.hookapp.pathClassLoaderHook2; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.Instrumentation; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.PersistableBundle; +import android.view.ViewGroup; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.view.ContextThemeWrapper; + +import com.baiiu.hookapp.MainActivity; +import com.baiiu.hookapp.MyApplication; +import com.baiiu.hookapp.R; +import com.baiiu.library.LogUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import dalvik.system.DexClassLoader; + +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_CLASS; +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_PACKAGE; + +/** + * author: zhuzhe + * time: 2020-01-19 + * description: + */ +public class PathClassLoaderHook2 { + private static boolean isAMSHooked = false; + private static File sApkFile; + + /** + * hook 插件用独立资源 + */ + public static void hook() { + /* + 1. hook classLoader,让mInstrumentation.newActivity()的classLoader里找到目标act + */ + hookPathClassLoader(); + + /* + 2. hook amn,让ams走过权限校验 + */ + hookAMN(); + + + /* + 3. hook instrumentation,接收端,打开目标act + */ + hookInstrumentation(); + } + + private static void hookPathClassLoader() { + try { + Class dexPathList = Class.forName("dalvik.system.DexPathList"); + Field dexElementsF = dexPathList.getDeclaredField("dexElements"); + dexElementsF.setAccessible(true); + + + Field pathListF = DexClassLoader.class.getSuperclass().getDeclaredField("pathList"); + pathListF.setAccessible(true); + Object pathList = pathListF.get(MainActivity.class.getClassLoader()); + + Object[] dexElements = (Object[]) dexElementsF.get(pathList); + LogUtil.d("dexElements: " + dexElements); + + // 构造elements + sApkFile = copyFromAssets("app-debug.apk"); + List list = extractAPK(sApkFile); +// ResourceHook2.hook(MyApplication.getContext(), apkFile); // 资源hook + + Method makeDexElements = Class.forName("dalvik.system.DexPathList").getDeclaredMethod("makeDexElements", List.class, File.class, List.class, ClassLoader.class); + makeDexElements.setAccessible(true); + Object[] toAddElementArray = (Object[]) makeDexElements.invoke(null, list, list.get(0).getParentFile(), new ArrayList<>(), dexPathList.getClassLoader()); + +// Class elementClass = dexElements.getClass().getComponentType(); +// Constructor constructor = elementClass.getConstructor(DexFile.class, File.class); +// Object element = constructor.newInstance(DexFile.loadDex(apkFile.getCanonicalPath(), dexFile.getAbsolutePath(), 0), apkFile); +// LogUtil.e("element: " + element); + +// // 添加 + Class elementClass = dexElements.getClass().getComponentType(); + Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + toAddElementArray.length); + System.arraycopy(dexElements, 0, newElements, 0, dexElements.length); + System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length); + + // 替换 + dexElementsF.set(pathList, newElements); + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook2#hookPathClassLoader: " + e.toString()); + } + } + + private static List extractAPK(File apkFile) { + List list = new ArrayList<>(); + + File parentFile = apkFile.getParentFile(); + + // 解压文件目录 + File dexDir = new File(parentFile, "secondary-dexes"); + if (!dexDir.exists()) { + dexDir.mkdir(); + } + + try { + final ZipFile apk = new ZipFile(apkFile); + + ZipEntry dexFile = apk.getEntry("classes.dex"); + int secondaryNumber = 1; + while (dexFile != null) { + File tempFile = new File(dexDir, dexFile.getName()); + tempFile.createNewFile(); + + list.add(tempFile); + + InputStream is = apk.getInputStream(dexFile); + FileOutputStream fos = new FileOutputStream(tempFile); + int len; + byte[] buf = new byte[1024]; + while ((len = is.read(buf)) != -1) { + fos.write(buf, 0, len); + } + fos.close(); + is.close(); + + + ++secondaryNumber; + dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); + } + + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook2#extractAPK: Exception:" + e.toString()); + } + + LogUtil.d("PathClassLoaderHook2#extractAPK: " + list); + return list; + + } + + private static void hookAMN() { + if (isAMSHooked) { + return; + } + + try { + Field iActivityManagerSingleton = ActivityManager.class.getDeclaredField("IActivityManagerSingleton"); + iActivityManagerSingleton.setAccessible(true); + Object singleton = iActivityManagerSingleton.get(null); + + Field mInstance = Class.forName("android.util.Singleton").getDeclaredField("mInstance"); + mInstance.setAccessible(true); + final Object IActivityManager = mInstance.get(singleton); + + Object IActivityManagerProxy = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{Class.forName("android.app.IActivityManager")}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if ("startActivity".equals(method.getName())) { + int index = 0; + for (; index < args.length; index++) { + if (args[index] instanceof Intent) { + break; + } + } + + LogUtil.d("PathClassLoaderHook2#hookAMS: " + args[index]); + + Intent rawIntent = (Intent) args[index]; + if (NAME_CLASS.equals(rawIntent.getComponent().getClassName())) { + rawIntent.setClassName("com.baiiu.hookapp", "com.baiiu.hookapp.StubActivity"); + } + } + + return method.invoke(IActivityManager, args); + } + }); + + mInstance.set(singleton, IActivityManagerProxy); + isAMSHooked = true; + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook2#hookAMS: " + e.toString()); + } + } + + private static void hookInstrumentation() { + try { + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread"); + Object activityThread = currentActivityThread.invoke(null); + + Field mInstrumentationF = activityThreadClass.getDeclaredField("mInstrumentation"); + mInstrumentationF.setAccessible(true); + Instrumentation origin = (Instrumentation) mInstrumentationF.get(activityThread); + + VInstrumentation vInstrumentation = new VInstrumentation(origin); + + mInstrumentationF.set(activityThread, vInstrumentation); + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook2#hookInstrumentation: " + e.toString()); + } + } + + private static class VInstrumentation extends Instrumentation { + private final Instrumentation mBase; + + VInstrumentation(Instrumentation base) { + this.mBase = base; + } + + @Override + public boolean onException(Object obj, Throwable e) { + LogUtil.d("VInstrumentation#onException: " + obj + ", " + e.toString()); + return false; + } + + @Override + public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { + if (!intent.getComponent().getClassName().equals("com.baiiu.hookapp.StubActivity")) { + return mBase.newActivity(cl, className, intent); + } + + LogUtil.d("VInstrumentation#newActivity: " + className + ", " + intent); + + intent.setClassName(NAME_PACKAGE, NAME_CLASS); + + return mBase.newActivity(cl, NAME_CLASS, intent); + } + + @Override + public void callActivityOnCreate(Activity activity, Bundle icicle) { + injectActivity(activity); + mBase.callActivityOnCreate(activity, icicle); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) { + injectActivity(activity); + mBase.callActivityOnCreate(activity, icicle, persistentState); + } + + private void injectActivity(Activity activity) { + try { + android.util.Log.d("mLogU", "before hook==============="); + android.util.Log.d("mLogU", "decor_content_parent:" + Integer.toHexString(androidx.appcompat.R.id.decor_content_parent) + "," + activity.getResources().getResourceEntryName(androidx.appcompat.R.id.decor_content_parent)); + android.util.Log.d("mLogU", "app_name:" + +R.string.app_name + ", " + activity.getString(R.string.app_name)); + android.util.Log.d("mLogU", "getText:" + activity.getResources().getText(0x7f0c0026)); + android.util.Log.d("mLogU", "===============before hook"); + } catch (Exception e) { + android.util.Log.d("mLogU", "before hook error:: " + e.toString()); + } + + + try { + Resources resources = ResourceHook2.hook2(activity, sApkFile); + + + Class aClass = activity.getClass(); + while (aClass != null) { + try { + android.util.Log.d("mLogU", aClass.getName()); + Field mResources = aClass.getDeclaredField("mResources"); + mResources.setAccessible(true); + mResources.set(activity, resources); + + } catch (Exception e) { + android.util.Log.d("mLogU", "22222222222: " + e.toString()); + } + + aClass = aClass.getSuperclass(); + } + + + Field mBase = ContextWrapper.class.getDeclaredField("mBase"); + mBase.setAccessible(true); + + Context origin = activity.getBaseContext(); + Field contextImplResource = origin.getClass().getDeclaredField("mResources"); + contextImplResource.setAccessible(true); + contextImplResource.set(origin, resources); + + + Object mPackage = PathClassLoaderHook2.getPackageInfo(sApkFile); + Field applicationInfoF = mPackage.getClass().getDeclaredField("applicationInfo"); + applicationInfoF.setAccessible(true); + ApplicationInfo mApplicationInfo = (ApplicationInfo) applicationInfoF.get(mPackage); + + + VContext vContext = new VContext(activity.getBaseContext(), resources, mApplicationInfo); + mBase.set(activity, vContext); + + activity.setTheme(mApplicationInfo.theme); + + try { + android.util.Log.d("mLogU", "after hook==============="); + android.util.Log.d("mLogU", "decor_content_parent:" + Integer.toHexString(androidx.appcompat.R.id.decor_content_parent) + "," + resources.getResourceEntryName(androidx.appcompat.R.id.decor_content_parent)); + android.util.Log.d("mLogU", "app_name:" + R.string.app_name + ", " + resources.getString(R.string.app_name)); + android.util.Log.d("mLogU", "getText:" + resources.getText(0x7f0c0026)); + android.util.Log.d("mLogU", "getText:" + activity.getText(0x7f0c0026)); + android.util.Log.d("mLogU", "===============after hook"); + } catch (Exception e) { + android.util.Log.d("mLogU", "after hook error: " + e.toString()); + } + + } catch (Exception e) { + LogUtil.e("VInstrumentation#injectActivity: " + e.toString()); + } + } + } + + private static class VContext extends ContextWrapper { + private final Resources resources; + private final ApplicationInfo mApplicationInfo; + + public VContext(Context base, Resources resources, ApplicationInfo applicationInfo) { + super(base); + this.resources = resources; + this.mApplicationInfo = applicationInfo; + } + + @Override + public Context getBaseContext() { + return super.getBaseContext(); + } + + @Override + public ApplicationInfo getApplicationInfo() { + return mApplicationInfo; + } + + @Override + public Resources getResources() { + return resources; + } + + @Override + public AssetManager getAssets() { + return resources.getAssets(); + } + + @Override + public Resources.Theme getTheme() { + Resources.Theme theme = this.resources.newTheme(); + theme.applyStyle(mApplicationInfo.theme, false); + return theme; + } + } + + private static Object getPackageInfo(File apkFile) throws Exception { + Class packageParserClass = Class.forName("android.content.pm.PackageParser"); + Class mPackageClass = Class.forName("android.content.pm.PackageParser$Package"); + Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); + + // 构造Package对象 + Object packageParser = packageParserClass.newInstance(); + Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); + + return parsePackageMethod.invoke(packageParser, apkFile, 1); + } + + + private static File copyFromAssets(String sourceName) { + AssetManager am = MyApplication.getContext().getAssets(); + + File outputFile = MyApplication.getContext().getFileStreamPath(sourceName); + deleteFile(outputFile); + + try { + InputStream is = am.open(sourceName); + + FileOutputStream fos = new FileOutputStream(outputFile); + byte[] buffer = new byte[1024]; + int count = 0; + while ((count = is.read(buffer)) > 0) { + fos.write(buffer, 0, count); + } + fos.flush(); + + is.close(); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return outputFile; + } + + private static void deleteFile(File file) { + if (!file.exists()) { + return; + } + if (file.isDirectory()) { + File[] files = file.listFiles(); + + for (File f : files) { + if (f.isDirectory()) { + deleteFile(f); + } else { + f.delete(); + } + } + + } else { + file.delete(); + } + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook2/ResourceHook2.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook2/ResourceHook2.java new file mode 100644 index 0000000..bd5bc35 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook2/ResourceHook2.java @@ -0,0 +1,164 @@ +package com.baiiu.hookapp.pathClassLoaderHook2; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.Build; + +import com.baiiu.library.LogUtil; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * author: zhuzhe + * time: 2020-04-16 + * description: + */ +class ResourceHook2 { + + /* + 合并资源方案: + 将插件资源添加到宿主的Resource中,资源id会冲突,需解决 + */ + static void hook(Context context, File apkFile) { + String newAssetPath = apkFile.getAbsolutePath(); + ApplicationInfo info = context.getApplicationInfo(); + + // 1. 将newAssetPath插入info.splitSourceDirs + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + info.splitSourceDirs = append(info.splitSourceDirs, newAssetPath); + } + + try { + // 2.将newAssetPath插入所有LoadedApk的splitSourceDirs + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread"); + Object activityThread = currentActivityThread.invoke(null); + + Field mPackages = activityThreadClass.getDeclaredField("mPackages"); + mPackages.setAccessible(true); + + Field mResourcePackages = activityThreadClass.getDeclaredField("mResourcePackages"); + mResourcePackages.setAccessible(true); + + Field mSplitResDirs = Class.forName("android.app.LoadedApk").getDeclaredField("mSplitResDirs"); + mSplitResDirs.setAccessible(true); + + for (Field field : new Field[]{mPackages, mResourcePackages}) { + Object value = field.get(activityThread); + + for (Map.Entry> entry : ((Map>) value).entrySet()) { + Object loadedApk = entry.getValue().get(); + + mSplitResDirs.set(loadedApk, append((String[]) mSplitResDirs.get(loadedApk), newAssetPath)); + } + } + + + // 构造插件的assetManager + Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); + addAssetPath.setAccessible(true); + AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance(); + addAssetPath.invoke(newAssetManager, newAssetPath); + + + // 4. 添加资源asset + Class resourcesManagerClass = Class.forName("android.app.ResourcesManager"); + Object resourcesManager = resourcesManagerClass.getDeclaredMethod("getInstance").invoke(null); + + // tinker +// Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences"); +// mResourceReferences.setAccessible(true); +// List> list = (List>) mResourceReferences.get(resourcesManager); +// Field mResourcesImplClass = Resources.class.getDeclaredField("mResourcesImpl"); +// mResourcesImplClass.setAccessible(true); +// +// for (WeakReference reference : list) { +// Resources resources = reference.get(); +// +// Object resourcesImpl = mResourcesImplClass.get(resources); +// +// Field implAssets = resourcesImpl.getClass().getDeclaredField("mAssets"); +// implAssets.setAccessible(true); +// +// AssetManager assetManager = (AssetManager) implAssets.get(resourcesImpl); +// addAssetPath.invoke(assetManager, newAssetPath); +// +//// Field modifersField = Field.class.getDeclaredField("accessFlags"); +//// modifersField.setAccessible(true); +//// modifersField.setInt(implAssets, implAssets.getModifiers() & ~Modifier.FINAL); +//// implAssets.set(resourcesImpl, newAssetManager); +// +// resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); +// } + + Field mResourceImpls = resourcesManagerClass.getDeclaredField("mResourceImpls"); + mResourceImpls.setAccessible(true); + Map> map = (Map) mResourceImpls.get(resourcesManager); + for (Map.Entry> entry : map.entrySet()) { + Object resourcesImpl = entry.getValue().get(); + + Field implAssets = resourcesImpl.getClass().getDeclaredField("mAssets"); + implAssets.setAccessible(true); + + AssetManager assetManager = (AssetManager) implAssets.get(resourcesImpl); + addAssetPath.invoke(assetManager, newAssetPath); +// Field modifersField = Field.class.getDeclaredField("accessFlags"); +// modifersField.setAccessible(true); +// modifersField.setInt(implAssets, implAssets.getModifiers() & ~Modifier.FINAL); +// implAssets.set(resourcesImpl, newAssetManager); + } + + Method appendPath = resourcesManagerClass.getDeclaredMethod("appendLibAssetForMainAssetPath", String.class, String.class); + appendPath.setAccessible(true); + appendPath.invoke(resourcesManager, info.publicSourceDir, "com.baiiu.zhihudaily.vastub"); + + + } catch (Exception e) { + LogUtil.e("ResourceHook#hook: " + e.toString()); + } + } + + private static String[] append(String[] paths, String newPath) { + if (paths == null) { + return null; + } + String[] newPaths = new String[paths.length + 1]; + System.arraycopy(paths, 0, newPaths, 0, paths.length); + newPaths[newPaths.length - 1] = newPath; + + return newPaths; + } + + private static Resources sResources; + + static Resources hook2(Context context, File apk) { + if (sResources != null) { + return sResources; + } + Resources hostResources = context.getResources(); + + try { + Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); + addAssetPath.setAccessible(true); + AssetManager newAssetManager = AssetManager.class.getDeclaredConstructor().newInstance(); + addAssetPath.invoke(newAssetManager, apk.getAbsolutePath()); + + sResources = new Resources(newAssetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); + + return sResources; + + } catch (Exception e) { + LogUtil.e("ResourceHook#hook2: " + e.toString()); + } + + return hostResources; + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook3/PathClassLoaderHook3.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook3/PathClassLoaderHook3.java new file mode 100644 index 0000000..87bb51d --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook3/PathClassLoaderHook3.java @@ -0,0 +1,490 @@ +package com.baiiu.hookapp.pathClassLoaderHook3; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.Instrumentation; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PersistableBundle; + +import com.baiiu.hookapp.MainActivity; +import com.baiiu.hookapp.MyApplication; +import com.baiiu.hookapp.R; +import com.baiiu.library.LogUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import dalvik.system.DexClassLoader; + +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_CLASS; +import static com.baiiu.hookapp.loadedApkHook.Util.NAME_PACKAGE; +import static java.lang.ClassLoader.getSystemClassLoader; + +/** + * author: zhuzhe + * time: 2020-01-19 + * description: + */ +public class PathClassLoaderHook3 { + private static boolean isAMSHooked = false; + private static File sApkFile; + private static String sOptimizedPath; + + public static void hook() { + /* + 1. hook classLoader,双亲委派代理 + */ + hookPathClassLoader2(); + + /* + 2. hook amn,让ams走过权限校验 + */ + hookAMN(); + + + /* + 3. hook instrumentation,接收端,打开目标act + */ + hookInstrumentation(); + } + + private static void hookPathClassLoader() { + try { + sApkFile = copyFromAssets("app-debug.apk"); + File optimize = new File(sApkFile.getParentFile(), "optimize"); + if (!optimize.exists()) { + optimize.mkdirs(); + } + sOptimizedPath = optimize.getAbsolutePath(); + + Field mLoadedApkF = MyApplication.getContext().getClass().getSuperclass().getDeclaredField("mLoadedApk"); + mLoadedApkF.setAccessible(true); + Object loadedApk = mLoadedApkF.get(MyApplication.getContext()); + + Field mClassLoaderF = loadedApk.getClass().getDeclaredField("mClassLoader"); + mClassLoaderF.setAccessible(true); + ClassLoader origin = (ClassLoader) mClassLoaderF.get(loadedApk); + + /* + + bootClassLoader + + PathClassLoader + + pluginClassLoader + */ + PluginClassLoader pluginClassLoader = new PluginClassLoader(sApkFile.getAbsolutePath(), sOptimizedPath, null, origin); + mClassLoaderF.set(loadedApk, pluginClassLoader); + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook2#hookPathClassLoader: " + e.toString()); + } + } + + // 只hook classLoader + private static void hookPathClassLoader2() { + try { + sApkFile = copyFromAssets("app-debug.apk"); + File optimize = new File(sApkFile.getParentFile(), "optimize"); + if (!optimize.exists()) { + optimize.mkdirs(); + } + sOptimizedPath = optimize.getAbsolutePath(); + + /* + bootClassLoader + + pluginClassLoader + + PathClassLoader + */ + + ClassLoader pathClassLoader = MyApplication.class.getClassLoader(); // pathClassLoader + + ClassLoader bootClassLoader = pathClassLoader.getParent(); // bootClassLoader + + PluginClassLoader pluginClassLoader = new PluginClassLoader(sApkFile.getAbsolutePath(), sOptimizedPath, null, bootClassLoader); + + // 设置父类 + Field parentF = ClassLoader.class.getDeclaredField("parent"); + parentF.setAccessible(true); + Field modifersField = Field.class.getDeclaredField("accessFlags"); + modifersField.setAccessible(true); + modifersField.setInt(parentF, parentF.getModifiers() & ~Modifier.FINAL); + + parentF.set(pathClassLoader, pluginClassLoader); + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook#hookPathClassLoader: " + e.toString()); + } + } + + private static class PluginClassLoader extends DexClassLoader { + private Map map = new HashMap<>(); + + PluginClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { + super(dexPath, optimizedDirectory, librarySearchPath, parent); + map.put("com.baiiu.hookapp.StubActivity", NAME_CLASS); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + LogUtil.e("PluginClassLoader#loadClass: " + name); + + if (map.containsKey(name)) { + name = map.get(name); + } + + return super.loadClass(name); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + LogUtil.e("PluginClassLoader#findClass: " + name); + return super.findClass(name); + } + } + + private static List extractAPK(File apkFile) { + List list = new ArrayList<>(); + + File parentFile = apkFile.getParentFile(); + + // 解压文件目录 + File dexDir = new File(parentFile, "secondary-dexes"); + if (!dexDir.exists()) { + dexDir.mkdir(); + } + + try { + final ZipFile apk = new ZipFile(apkFile); + + ZipEntry dexFile = apk.getEntry("classes.dex"); + int secondaryNumber = 1; + while (dexFile != null) { + File tempFile = new File(dexDir, dexFile.getName()); + tempFile.createNewFile(); + + list.add(tempFile); + + InputStream is = apk.getInputStream(dexFile); + FileOutputStream fos = new FileOutputStream(tempFile); + int len; + byte[] buf = new byte[1024]; + while ((len = is.read(buf)) != -1) { + fos.write(buf, 0, len); + } + fos.close(); + is.close(); + + + ++secondaryNumber; + dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); + } + + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook2#extractAPK: Exception:" + e.toString()); + } + + LogUtil.d("PathClassLoaderHook2#extractAPK: " + list); + return list; + + } + + private static void hookAMN() { + if (isAMSHooked) { + return; + } + + try { + Field iActivityManagerSingleton = ActivityManager.class.getDeclaredField("IActivityManagerSingleton"); + iActivityManagerSingleton.setAccessible(true); + Object singleton = iActivityManagerSingleton.get(null); + + Field mInstance = Class.forName("android.util.Singleton").getDeclaredField("mInstance"); + mInstance.setAccessible(true); + final Object IActivityManager = mInstance.get(singleton); + + Object IActivityManagerProxy = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{Class.forName("android.app.IActivityManager")}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if ("startActivity".equals(method.getName())) { + int index = 0; + for (; index < args.length; index++) { + if (args[index] instanceof Intent) { + break; + } + } + + LogUtil.d("PathClassLoaderHook2#hookAMS: " + args[index]); + + Intent rawIntent = (Intent) args[index]; + if (NAME_CLASS.equals(rawIntent.getComponent().getClassName())) { + rawIntent.setClassName("com.baiiu.hookapp", "com.baiiu.hookapp.StubActivity"); + } + } + + return method.invoke(IActivityManager, args); + } + }); + + mInstance.set(singleton, IActivityManagerProxy); + isAMSHooked = true; + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook2#hookAMS: " + e.toString()); + } + } + + private static void hookInstrumentation() { + try { + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread"); + Object activityThread = currentActivityThread.invoke(null); + + Field mInstrumentationF = activityThreadClass.getDeclaredField("mInstrumentation"); + mInstrumentationF.setAccessible(true); + Instrumentation origin = (Instrumentation) mInstrumentationF.get(activityThread); + + VInstrumentation vInstrumentation = new VInstrumentation(origin); + + mInstrumentationF.set(activityThread, vInstrumentation); + + } catch (Exception e) { + LogUtil.e("PathClassLoaderHook2#hookInstrumentation: " + e.toString()); + } + } + + private static class VInstrumentation extends Instrumentation { + private final Instrumentation mBase; + + VInstrumentation(Instrumentation base) { + this.mBase = base; + } + + @Override + public boolean onException(Object obj, Throwable e) { + LogUtil.d("VInstrumentation#onException: " + obj + ", " + e.toString()); + return false; + } + + @Override + public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { +// if (!intent.getComponent().getClassName().equals("com.baiiu.hookapp.StubActivity")) { +// return mBase.newActivity(cl, className, intent); +// } +// +// LogUtil.d("VInstrumentation#newActivity: " + className + ", " + intent); +// +// intent.setClassName(NAME_PACKAGE, NAME_CLASS); +// + return mBase.newActivity(cl, NAME_CLASS, intent); + } + + @Override + public void callActivityOnCreate(Activity activity, Bundle icicle) { + injectActivity(activity); + mBase.callActivityOnCreate(activity, icicle); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) { + injectActivity(activity); + mBase.callActivityOnCreate(activity, icicle, persistentState); + } + + private void injectActivity(Activity activity) { + try { + android.util.Log.d("mLogU", "before hook==============="); + android.util.Log.d("mLogU", "decor_content_parent:" + Integer.toHexString(androidx.appcompat.R.id.decor_content_parent) + "," + activity.getResources().getResourceEntryName(androidx.appcompat.R.id.decor_content_parent)); + android.util.Log.d("mLogU", "app_name:" + +R.string.app_name + ", " + activity.getString(R.string.app_name)); + android.util.Log.d("mLogU", "getText:" + activity.getResources().getText(0x7f0c0026)); + android.util.Log.d("mLogU", "===============before hook"); + } catch (Exception e) { + android.util.Log.d("mLogU", "before hook error:: " + e.toString()); + } + + + try { + Resources resources = ResourceHook3.hook2(activity, sApkFile); + + + Class aClass = activity.getClass(); + while (aClass != null) { + try { + android.util.Log.d("mLogU", aClass.getName()); + Field mResources = aClass.getDeclaredField("mResources"); + mResources.setAccessible(true); + mResources.set(activity, resources); + + } catch (Exception e) { + android.util.Log.d("mLogU", "22222222222: " + e.toString()); + } + + aClass = aClass.getSuperclass(); + } + + + Field mBase = ContextWrapper.class.getDeclaredField("mBase"); + mBase.setAccessible(true); + + Context origin = activity.getBaseContext(); + Field contextImplResource = origin.getClass().getDeclaredField("mResources"); + contextImplResource.setAccessible(true); + contextImplResource.set(origin, resources); + + + Object mPackage = PathClassLoaderHook3.getPackageInfo(sApkFile); + Field applicationInfoF = mPackage.getClass().getDeclaredField("applicationInfo"); + applicationInfoF.setAccessible(true); + ApplicationInfo mApplicationInfo = (ApplicationInfo) applicationInfoF.get(mPackage); + + + VContext vContext = new VContext(activity.getBaseContext(), resources, mApplicationInfo); + mBase.set(activity, vContext); + + activity.setTheme(mApplicationInfo.theme); + + try { + android.util.Log.d("mLogU", "after hook==============="); + android.util.Log.d("mLogU", "decor_content_parent:" + Integer.toHexString(androidx.appcompat.R.id.decor_content_parent) + "," + resources.getResourceEntryName(androidx.appcompat.R.id.decor_content_parent)); + android.util.Log.d("mLogU", "app_name:" + R.string.app_name + ", " + resources.getString(R.string.app_name)); + android.util.Log.d("mLogU", "getText:" + resources.getText(0x7f0c0026)); + android.util.Log.d("mLogU", "getText:" + activity.getText(0x7f0c0026)); + android.util.Log.d("mLogU", "===============after hook"); + } catch (Exception e) { + android.util.Log.d("mLogU", "after hook error: " + e.toString()); + } + + } catch (Exception e) { + LogUtil.e("VInstrumentation#injectActivity: " + e.toString()); + } + } + } + + private static class VContext extends ContextWrapper { + private final Resources resources; + private final ApplicationInfo mApplicationInfo; + + public VContext(Context base, Resources resources, ApplicationInfo applicationInfo) { + super(base); + this.resources = resources; + this.mApplicationInfo = applicationInfo; + } + + @Override + public Context getBaseContext() { + return super.getBaseContext(); + } + + @Override + public ApplicationInfo getApplicationInfo() { + return mApplicationInfo; + } + + @Override + public Resources getResources() { + return resources; + } + + @Override + public AssetManager getAssets() { + return resources.getAssets(); + } + + @Override + public Resources.Theme getTheme() { + Resources.Theme theme = this.resources.newTheme(); + theme.applyStyle(mApplicationInfo.theme, false); + return theme; + } + } + + private static Object getPackageInfo(File apkFile) throws Exception { + Class packageParserClass = Class.forName("android.content.pm.PackageParser"); + Class mPackageClass = Class.forName("android.content.pm.PackageParser$Package"); + Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); + + // 构造Package对象 + Object packageParser = packageParserClass.newInstance(); + Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); + + return parsePackageMethod.invoke(packageParser, apkFile, 1); + } + + + private static File copyFromAssets(String sourceName) { + AssetManager am = MyApplication.getContext().getAssets(); + + File outputFile = MyApplication.getContext().getFileStreamPath(sourceName); + deleteFile(outputFile); + + try { + InputStream is = am.open(sourceName); + + FileOutputStream fos = new FileOutputStream(outputFile); + byte[] buffer = new byte[1024]; + int count = 0; + while ((count = is.read(buffer)) > 0) { + fos.write(buffer, 0, count); + } + fos.flush(); + + is.close(); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return outputFile; + } + + private static void deleteFile(File file) { + if (!file.exists()) { + return; + } + if (file.isDirectory()) { + File[] files = file.listFiles(); + + for (File f : files) { + if (f.isDirectory()) { + deleteFile(f); + } else { + f.delete(); + } + } + + } else { + file.delete(); + } + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook3/ResourceHook3.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook3/ResourceHook3.java new file mode 100644 index 0000000..d2901a8 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/pathClassLoaderHook3/ResourceHook3.java @@ -0,0 +1,164 @@ +package com.baiiu.hookapp.pathClassLoaderHook3; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.Build; + +import com.baiiu.library.LogUtil; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * author: zhuzhe + * time: 2020-04-16 + * description: + */ +class ResourceHook3 { + + /* + 合并资源方案: + 将插件资源添加到宿主的Resource中,资源id会冲突,需解决 + */ + static void hook(Context context, File apkFile) { + String newAssetPath = apkFile.getAbsolutePath(); + ApplicationInfo info = context.getApplicationInfo(); + + // 1. 将newAssetPath插入info.splitSourceDirs + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + info.splitSourceDirs = append(info.splitSourceDirs, newAssetPath); + } + + try { + // 2.将newAssetPath插入所有LoadedApk的splitSourceDirs + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread"); + Object activityThread = currentActivityThread.invoke(null); + + Field mPackages = activityThreadClass.getDeclaredField("mPackages"); + mPackages.setAccessible(true); + + Field mResourcePackages = activityThreadClass.getDeclaredField("mResourcePackages"); + mResourcePackages.setAccessible(true); + + Field mSplitResDirs = Class.forName("android.app.LoadedApk").getDeclaredField("mSplitResDirs"); + mSplitResDirs.setAccessible(true); + + for (Field field : new Field[]{mPackages, mResourcePackages}) { + Object value = field.get(activityThread); + + for (Map.Entry> entry : ((Map>) value).entrySet()) { + Object loadedApk = entry.getValue().get(); + + mSplitResDirs.set(loadedApk, append((String[]) mSplitResDirs.get(loadedApk), newAssetPath)); + } + } + + + // 构造插件的assetManager + Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); + addAssetPath.setAccessible(true); + AssetManager newAssetManager = AssetManager.class.getConstructor().newInstance(); + addAssetPath.invoke(newAssetManager, newAssetPath); + + + // 4. 添加资源asset + Class resourcesManagerClass = Class.forName("android.app.ResourcesManager"); + Object resourcesManager = resourcesManagerClass.getDeclaredMethod("getInstance").invoke(null); + + // tinker +// Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences"); +// mResourceReferences.setAccessible(true); +// List> list = (List>) mResourceReferences.get(resourcesManager); +// Field mResourcesImplClass = Resources.class.getDeclaredField("mResourcesImpl"); +// mResourcesImplClass.setAccessible(true); +// +// for (WeakReference reference : list) { +// Resources resources = reference.get(); +// +// Object resourcesImpl = mResourcesImplClass.get(resources); +// +// Field implAssets = resourcesImpl.getClass().getDeclaredField("mAssets"); +// implAssets.setAccessible(true); +// +// AssetManager assetManager = (AssetManager) implAssets.get(resourcesImpl); +// addAssetPath.invoke(assetManager, newAssetPath); +// +//// Field modifersField = Field.class.getDeclaredField("accessFlags"); +//// modifersField.setAccessible(true); +//// modifersField.setInt(implAssets, implAssets.getModifiers() & ~Modifier.FINAL); +//// implAssets.set(resourcesImpl, newAssetManager); +// +// resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); +// } + + Field mResourceImpls = resourcesManagerClass.getDeclaredField("mResourceImpls"); + mResourceImpls.setAccessible(true); + Map> map = (Map) mResourceImpls.get(resourcesManager); + for (Map.Entry> entry : map.entrySet()) { + Object resourcesImpl = entry.getValue().get(); + + Field implAssets = resourcesImpl.getClass().getDeclaredField("mAssets"); + implAssets.setAccessible(true); + + AssetManager assetManager = (AssetManager) implAssets.get(resourcesImpl); + addAssetPath.invoke(assetManager, newAssetPath); +// Field modifersField = Field.class.getDeclaredField("accessFlags"); +// modifersField.setAccessible(true); +// modifersField.setInt(implAssets, implAssets.getModifiers() & ~Modifier.FINAL); +// implAssets.set(resourcesImpl, newAssetManager); + } + + Method appendPath = resourcesManagerClass.getDeclaredMethod("appendLibAssetForMainAssetPath", String.class, String.class); + appendPath.setAccessible(true); + appendPath.invoke(resourcesManager, info.publicSourceDir, "com.baiiu.zhihudaily.vastub"); + + + } catch (Exception e) { + LogUtil.e("ResourceHook#hook: " + e.toString()); + } + } + + private static String[] append(String[] paths, String newPath) { + if (paths == null) { + return null; + } + String[] newPaths = new String[paths.length + 1]; + System.arraycopy(paths, 0, newPaths, 0, paths.length); + newPaths[newPaths.length - 1] = newPath; + + return newPaths; + } + + private static Resources sResources; + + static Resources hook2(Context context, File apk) { + if (sResources != null) { + return sResources; + } + Resources hostResources = context.getResources(); + + try { + Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); + addAssetPath.setAccessible(true); + AssetManager newAssetManager = AssetManager.class.getDeclaredConstructor().newInstance(); + addAssetPath.invoke(newAssetManager, apk.getAbsolutePath()); + + sResources = new Resources(newAssetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); + + return sResources; + + } catch (Exception e) { + LogUtil.e("ResourceHook#hook2: " + e.toString()); + } + + return hostResources; + } + + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/startActivityHook/StartActivityHook.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/startActivityHook/StartActivityHook.java new file mode 100644 index 0000000..b2e653a --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/startActivityHook/StartActivityHook.java @@ -0,0 +1,87 @@ +package com.baiiu.hookapp.startActivityHook; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import com.baiiu.library.LogUtil; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * author: zhuzhe + * time: 2020-01-15 + * description: + */ +public class StartActivityHook { + + + public static void hook(Activity activity) { + try { + + Class aClass = Class.forName("android.app.ActivityThread"); + + Method currentActivityThreadMethod = aClass.getDeclaredMethod("currentActivityThread"); + currentActivityThreadMethod.setAccessible(true); + Object currentActivityThread = currentActivityThreadMethod.invoke(null); + + + Field mInstrumentationField = aClass.getDeclaredField("mInstrumentation"); + mInstrumentationField.setAccessible(true); + Object mInstrumentation = mInstrumentationField.get(currentActivityThread); + + + EvilInstrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation); + mInstrumentationField.set(currentActivityThread, evilInstrumentation); + + + try { + Field field = Activity.class.getDeclaredField("mInstrumentation"); + field.setAccessible(true); + field.set(activity, evilInstrumentation); + } catch (Exception e) { + LogUtil.e("HookStartActivity#hook: " + e.toString()); + } + + } catch (Exception e) { + LogUtil.e("HookStartActivity#hook: " + e.toString()); + } + + } + + private static class EvilInstrumentation extends Instrumentation { + Object mInstrumentation; + + EvilInstrumentation(Object instrumentation) { + mInstrumentation = instrumentation; + } + + public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, + Activity target, Intent intent, int requestCode, Bundle options) { + + LogUtil.e("before EvilInstrumentation#execStartActivity"); + + try { + Method execStartActivity = + Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class, + IBinder.class, IBinder.class, + Activity.class, Intent.class, + int.class, + Bundle.class); + + return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, + contextThread, token, + target, + intent, requestCode, options); + } catch (Exception e) { + LogUtil.e("EvilInstrumentation#execStartActivity: " + e.toString()); + throw new RuntimeException(e); + } finally { + LogUtil.e("after EvilInstrumentation#execStartActivity"); + } + } + } + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/startStubActivity/StubHook.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/startStubActivity/StubHook.java new file mode 100644 index 0000000..7a0b3b4 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/startStubActivity/StubHook.java @@ -0,0 +1,136 @@ +package com.baiiu.hookapp.startStubActivity; + +import android.app.ActivityManager; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; + +import com.baiiu.library.LogUtil; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; + +/** + * author: zhuzhe + * time: 2020-01-17 + * description: + */ +public class StubHook { + + /** + * 打开未在manifest文件中注册的本dex下的activity + */ + public static void hook() { + + // 1. hook 发送端 + hookAMS(); + + // 2. hook 接收端 + hookActivityThread(); + } + + /** + * {@link com.baiiu.hookapp.msHook.AMSHook} + */ + private static void hookAMS() { + try { + Field iActivityManagerSingletonF = ActivityManager.class.getDeclaredField("IActivityManagerSingleton"); + iActivityManagerSingletonF.setAccessible(true); + Object singleton = iActivityManagerSingletonF.get(null); + + Field mInstance = Class.forName("android.util.Singleton").getDeclaredField("mInstance"); + mInstance.setAccessible(true); + final Object rawAMS = mInstance.get(singleton); + + Object proxyAMS = Proxy.newProxyInstance(IBinder.class.getClassLoader(), new Class[]{Class.forName("android.app.IActivityManager")}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { +// LogUtil.e("StubHook#hook: " + method + ", " + args); + + if ("startActivity".equals(method.getName())) { + int index = 0; + for (; index < args.length; index++) { + if (args[index] instanceof Intent) { + break; + } + } + + Intent rawItent = (Intent) args[index]; + if (rawItent.getComponent().getClassName().equals("com.baiiu.hookapp.startStubActivity.TargetActivity")) { + Intent intent = new Intent(); + intent.setClassName("com.baiiu.hookapp", "com.baiiu.hookapp.StubActivity"); + args[index] = intent; + } + } + + return method.invoke(rawAMS, args); + } + }); + + mInstance.set(singleton, proxyAMS); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * {@link android.app.ActivityThread#mH} + *

+ * 拦截callBack + */ + private static void hookActivityThread() { + try { + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); + Object currentActivityThread = currentActivityThreadMethod.invoke(null); + + Field mH = activityThreadClass.getDeclaredField("mH"); + mH.setAccessible(true); + Handler handler = (Handler) mH.get(currentActivityThread); + + Handler.Callback callback = new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case 159: + Object obj = msg.obj; + try { + Class transaction = Class.forName("android.app.servertransaction.ClientTransaction"); + Field mActivityCallbacksF = transaction.getDeclaredField("mActivityCallbacks"); + mActivityCallbacksF.setAccessible(true); + List mActivityCallbacks = (List) mActivityCallbacksF.get(obj); + Object item = mActivityCallbacks.get(0); + + Field mIntentF = Class.forName("android.app.servertransaction.LaunchActivityItem").getDeclaredField("mIntent"); + mIntentF.setAccessible(true); + Intent intent = (Intent) mIntentF.get(item); + if (intent.getComponent().getClassName().equals("com.baiiu.hookapp.StubActivity")) { + mIntentF.set(item, new Intent().setClassName("com.baiiu.hookapp.startStubActivity", TargetActivity.class.getName())); + } + } catch (Exception e) { + LogUtil.e("StubHook#hookActivityThread: " + e.toString()); + } + + + break; + } + + return false; + } + }; + Field mCallback = Handler.class.getDeclaredField("mCallback"); + mCallback.setAccessible(true); + mCallback.set(handler, callback); + + + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/hook/hookapp/src/main/java/com/baiiu/hookapp/startStubActivity/TargetActivity.java b/hook/hookapp/src/main/java/com/baiiu/hookapp/startStubActivity/TargetActivity.java new file mode 100644 index 0000000..d18e7f5 --- /dev/null +++ b/hook/hookapp/src/main/java/com/baiiu/hookapp/startStubActivity/TargetActivity.java @@ -0,0 +1,57 @@ +package com.baiiu.hookapp.startStubActivity; + +import android.annotation.SuppressLint; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.baiiu.hookapp.R; +import com.baiiu.library.LogUtil; + +/** + * author: zhuzhe + * time: 2020-01-17 + * description: + */ +@SuppressLint("Registered") +public class TargetActivity extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_target); + + LogUtil.e("TargetActivity#onCreate"); + } + + @Override + protected void onStart() { + super.onStart(); + LogUtil.e("TargetActivity#onStart"); + } + + @Override + protected void onResume() { + super.onResume(); + LogUtil.e("TargetActivity#onResume"); + } + + @Override + protected void onPause() { + super.onPause(); + LogUtil.e("TargetActivity#onPause"); + } + + @Override + protected void onStop() { + super.onStop(); + LogUtil.e("TargetActivity#onStop"); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + LogUtil.e("TargetActivity#onDestroy"); + } +} diff --git a/hook/hookapp/src/main/res/drawable-v24/ic_launcher_foreground.xml b/hook/hookapp/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/hook/hookapp/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/hook/hookapp/src/main/res/drawable/ic_launcher_background.xml b/hook/hookapp/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/hook/hookapp/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hook/hookapp/src/main/res/layout/activity_main.xml b/hook/hookapp/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..af844c0 --- /dev/null +++ b/hook/hookapp/src/main/res/layout/activity_main.xml @@ -0,0 +1,62 @@ + + + +