代理、反射、Hook

创建对象:newInstance
调用方法:invoke()
设置参数:field.set(obj,21);

反射机制

image.png|900

what

JAVA反射机制是在运行状态中,对于任意一个类 (class文件),都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 
动态获取类中信息,就是java反射 。可以理解为对类的解剖。

Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Person {

private int age;
private String name;

public Person(String name,int age) {
super();
this.age = age;
this.name = name;

System.out.println("Person param run..."+this.name+":"+this.age);

}
public Person() {
super();
System.out.println("person run");
}

public void show(){
System.out.println(name+"...show run..."+age);
}

private void privateMethod(){
System.out.println(" method run ");
}

public void paramMethod(String str,int num){
System.out.println("paramMethod run....."+str+":"+num);

}
public static void staticMethod(){
System.out.println(" static method run......");
}
}

如何获取字节码文件对象呢

要想要对字节码文件进行解剖,必须要有字节码文件对象.

获取Class对象的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class ReflectDemo {

/**
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException {
getClassObject_3();
}

/*
* 获取字节码对象的方式:
* Object类中的getClass()方法的。
* 想要用这种方式,必须要明确具体的类,并创建对象。
* 麻烦 .
*
*/
public static void getClassObject_1(){

Person p = new Person();
Class clazz = p.getClass();

Person p1 = new Person();
Class clazz1 = p1.getClass();

System.out.println(clazz==clazz1);
}

/*
* 方式二:
* 任何数据类型都具备一个静态的属性.class来获取其对应的Class对象。
* 相对简单,但是还是要明确用到类中的静态成员。
* 还是不够扩展。
*
*/
public static void getClassObject_2() {

Class clazz = Person.class;

Class clazz1 = Person.class;
System.out.println(clazz==clazz1);
}

/*
* 方式三:
* 只要通过给定的类的 字符串名称就可以获取该类,更为扩展。
* 可是用Class类中的方法完成。
* 该方法就是forName.
* 这种方式只要有名称即可,更为方便,扩展性更强。
*/
public static void getClassObject_3() throws ClassNotFoundException {

String className = "cn.test.bean.Person";

Class clazz = Class.forName(className);

System.out.println(clazz);
}

}

获取 Class 中的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class ReflectDemo2 {

/**
* @param args
* @throws Exception
* @throws InstantiationException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, Exception {
createNewObject_2();
}

public static void createNewObject_2() throws Exception {

// cn.test.bean.Person p = new cn.test.bean.Person("小强",39);

/*
* 当获取指定名称对应类中的所体现的对象时,
* 而该对象初始化不使用空参数构造该怎么办呢?
* 既然是通过指定的构造 函数进行对象的初始化,
* 所以应该先获取到该构造函数。 通过字节码文件对象即可完成。
* 该方法是:getConstructor(paramterTypes);
*
*/
String name = "cn.test.bean.Person";
//找寻该名称类文件,并加载进内存,并产生Class对象。
Class clazz = Class.forName(name);
//获取到了指定的构造函数对 象。
Constructor constructor = clazz.getConstructor(String.class,int.class);

//通过该构造器对象的newInstance方法进行对象的初始化。
Object obj = constructor.newInstance("小明",38);



}

public static void createNewObject() throws ClassNotFoundException, InstantiationException, IllegalAccessException{

//早期:new时候,先根据被new的类的名称找寻该类的字节码文件,并加载进内存,
// 并创建该字节码文件对象,并接着创建该字节文件的对应的Person对象.
// cn.test.bean.Person p = new cn.test.bean.Person();

//现在:
String name = "cn.test.bean.Person";
//找寻该名称类文件,并加载进内存,并产生Class对象。
Class clazz = Class.forName(name);
//如何产生该类的对象呢?
Object obj = clazz.newInstance();
}
}

获取 Class 中的字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	/*
* 获取字节码文件中的字段。
*/
public static void getFieldDemo() throws Exception {

Class clazz = Class.forName("cn.test.bean.Person");
Field field = null;//clazz.getField("age");//只能获取公有的,
field = clazz.getDeclaredField("age");//只获取本类,但包含私有。
//对私有字段的访问取消权限检查。暴力访问。
field.setAccessible(true);
Object obj = clazz.newInstance();
field.set(obj, 89);


Object o = field.get(obj);
System.out.println(o);
// cn.test.bean.Person p = new cn.test.bean.Person();
// p.age = 30;

}

获取 Class 中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
	public static void getMethodDemo_3() throws Exception {
Class clazz = Class.forName("cn.test.bean.Person");
Method method = clazz.getMethod("paramMethod", String.class,int.class);
Object obj = clazz.newInstance();
method.invoke(obj, "小强",89);


}

public static void getMethodDemo_2() throws Exception {

Class clazz = Class.forName("cn.test.bean.Person");

Method method = clazz.getMethod("show", null);//获取空参数一般方法。

// Object obj = clazz.newInstance();
Constructor constructor = clazz.getConstructor(String.class,int.class);
Object obj = constructor.newInstance("小明",37);

method.invoke(obj, null);

}

/*
* 获取指定Class中的所有公共函数。
*/
public static void getMethodDemo() throws Exception {

Class clazz = Class.forName("cn.test.bean.Person");

Method[] methods = clazz.getMethods();//获取的都是公有的方法。
methods = clazz.getDeclaredMethods();//只获取本类中所有方法,包含私有。
for(Method method : methods){
System.out.println(method);
}
}

泛型类的真实类型

通过Class类上的 getGenericSuperclass() 或者 getGenericInterfaces() 获取父类或者接口的类型,然后通过ParameterizedType.getActualTypeArguments()
Actual Type Arguments 实际的类型参数
get Generic Super class 获取泛型超类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RealType<T>{

private Class<T> clazz;
// 使用反射技术得到T的真实类型
public Class getRealType(){
// 获取当前new的对象的泛型的父类类型
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
// 获取第一个类型参数的真实类型
this.clazz = (Class<T>) pt.getActualTypeArguments()[0];
return clazz;
}

}

动态代理

通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,扩展目标对象的功能。
代理对象拦截真实对象的方法调用,在真实对象调用前/后实现自己的逻辑调用
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

动态代理的用途与装饰模式很相似,就是为了对某个对象进行增强。所有使用装饰者模式的案例都可以使用动态代理来替换。

Image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* subject(抽象主题角色):
* 真实主题与代理主题的共同接口。
*/
interface Subject {
    void sellBook();
}


/**
* ReaISubject(真实主题角色):
* 定义了代理角色所代表的真实对象。
*/
public class RealSubject implements Subject {

    @Override
    public void sellBook() {
        System.out.println("出版社卖书");
    }
}



/**
* Proxy(代理主题角色):
* 含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真实主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。
*/

public class ProxySubject implements Subject {

    private RealSubject realSubject;

    @Override
    public void sellBook() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        sale();
        realSubject.sellBook();
        give();
    }

    public void sale() {
        System.out.println("打折");
    }

    public void give() {
        System.out.println("送优惠券");
    }
}


public class Main {

    public static void main(String[] args) {

        //静态代理(我们自己静态定义的代理类)
        ProxySubject proxySubject = new ProxySubject();
        proxySubject.sellBook();

        //动态代理(通过程序动态生成代理类,该代理类不是我们自己定义的。而是由程序自动生成)
        RealSubject realSubject = new RealSubject();
        MyHandler myHandler = new MyHandler();
        myHandler.setProxySubject(realSubject);
        Subject subject = (Subject)
Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
         realSubject.getClass().getInterfaces(), myHandler);
        subject.sellBook();
    }
}

public class MyHandler implements InvocationHandler {
    private RealSubject realSubject;

    public void setProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    /**
     * @param proxy   指代我们所代理的那个真实对象
     * @param method   指代的是我们所要调用真实对象的某个方法的Method对象
     * @param args    指代的是调用真实对象某个方法时接受的参数
     * @reurn
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        sale();
        proxy = method.invoke(realSubject, args);
        give();
        return proxy;
    }

    public void sale() {
        System.out.println("打折");
    }

    public void give() {
        System.out.println("送优惠券");
    }
}

三个参数

接口类型(有一个接口)才能动态代理
第二个参数是,Java 规定必须实现个接口,第三个是代理逻辑,要把要代理的类传进去 InvocationHandler
unknown_filename.2

代理 startActivity

unknown_filename.7|600

unknown_filename.8|600

HOOK

理解 Android Hook 技术以及简单实战 - 简书

使用反射修改 Android 系统底层的方法和宇段
Hook ,就是使用反射修改 Android 系统底层的方法和宇段

Hook(钩子): Android 操作系统中系统维护着自己的一套事件分发机制,那么 Hook 就是在事件传送到终点前截获并监控事件的传输,并修改事件流程的过程。其实就是代理模式+反射

1
2
3
4
5
public static void showToast(Context context, CharSequence cs, int length) {
Toast toast = Toast.makeText(context,cs,length);
hook(toast);
toast.show();
}

Hook 的选择点:
静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。

Hook 过程:
寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
选择合适的代理方式,如果是接口可以用动态代理。
偷梁换柱——用代理对象替换原始对象。
Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作

应用

  • Hook指定应用注入广告
  • 修复bug
  • App登录劫持

登录界面上面的用户信息都存储在EditText控件上,然后通过用户手动点击“登录”按钮才会将上面的信息发送至服务器端去验证账号与密码是否正确。这样就很简单了,黑客们只需要找到开发者在使用EditText控件的getText方法后进行网络验证的方法,Hook该方法,就能劫持到用户的账户与密码了

hook练习


代理、反射、Hook
http://peiniwan.github.io/2024/04/afdce5fadbd9.html
作者
六月的雨
发布于
2024年4月6日
许可协议