首页
社区
课程
招聘
[原创]java和smali汇编
发表于: 2024-6-22 11:23 2675

[原创]java和smali汇编

2024-6-22 11:23
2675

关注菜狗,一起进步!

本公众号分享的所有技术仅用于学习交流,如作他用所承受的法律责任一概与作者无关。如有错漏,欢迎留言交流。预计阅读全文耗时:70min

目录

目录思维导图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
一、java基础
├── java变量、 基本数据类型、条件语句、循环语句、字符和字符串及字符串操作、数组、instanceof
├── 应用数据类型与基本数据类型之间的转换
├── Java中List、Set、Map
├── 包的概念、静态成员、构造函数、函数重载、抽象类、接口、继承、多态、内部类
├── 异常、反射、注解
└── 多线程
 
二、smali汇编
├── Dalvik字节码
├── Smali汇编
│   ├── 寄存器命名方法:V与P
│   ├── 类型描述符
│   └── 指令
├── smali文件详解
└── 实战
    ├── 实战1:Crackme
    ├── 实战2:crackme0502
    └── 实战3:Androideasy

一、java基础

java变量、 基本数据类型、条件语句、循环语句、字符和字符串及字符串操作、数组、instanceof

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
import java.util.Scanner;
import java.util.Arrays;
 
/**
 * 对这个类进行注释
 * Main.java
 * 类名:首字母大写且驼峰式命名,例如:Main、UserInfo、PersonApplication
 * 类修饰符:public、default(不写)
 * 一个文件中最多只能有一个public类 且文件名必须和public类名一致。
 * 如果文件中有多个类,文件名与public类名一致。
 * 如果文件中有多个类且无public类,文件名可以是任意类名。
 */
public class Main {
    /**
     * 对这个方法进行注释
     * 主函数必须要是main
     * 方法名: 首字母小写且驼峰式命名,后面的每一个单词的首字母大写。例如:getName()
     * 类中成员修饰符:public、private、protected、default(不写)
     * void表示方法没有返回值。如果有返回则必须在定义方式就要写明他是返回的什么类型。
     *
     * @param args 字符数组
     */
    public static void main(String[] args) {
        String name = ""; // 单行注释,变量
        int age = 24;
        final char gender = 'M'; // 常量
 
        // 输出
        System.out.print("please input name:"); // 尾部不加换行 please input name:cisco
        // 输入
        Scanner input = new Scanner(System.in);
        name = input.nextLine();
        System.out.println(name + ',' + gender + ',' + age); // 尾部自动加换行 cisco,M,24
 
        // 静态成员,无需实例化就可以指定调用。
        ControlFlow.switchFun(gender); // man
        // 非静态成员需实例化才可以指定调用。
        ControlFlow obj = new ControlFlow();
        obj.ifFun(age); // 大叔
 
        obj.whileFun();
        /*
         * 多行注释
         * 0,执行中...
         * 1,执行中...
         * 2,执行中...
         * 3,退出while...
         */
        obj.doWhileFun();
        /*
         * 3,执行中... do-while至少执行一次do{}。即使一开始就不满足条件((count = 3) < 3),也会执行一次do{}里面的内容
         * 4,退出do-while...
         */
        obj.forFun();
        /*
         * 0 1 2 3 4 continue 6 7 8 9
         */
        /*
         * 基本数据类型
         * byte,字节 【1字节】表示范围:-128 ~ 127 即:-2^7 ~ 2^7 -1
         * short,短整型 【2字节】表示范围:-32768 ~ 32767
         * int,整型 【4字节】表示范围:-2147483648 ~ 2147483647
         * long,长整型 【8字节】表示范围:-9223372036854775808 ~ 9223372036854775807
         * float:单精度浮点数,占用4字节,表示范围约为 ±1.4E-45 到 ±3.4E+38,精度约为6-7位有效数字。
         * double:双精度浮点数,占用8字节,表示范围约为 ±4.9E-324 到 ±1.7E+308,精度约为15-16位有效数字。
         * boolean:布尔类型,占用1字节,表示值为 true 或 false。
         */
        DataType obj2 = new DataType();
         
        /*
         * java的String和char在内存中总是以UTF-16编码表示(UTF-16是一种Unicode编码格式,它使用16位(2字节)来表示字符。UTF-16能够表示Unicode中的所有字符。)
         * 每一个字符用一个码点编码(\u0000到\u10FFFF),用一个char(BMP字符,U+0000到U+FFFF)或者两个char(BMP外的字符)表示一个码点。
         * 由于网络传输或存储需要,把unicode编码的字符串压缩成UTF-8、GBK编码的字节数组.
         *
         * Char:'a','b','c'.在Java中,char类型是一个16位无符号整数,每一个字符用一个码点编码,只能够表示BMP字符(码点范围\u0000到\uFFFF).
         *    字符 'A' 的码点是 U+0041,在UTF-16中表示为 0x0041。'中' 的码点是 U+4E2D,在UTF-16中表示为 0x4E2D。
         *    但对于超出BMP的字符'????'(对应的码点\u10348),UTF-16使用一对16位的编码单元(代理对)来表示:
         *        基础码点 P = 0x10348
         *        基数值 base = P - 0x10000 = 0x348
         *        高位代理 highSurrogate = (base >>> 10) + 0xD800 = 0xD800
         *        低位代理 lowSurrogate = (base & 0x3FF) + 0xDC00 = 0xDF48
         *        计算后码点 \u10348 在UTF-16中表示为 D800 DF48 占32bit,显然用char c = '????';是不合法的,得用String c = '????';
         * 字符串: "????abc",在C语言中字符串由char组成并且结尾处加'\0'.但在Java中,字符串是由码点组成的,每个码点对应1个char或者2格char.
         * BMP(Basic Multilingual Plane,基本多文种平面)是Unicode字符集中最重要和最常用的部分,它涵盖了从\u0000到\uFFFF的字符范围。BMP包括大多数常用的字符,如拉丁字母、数字、标点符号、常见的汉字、希腊字母、西里尔字母等。
         */
        obj2.byteToString(); // 字符串是:aid=246387571&auto_play=0&cid=289008441&did=KREhESMUJh8tFCEVaRVpE2taPQk7WChCJg&epid=0&ftime=1627100937&lv=0&mid=0&part=1&sid=0&stime=1627104372&sub_type=0&type=3
        obj2.stringToByte();
        /*
         * [-42, -48, -50, -60, -41, -42, -73, -5] GBK编码
         * [-28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90] UTF-8编码
         */
        // 字符串的定义
        String v11 = "中文字符";
        System.out.println(v11); // 中文字符
        String v12 = new String("中文字符");
        System.out.println(v12); // 中文字符
        String v13 = new String(new byte[] { -28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90 }); // 可以看出这里默认是以UTF-8编码读取字节数组
        System.out.println(v13); // 中文字符
        try {
            String v14 = new String(new byte[] { -42, -48, -50, -60, -41, -42, -73, -5 }, "GBK");
            System.out.println(v14); // 中文字符
        } catch (Exception e) {
 
        }
        String v15 = new String(new char[] { '中', '文', '字', '符' }); // 这些汉字字符都在BMP范围内(U+0000到U+FFFF),可以用一个16位的char表示。这个字符数组可以直接传给String的构造函数,输出“中文字符”。
        System.out.println(v15); // 中文字符
 
        // 字符串的方法
        String origin = "cisco 字符串 使用方法";
        char v1 = origin.charAt(6); // 指定字符
        System.out.println(v1); // 字
        int len = origin.length(); // 长度
        System.out.println(len); // 14
        for (int i = 0; i < len; i++) {
            char item = origin.charAt(i);
        }
 
        String v2 = origin.trim(); // 去除空白
        System.out.println(v2); // cisco 字符串 使用方法
        String v3 = origin.toLowerCase(); // 小写
        System.out.println(v3); // cisco 字符串 使用方法
        String v4 = origin.toUpperCase(); // 大写
        System.out.println(v4); // CISCO 字符串 使用方法
        String[] v5 = origin.split("字"); // 分割
        System.out.println(v5); // [Ljava.lang.String;@5197848c
        System.out.println(Arrays.toString(v5)); // [cisco , 符串 使用方法]
        String v6 = origin.replace("o", "i"); // 替换
        System.out.println(v6); // cisci 字符串 使用方法
        String v7 = origin.substring(2, 6); // 子字符串=切片 [2:6]
        System.out.println(v7); // "sco " 左闭右开区间
        boolean v8 = origin.equals("cisco 字符串 使用方法");
        System.out.println(v8); // true
        boolean v9 = origin.contains("co");
        System.out.println(v9); // true
        boolean v10 = origin.startsWith("c");
        System.out.println(v10); // true
        String v100 = origin.concat("哈哈哈"); // 末尾追加
        System.out.println(v100); // cisco 字符串 使用方法哈哈哈
 
        // 字符串拼接
        StringBuilder sb = new StringBuilder(); // StringBuffer是多线程安全的
        sb.append("name");
        sb.append("=");
        sb.append("cisco");
        sb.append("&");
        sb.append("age");
        sb.append("=");
        sb.append("18");
        System.out.println(sb); // name=cisco&age=18
        System.out.println(sb.toString()); // name=cisco&age=18
 
        /*
         * 数组,存放固定长度的元素。容器+特定类型+固定长度(数组一旦创建个数就不可调整)
         */
        // [123,1,99]
        int[] numArray = new int[3];
        numArray[0] = 123;
        numArray[1] = 1;
        numArray[2] = 99;
        System.out.println(Arrays.toString(numArray)); // [123, 1, 99]
 
        String[] names = new String[] { "cisco", "123", "456" }; // 使用 "new" 关键字来创建数组对象,并在括号中指定数组的大小和初始值。
        System.out.println(Arrays.toString(names)); // [cisco, 123, 456]
 
        String[] nameArray = { "cisco", "123", "456" }; // 使用 "{" 和 "}" 来创建数组对象,并在括号中指定数组的初始值,不需要使用 "new" 关键字。
        System.out.println(Arrays.toString(nameArray)); // [cisco, 123, 456]
 
        // 索引
        // nameArray[0]
        // nameArray.length
        for (int idx = 0; idx < nameArray.length; idx++) {
            String item = nameArray[idx];
        }
 
        /*
         * object 在Python中每个类都默认继承Object类(所有的类都是Object的子类)。
         * 在Java所有的类都是默认继承Object类。用基类可以泛指他的子类的类型。
         */
        String v00 = "cisco";
        System.out.println(v00); // cisco
        System.out.println(v00.getClass()); // class java.lang.String
        Object v01 = new String("cisco");
        System.out.println(v01); // cisco
        System.out.println(v01.getClass()); // class java.lang.String
 
        // 声明数组,数组中元素必须int类型;
        int[] v02 = new int[3];
 
        // 声明数组,数组中元素必须String类型;
        String[] v03 = new String[3];
 
        // 想要声明的数组中想要是混合类型,就可以用Object来实现。数组中可以是int/String类型;
        Object[] v04 = new Object[3];
        v04[0] = 123;
        v04[1] = "cisco";
        System.out.println(Arrays.toString(v04)); // [123, cisco, null]
 
        // 类型判断
        TypeOf.func(123); // 整型
        TypeOf.func("123"); // 字符串类型
    }
}
 
class ControlFlow {
    public void ifFun(int age) {
        // 条件1:if
        if (age < 18) {
            System.out.println("少年");
        } else if (age < 40) {
            System.out.println("大叔");
        } else {
            System.out.println("老汉");
        }
    }
 
    // 静态成员,无需实例化就可以指定调用。
    public static void switchFun(char gender) {
        // 条件2:switch
        switch (gender) {
            case 'F':
                System.out.println("female");
                break; // 退出{},不再执行后续的代码
            case 'M':
                System.out.println("man");
                break;
            default:
                System.out.println("unknown");
                break;
        }
    }
 
    public void whileFun() {
        // 循环1:while
        int count = 0;
        while (count < 3) {
            System.out.println(count + ",执行中...");
            count += 1;
        }
        System.out.println(count + ",退出while...");
    }
 
    public void doWhileFun() {
        // 循环2:do-while
        int count = 3;
        do {
            System.out.println(count + ",执行中...");
            count += 1;
        } while (count < 3);
        System.out.println(count + ",退出do-while...");
    }
 
    public void forFun() {
        // 循环3:for
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                System.out.println("continue");
                continue; // 单次退出{},这次循环内不再执行后续的代码
            }
            System.out.println(i);
        }
    }
 
}
 
class DataType {
    public void byteToString() {
        // 1.字节数组(转换为Unicode编码的字符串) [字节,字节,字节]
        // byte[] dataList = { 97, 105, 100, 61, 50, 52, 54, 51, 56, 55, 53, 55, 49, 38, 97, 117, 116, 111, 95, 112, 108,
        //         97, 121, 61, 48, 38, 99, 105, 100, 61, 50, 56, 57, 48, 48, 56, 52, 52, 49, 38, 100, 105, 100, 61, 75,
        //         82, 69, 104, 69, 83, 77, 85, 74, 104, 56, 116, 70, 67, 69, 86, 97, 82, 86, 112, 69, 50, 116, 97, 80, 81,
        //         107, 55, 87, 67, 104, 67, 74, 103, 38, 101, 112, 105, 100, 61, 48, 38, 102, 116, 105, 109, 101, 61, 49,
        //         54, 50, 55, 49, 48, 48, 57, 51, 55, 38, 108, 118, 61, 48, 38, 109, 105, 100, 61, 48, 38, 112, 97, 114,
        //         116, 61, 49, 38, 115, 105, 100, 61, 48, 38, 115, 116, 105, 109, 101, 61, 49, 54, 50, 55, 49, 48, 52, 51,
        //         55, 50, 38, 115, 117, 98, 95, 116, 121, 112, 101, 61, 48, 38, 116, 121, 112, 101, 61, 51 };
        byte[] dataList = {97, 105, 100, 61}; // aid= 这不是unicode编码啊,是ascii编码,而且是10进制
        String dataString = new String(dataList); // Java的String和char在内存中总是以UTF-16编码表示。 所以这里是将ascii编码转换为unicode编码.
        // 遍历字符串中的每个字符,将其以16进制格式输出,可以看到,ASCII字符的Unicode码点是其ASCII码值的十六进制表示.ASCII字符的Unicode编码就是简单地在原来的ascii编码前面添加一个字节00
        for (char ch : dataString.toCharArray()) {
            System.out.printf("%4x ", (int) ch); // 输出为四位宽度的十六进制数,输出是"  61   69   64   3d",不是0x 0061 0069 0064 003d. %4输出是以4个字符宽度一组的16进制数,不足部分用空格填充,细心看可以看到确实填充的两个空格.
        }
        try {
            byte[] v0 = dataString.getBytes("UTF-16"); // 在Java中,String 对象的构造并不会自动在字符串的开头添加BOM。只有将字符串转换为字节数组(例如通过 getBytes("UTF-16"))时才会添加。在字符串本身或字符数组中,BOM是不存在的。
            System.out.printf(Arrays.toString(v0)); // 输出[-2, -1, 0, 97, 0, 105, 0, 100, 0, 61] -2, -1 这两个字节从哪里来的? Java的UTF-16编码会在字节数组的开始添加一个字节顺序标记(BOM),用于指示字节顺序。Java默认使用小端序,因此BOM是 FF FE,在数组中有符号整数表示为 -2, -1。
        } catch (Exception e) {
        }
 
        System.out.println("字符串是:" + dataString); // 字符串是:aid=246387571&auto_play=0&cid=289008441&did=KREhESMUJh8tFCEVaRVpE2taPQk7WChCJg&epid=0&ftime=1627100937&lv=0&mid=0&part=1&sid=0&stime=1627104372&sub_type=0&type=3
    }
 
    public void stringToByte() {
        // 2.Unicode编码的字符串->其他编码格式的字节数组
        try {
            // Python中的 name.encode("gbk")
            String name = "中文字符";
            byte[] v1 = name.getBytes("GBK");
            System.out.println(Arrays.toString(v1)); // [-42, -48, -50, -60, -41, -42, -73, -5]
 
            // Python中的 name.encode("utf-8")
            byte[] v2 = name.getBytes("UTF-8");
            System.out.println(Arrays.toString(v2)); // [-28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90]
        } catch (Exception e) {
 
        }
    }
}
 
class TypeOf {
    public static void func(Object v1) {
        // System.out.println(v1);
        // System.out.println(v1.getClass());
        if (v1 instanceof Integer) {
            System.out.println("整型");
        } else if (v1 instanceof String) {
            System.out.println("字符串类型");
        } else {
            System.out.println("未知类型");
        }
    }
}

应用数据类型与基本数据类型之间的转换

  • 应用数据类型(或称为包装类型)是对基本数据类型的封装,应用数据类型是对象,而基本数据类型是数据值
    • 抽象类NumberBigDecimalBigIntegerByte(byte)、Boolean(boolean)、Character(char)、Short(short)、Integer(int)、Long(long)、Float(float)、Double(double)的父类。
    • 基本数据类型用于存储简单数据值的原始类型,它们不是对象,而是直接存储在栈内存中的值,不支持 null 值。
    • 应用数据类型是基本数据类型的对象表示,它们是类,属于对象,可以存储在堆内存中,支持 null 值和对象操作。
  • Java中使用应用数据类型(如Integer、Double等)是为了解决基本数据类型(如int、double等)存在的问题。应用数据类型提供了更多的灵活性和安全性,使得程序员能够更容易地编写安全且易于维护的代码。
    • 使用应用数据类型可以避免空指针异常,因为应用数据类型对象默认初始值为null,而基本数据类型则没有默认值
    • 可以使用应用数据类型作为泛型参数,而不能使用基本数据类型。
    • 应用数据类型对象可以更容易地在集合中存储和检索。
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
public class PackingDataType { 
    public static void main(String[] args) { 
        /* 
         * java中的应用数据类型(引用类型)和基本数据类型可以通过自动装箱和拆箱进行转换。 
         * 自动装箱:将基本类型转换为对应的应用数据类型,例如int转换为Integer。 
         * 自动拆箱:将应用数据类型转换为对应的基本类型,例如Integer转换为int。 
         */       
        int inta = 1; 
        System.out.println(inta);  // 1 
        // System.out.println(inta.getClass());  // Cannot invoke getClass() on the primitive type int
        Integer IntegerA = inta; 
        System.out.println(IntegerA); 
        System.out.println(IntegerA.getClass());  // class java.lang.Integer 
        int inta1 = IntegerA; 
        System.out.println(inta1);  // 1 
        // System.out.println(inta1.getClass());  // Cannot invoke getClass() on the primitive type int
   
        /*
         * Java 5 之前使用包装类的构造函数进行装箱操作效率较低, 
         * 建议使用包装类的静态方法进行转换,如Integer.valueOf() 和 Integer.intValue()
         */       
        int a = 5
        Integer A = Integer.valueOf(a); // int 转 Integer: 使用 Integer.valueOf(int i) 或者 new Integer(int i)       
        int a1 = A.intValue(); // Integer 转 int: 使用 Integer.intValue() 
   
        long b = 10
        Long B = Long.valueOf(b);  // long 转 Long: 使用 Long.valueOf(long l) 或者 new Long(long l)       
        long b1 = B.longValue();   // Long 转 long: 使用 Long.longValue() 
   
        double c = 3.14
        Double C = Double.valueOf(c);  // double 转 Double: 使用 Double.valueOf(double d) 或者 new Double(double d)       
        double c1 = C.doubleValue();  // Double 转 double: 使用 Double.doubleValue() 
   
        char d = 'a'
        Character D = Character.valueOf(d);  // char 转 Character: 使用 new Character(char c)       
        char d1 = D.charValue();             // Character 转 char: 使用 Character.charValue() 
        // short 转 Short: 使用 Short.valueOf(short s) 或者 new Short(short s)       
        // Short 转 short: 使用 Short.shortValue()       
        // byte 转 Byte: 使用 Byte.valueOf(byte b) 或者 new Byte(byte b)       
        // Byte 转 byte: 使用 Byte.byteValue()       
        // boolean 转 Boolean: 使用 Boolean.valueOf(boolean b) 或者 new Boolean(boolean b)     
        // Boolean 转 boolean: 使用 Boolean.booleanValue()  
    
}

Java中List、Set、Map

  • 这三个类是 Java 中用来操作字符串的工具类。
    • StringBuffer: 用于构建可变的字符串,可以对字符串进行增删改操作。由于其线程安全,所以在多线程环境下可以使用。
    • StringBuilder: 用于构建可变的字符串,也可以对字符串进行增删改操作。但是不保证线程安全,因此在单线程环境下效率更高。
    • Array: 数组是一种容器,用于存储固定数量的同类型的元素,因此可以用来存储大量的数据。Array 中的各种方法,如 sort、search、copy、fill 等,可以帮助你管理和处理数组中的数据。
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Iterator; // Java 中用于遍历集合元素的迭代器接口 
import java.util.HashSet;
import java.util.TreeSet;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.Map;
import java.util.Set;
 
public class ComplexType {
 
    public static void main(String[] args) { 
        /// 1. List 
        /* 
        List是一个接口,接口下面有两个常见的类型(目的是可以存放动态的多个数据) 
        ArrayList,连续的内存地址的存储(内部自动扩容)。 
        LinkedList,底层基于链表实现(自行车链条)。 
        Java中接口,是用来约束实现他的类。比如约束他里面的成员必须有add。 
        interface List{           
            public void add(Object data);   // 接口中的方法,不写具体的实现,只用于约束。 
        
        // 类ArrayList实现了接口List,此时这个类就必须有一个add方法。 
        class ArrayList implements List{           
            public void add(Object data){                // 将数据data按照连续存储的方法放在内存。 
            // ..           
            }       
        
        // 类LinkedList实现了接口List,此时这个类就必须有一个add方法。 
        class LinkedList implements List{           
            public void add(Object data){                // 将数据data按照链表的形式存储 
                // ..           
                }       
            
        */ 
        // ArrayList,默认内部存放的是混合数据类型。 
        // ArrayList<String> data = new ArrayList<String>(); // 指定数据类型String 
        // ArrayList<Object> data = new ArrayList<Object>(); 
        ArrayList data = new ArrayList(); 
        data.add("cisco1"); 
        data.add("cisco2"); 
        data.add(666); 
        data.add("cisco3"); 
   
   
        // String value = data.get(1);  // java: 不兼容的类型: java.lang.Object无法转换为java.lang.String 
        String value = (String) data.get(1); 
        System.out.println(value);      // cisco2 
   
        Object temp = data.get(1); 
        String value1 = (String) temp; 
        System.out.println(value1);    // cisco2 
   
        int xo = (int) data.get(2); 
        System.out.println(xo);         // 666 
   
        data.set(0, "哈哈哈哈"); 
        System.out.println(data);    // [哈哈哈哈, cisco2, 666, cisco3] 
   
        data.remove("cisco2");   
        data.remove(0); 
        System.out.println(data);  // [666, cisco3]  
   
        int size = data.size(); 
        System.out.println(size);    // 2 
   
        boolean exists = data.contains("cisco3"); 
        System.out.println(exists);   // true 
   
   
        for (int i = 0; i < data.size(); i++) { 
            Object item = data.get(i); 
            System.out.println(item);     // 666 cisco3 
        
   
        for (Object item : data) {    // 迭代器 
            System.out.println(item);    // 666 cisco3 
        
   
        Iterator it = data.iterator(); // 迭代器 
        while (it.hasNext()) { 
            // String item = (String) it.next(); // ArrayList data中存在不是String的元素666,迭代器返回的元素不是字符串类型,将迭代器遍历出来的元素强制转换为字符串时就会抛出ClassCastException异常。 
            Object item = it.next(); 
            System.out.println(item);    // 666 cisco3 
        
   
   
        LinkedList<Integer> v1 = new LinkedList<Integer>(); // int的链表 
        v1.add(11); 
        v1.add(22); 
        System.out.println(v1); // [11, 22] 
   
        LinkedList<Object> v2 = new LinkedList<Object>(); 
        v2.add("cisco1"); 
        v2.add("cisco2"); 
        v2.add(666); 
        v2.add(123); 
        System.out.println(v2); // [cisco1, cisco2, 666, 123] 
   
        v2.remove(1); 
        v2.remove("666"); 
        System.out.println(v2); // [cisco1, 666, 123] 
   
        v2.set(2, "xxx"); 
        v2.push("哈哈哈"); 
        v2.addFirst(11); 
        System.out.println(v2); // [11, 哈哈哈, cisco1, 666, xxx] 
   
   
        for (int i = 0; i < v2.size(); i++) { 
            Object item = v2.get(i); 
            System.out.println(item);  //  11 哈哈哈 cisco1 666 xxx       
            
   
        for (Object item : v2) { 
            System.out.println(item);  //  11 哈哈哈 cisco1 666 xxx       
            
   
        Iterator it1 = data.iterator(); // 迭代器 
        while (it.hasNext()) { 
            Object item = it1.next(); 
            System.out.println(item);    // 11 哈哈哈 cisco1 666 xxx       
            
   
   
        /// 2. Set 
        /*
        Set是一个接口,常见实现这个接口的有两个类,用于实现不重复的多元素集合。 
        HashSet,去重,无序。 
        TreeSet,去重,内部默认排序(ascii、unicode)【不同的数据类型,无法进行比较】。 
         */       
         // HashSet s1 = new HashSet();       
         // Set s1 = new HashSet();       
         // HashSet<String> s1 = new HashSet<String>();      
        HashSet s1 = new HashSet(); 
        s1.add("P站"); 
        s1.add("B站"); 
        s1.add("A站"); 
        s1.add("P站"); 
        s1.add(666); 
        s1.remove("A站"); 
        System.out.println(s1);     // [B站, P站, 666] 
   
        boolean exist = s1.contains("B站"); 
        System.out.println(exist);  // true 
   
        // s2 = {"东京热","东北热","南京热"} 
        HashSet s2 = new HashSet(){ 
            
                add("东京热"); 
                add("东北热"); 
                add("南京热"); 
            
        }; 
        System.out.println(s2); // [东京热, 南京热, 东北热] 
   
        s2.addAll(s1); 
        System.out.println(s2); // [东京热, B站, P站, 南京热, 666, 东北热] 
        s2.retainAll(s1); // 交集 &       
        System.out.println(s2); // [B站, P站, 666] 
   
        s2.addAll(s1); // 并集 |      
         System.out.println(s2); // [B站, P站, 666] 
   
        s2.removeAll(s1); // 差集 s1 - s2       
        System.out.println(s2);  // [] 
   
        // Set s2 = new TreeSet();       
        // TreeSet<String> s2 = new TreeSet<String>();       
        TreeSet s3 = new TreeSet(); 
        s3.add("P站"); 
        s3.add("B站"); 
        s3.add("A站"); 
        s3.add("P站"); 
        // s3.add(666); // 不可以,不同的数据类型,无法进行比较 
   
        System.out.println(s3); // [A站, B站, P站] 
   
        TreeSet s4 = new TreeSet(){ 
            
                add("P站"); 
                add("B站"); 
                add("A站"); 
                add("P站"); 
            
        }; 
        System.out.println(s4); // [A站, B站, P站] 
   
        for (Object item : s4) { 
            System.out.println(item);  // A站 B站 P站 
        
   
        Iterator it11 = s4.iterator(); 
        while (it11.hasNext()) { 
            Object item = it11.next(); 
            System.out.println(item);  // A站 B站 P站 
        
   
        /// 3.Map 
        /*       
        Map是一个接口,常见实现这个接口的有两个类,用于存储键值对。 
        HashMap,无序。 
        TreeMap,默认根据key排序。(常用) 
         */ 
        HashMap h1 = new HashMap(); 
        h1.put("name","cisco"); 
        h1.put("age",18); 
        h1.put("gender","男"); 
        System.out.println(h1); // {gender=男, name=cisco, age=18} 
        h1.remove("age"); 
        System.out.println(h1); // {gender=男, name=cisco} 
        int size1 = h1.size(); 
        System.out.println(size1); // 2 
        Object value11 = h1.get("name"); // 不存在,返回null 
        System.out.println(value11);  // cisco 
        boolean existsKey = h1.containsKey("age"); 
        System.out.println(existsKey);  // false 
        boolean existsValue = h1.containsValue("cisco"); 
        System.out.println(existsValue);  // true 
        h1.replace("name", "cc"); 
        System.out.println(h1); // {gender=男, name=cc} 
   
        // 循环: 示例1 
        Set<Map.Entry<String, String>> s11 = h1.entrySet();  // 使用h1.entrySet()方法获取所有键值对并将它们存储在一个set中。 
        System.out.println(s11);  // [gender=男, name=cc] 
        Iterator it111 = s11.iterator(); 
        while (it111.hasNext()) { 
            // ("name", "cisco") 
            Map.Entry<String, String> entry = (Map.Entry<String, String>) it111.next(); 
            String k = (String) entry.getKey(); 
            String v = (String) entry.getValue(); 
            System.out.println(k + ':' + v); // gender:男  name:cc       
        
        // 循环: 示例2 
        Set s22 = h1.entrySet(); 
        Iterator it222 = s22.iterator(); 
        while (it222.hasNext()) { 
            Map.Entry entry = (Map.Entry) it222.next(); 
            String k = (String) entry.getKey(); 
            String v = (String) entry.getValue(); 
            System.out.println(k + ':' + v); // gender:男  name:cc       
        
   
        HashMap<String,String> h2 = new HashMap<String,String>(); 
        h2.put("name","cisco"); 
        h2.put("age","18"); 
        h2.put("gender","男"); 
        System.out.println(h2); // {gender=男, name=cisco, age=18} 
   
   
        HashMap<String,String> h3 = new HashMap<String,String>(){ 
            
                put("name","cisco"); 
                put("age","18"); 
                put("gender","男"); 
            
        }; 
        System.out.println(h3); // {gender=男, name=cisco, age=18} 
   
        TreeMap t1 = new TreeMap(); // 改为了TreeMap 
        t1.put("name","cisco"); 
        t1.put("age","18"); 
        t1.put("gender","男"); 
        System.out.println(t1); // {age=18, gender=男, name=cisco} 
   
        TreeMap<String,String> t2 = new TreeMap<String,String>(); 
        t2.put("name","cisco"); 
        t2.put("age","18"); 
        t2.put("gender","男"); 
        System.out.println(t2); // {age=18, gender=男, name=cisco} 
   
        Map t3 = new TreeMap(); 
        t3.put("name","cisco"); 
        t3.put("age",18); 
        t3.put("gender","男"); 
        System.out.println(t3); // {age=18, gender=男, name=cisco} 
   
        // 循环: 示例3 
        for (Object entry : t1.entrySet()) { 
            Map.Entry<String, Object> entryMap = (Map.Entry<String, Object>) entry; 
            String k = entryMap.getKey(); 
            Object v = entryMap.getValue(); 
            System.out.println(k + ':' + v); // age:18 gender:男  name:cisco       
        
   
        // 循环: 示例4 
        for (Map.Entry<String, String> entry : t2.entrySet()) { 
            String k = entry.getKey(); 
            String v = entry.getValue(); 
            System.out.println(k + ':' + v); // age:18 gender:男  name:cisco       
        
           
    
 
}

在逆向中出现的案例

  • List
  • Set
  • Map

1
2
3
4
5
6
7
8
9
10
# 在Python中需要自己处理key排序的问题。 
v4 =
   "aid":123,   "xx":999,   "wid":888
   
# 1.根据key进行排序 
# data = ["{}={}".format(key,v4[key])  for key in sorted(v4.keys())] 
# 2.再进行拼接 
# result = "&".join(data) 
   
result = "&".join(["{}={}".format(key,v4[key])  for key in sorted(v4.keys())]) 

包的概念、静态成员、构造函数、函数重载、抽象类、接口、继承、多态、内部类

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/* 
/// 1.包的概念 
import utils.Helper;  // 导入utils 包中的Helper类 
   
文件目录结构 
src 
├── ObjectOriented.java 
└── utils 
    └── Helper.java 
// Helper类的实现,存放在helper.java 文件中 
package utils; 
   
public class Helper { 
    public static String getInfo() {       
        return "Helper类的实现"; 
    }
 */ 
   
/** 
 * 类的修饰符: 
 * public,公共(任何人都能调用包中的类)。 
 * default,只能在当前包中被调用。 
 */ 
public class ObjectOriented { 
    /** 
     * 类成员修饰符: 
     * public,公共,所有的只要有权限访问类,类中的成员都可以访问到。 
     * private,私有,只允许自己类调用。 
     * protected,同一个包 或 子类可以访问(即使没有在同一个包内,也可以访问父类中的受保护成员)。 
     * default,只能在同一个包内访问。 
     * @param args 
     */ 
    public static void main(String[] args) { 
   
        /// 2. 静态成员、构造函数、函数重载 
        // 静态成员,无需实例化就可以指定调用。 
        System.out.println(Person.email);    // xxx@xxx.com 
        Person.showData();                   // 静态方法 
   
        // 非静态成员需实例化才可以指定调用。 
        Person p1 = new Person();                                     // 构造方法1 
        Person p2 = new Person("cisco", 73);               // 构造方法2 
        Person p3 = new Person("cisco1", "cisc@sb.com");  // 构造方法3 
   
        p1.doSomething();                // 重载函数1     cisco 
        p1.doSomething("你好呀,");  // 重载函数1     你好呀,-cisco 
   
        p2.doSomething(); 
        p2.doSomething("你好呀,"); 
   
        p3.doSomething(); 
        p3.doSomething("你好呀,"); 
        /// 3. 抽象类、继承、接口、多态 
        Son1 v1 = new Son1(); 
        v1.stop();                // Son1.stop() 
        Son2 v2 = new Son2(); 
        v2.stop();                // Son2.stop() 
        Base v3 = v1;    // 让父类型的引用变量指向多种类型的子类型对象,实现多态的另一个条件 
        v3.stop();                // Son1.stop() 
        Base v4 = v2;    // 让父类型的引用变量指向多种类型的子类型对象,实现多态的另一个条件 
        v4.stop();                // Son2.stop() 
   
        Wechat v11 = new Wechat(); 
        v11.send();                     // Wechat.send() 发送微信 
        DingDing v22 = new DingDing(); 
        v22.send();                     // DingDing.send() 发送钉钉 
        IMessage v33 = v11; 
        v33.send();                     // Wechat.send() 发送微信 
        IMessage v44 = v22; 
        v44.send();                     // DingDing.send() 发送钉钉 
        Iphone v55 = new Iphone(); 
        v55.send();                     // Iphone.send() 发送短信 
        v55.call();                     // Iphone.call() 打电话 
        IMessage v555 = v55; 
        v555.send();                    // Iphone.send() 发送短信 
        ICall v5555 = v55; 
        v5555.call();                   // Iphone.call() 打电话 
   
   
        /// 4.内部类: 
        // 成员内部类(实例内部类):定义在类中的类,即没有被static修饰的内部类。可以访问外部类的所有成员变量和方法。 
        Outer1 out1 = new Outer1(); 
        Outer1.MemberInner in1 = out1.new MemberInner(); 
        in1.display(); 
        // 静态内部类:定义在类中的静态类,即被static修饰的内部类。可以直接访问外部类的静态成员变量和方法 
        Outer2.StaticInner in2 = new Outer2.StaticInner(); 
        in2.display();   // x = 10 
        // 局部内部类,指的是在方法或一个作用域内里面定义的类,它只能在该方法或该作用域内使用。 
        Outer3 out4 = new Outer3(); 
        out4.method();    // x = 10 
        // 匿名内部类:没有类名的局部内部类,通常用于创建一个只使用一次的类。,常用于实现接口或继承父类。比较常见的可以放在方法里面,也可以类里面 
        // 匿名内部类的优点是简化了代码结构,减少了类的数量,并且易于使用。但是,由于它们没有名字,因此不能在多个地方重用。 
        // Outer4 test = new Outer4(); // java: Outer4是抽象的; 无法实例化 
        Outer4 s1 = new Outer4() {    // 这里并不是实例化抽象类Outer4,抽象类是不能实例化的。而是定义了一个继承了Outer4抽象类的匿名类,并且通过new实例化该匿名类,然后转型为Outer4
            @Override 
            public void display() { 
                System.out.println("x = " + x); 
            
        }; 
        s1.display();  // x = 10 
   
        Outer5 s2 = new Outer5() {    // 定义了一个实现了Outer5接口的匿名类,并且通过new实例化该匿名类,然后转型为Outer5
            @Override 
            public void display() { 
                System.out.println("x = " + x); 
            
        }; 
        s2.display();  // x = 10 
   
    
   
class Person { 
    // 静态变量 
    public static String email = "xxx@xxx.com"; 
   
    // 实例变量 
    public String name; 
    public Integer age; 
   
    // 构造方法1:无参构造方法 
    public Person() { 
        this.name = "cisco"; 
        this.age = 99999; 
    
   
    // 构造方法2 
    public Person(String name, Integer age) { 
        this.name = name; 
        this.age = age; 
        this.email = "xxx@live.com"; 
    
   
    // 构造方法3 
    public Person(String name, String email) { 
        this.name = name; 
        this.age = 83; 
        this.email = email; 
    
   
    // 定义方法(重载) 
    public void doSomething() { 
        System.out.println(this.name); 
    
   
    // 定义方法(重载) 
    public void doSomething(String prev) { 
        String text = String.format("%s-%s", prev, this.name); 
        System.out.println(text); 
    
   
    // 普通方法 
    public void showInfo(){ 
        System.out.println("普通方法"); 
    
   
    // 静态方法 
    public static void showData(){ 
        System.out.println("静态方法"); 
    
   
/** 
 * 抽象类,被abstract关键字修饰的类,不能实例化 
 * 抽象类里面可以没有抽象方法,但有抽象方法,该类必须用abstract进行修饰 
 */ 
abstract class Base { 
    String member;  // 抽象类和接口的区别1:抽象类可以有成员变量,而接口中只能有常量 
    // 抽象方法(约束子类中必须有这个方法) 
    public abstract void play(String name); 
    // 普通方法 
    // 抽象类和接口的区别2:抽象类可以实现公共方法和定义抽象方法,而接口只能定义抽象方法。 
    public void stop() { 
        System.out.println("Son.stop()"); 
    
    // 抽象类和接口的区别3:接口中的方法默认是public abstract,而抽象类中的方法默认是protected 
    void funcDefaultAttribute() { 
        System.out.println("Son.default()"); 
    
   
/** 
 * Son继承抽象类Base。 
 * class Son extends Base,Base1{       // 这种写法不支持,java不支持继承多个类 
 * 如果子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法 
 * 如果不想实现父类的抽象方法,可以使用abstract进行修饰,声明子类是抽象类 
 */ 
class Son1 extends Base{ 
    // 子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法 
    public void play(String name){ 
        System.out.println("Son1.play()"); 
    
    // 子类Son1中重写了父类Base的stop函数,这是实现多态的的条件之一 
    // 另一个条件是:让父类型的引用变量指向多种类型的子类型对象 
    public void stop() { 
        System.out.println("Son1.stop()"); 
    
   
class Son2 extends Base{ 
    // 子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法 
    public void play(String name){ 
        System.out.println("Son2.play()"); 
    
    // 子类Son中重写了父类Base的stop函数,这是实现多态的的条件之一 
    // 另一个条件是:让父类型的引用变量指向多种类型的子类型对象 
    public void stop() { 
        System.out.println("Son2.stop()"); 
    
   
/** 
 * 定义接口的关键字interface,定义了IMessag接口,接口是特殊的抽象类 
 */ 
interface IMessage { 
    final String member1 = "接口只能有常量";  // 抽象类和接口的区别1:抽象类可以有成员变量,而接口中只能有常量 
    // 抽象类和接口的区别2:抽象类可以实现公共方法和定义抽象方法,而接口只能定义抽象方法。 
    public void send(); 
   
    /* 
    抽象类和接口的区别3:接口中的方法默认是public abstract,而抽象类中的方法默认是protected 
    void funcDefaultAttribute();    
    */ 
   
/** 
 * 定义接口的关键字interface,定义了ICall接口 
 */ 
interface ICall { 
    public void call(); 
   
/** 
 * 实现接口的关键字implements,Wechat类"实现"了Imessage接口 
 */ 
class Wechat implements IMessage { 
    // Wechat中重写了实现了Imessage接口,这是实现多态的的条件之一 
    // 另一个条件是:让父类型的引用变量指向多种类型的子类型对象 
    public void send() { 
        System.out.println("Wechat.send() 发送微信"); 
    
   
/** 
 * 实现接口的关键字implements,DingDing类"实现"了Imessage接口 
 */ 
class DingDing implements IMessage { 
    public void send() { 
        System.out.println("DingDing.send() 发送钉钉"); 
    
   
/** 
 * 抽象类和接口的区别4:java中只能单继承(一个类只能继承一个父类),但一个类可以实现多个接口 
 * Iphone类实现了IMessage,ICall接口 
 */ 
class Iphone implements IMessage,ICall{ 
    public void send() { 
        System.out.println("Iphone.send() 发送短信"); 
    
    public void call() { 
        System.out.println("Iphone.call() 打电话"); 
    
// [java内部类的四大作用_java内部类的作用-CSDN博客](https://blog.csdn.net/u013728021/article/details/87358517)
// 实例内部类(成员内部类):定义在类中的类,即没有被static修饰的内部类。 
class Outer1 { 
    int x = 10
   
    class MemberInner { 
        void display() { 
            System.out.println("x = " + x);  // 可以访问外部类的所有成员变量和方法。 
        
    
   
// 静态内部类:定义在类中的静态类,即被static修饰的内部类。 
class Outer2 { 
    static int x = 10
   
    static class StaticInner { 
        void display() { 
            System.out.println("x = " + x);  // 可以直接访问外部类的静态成员变量和方法 
        
    
   
// 局部内部类,指的是在方法或一个作用域内里面定义的类,它只能在该方法或该作用域内使用。 
class Outer3 { 
    int x = 10
    public void method() { 
        class LocalInner {   // 指的是在方法或一个作用域内里面定义的类 
            void display() { 
                System.out.println("x = " + x); 
            
        
        LocalInner in = new LocalInner(); 
        in.display();           // 它只能在该方法或该作用域内使用。 
    
   
// 匿名内部类:没有类名的局部内部类,通常用于创建一个只使用一次的类。通常用于简化代码编写,常用于实现接口或继承父类。比较常见的可以放在方法里面,也可以类里面 
abstract class Outer4 { 
    int x = 10
    public abstract void display(); 
   
interface Outer5 { 
    final int x = 10
    public  void display(); 
}

异常、反射、注解

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.reflect.Constructor; 
import java.lang.reflect.Field; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
   
public class ReflectionAnnotation { 
    /// 1. 异常捕获,反射操作失败会抛出异常,这里需要捕获一下异常 
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { 
   
   
        /// 2.反射 
        /* 
         * Java中的反射是一种用于在运行时获取和操作类、接口、对象和包的能力。 
         * 反射可以让我们在运行时访问类中的属性和方法,并且可以动态地创建实例。       
         * 反射的主要用途是在运行时获取对象的类型,并且在不了解对象类型的情况下调用对象的方法。 
         */ 
 
        /// 2.1 使用反射获取类的三种方式 
        // 第一种:Class.forName 
        //class<?> clazz = class.forName( "com.example.helloworld");       
        // 第二种:直接类名.class 
        // <?> 是通配符,表示不确定类型,相当于 "class <? extends Object>"。这样定义的 clazz 对象可以表示任何类型的 Class 对象。 
        Class<?> clazz = Person1.class;  // 将类Person1的类型赋值给类变量clazz 。
        // 第三种:通过对象获取类 
        // class<?> clazz = new MainActivity().getclass(); 
        System.out.println(clazz.getName()); // 输出:Person1 
   
        /// 2.2 使用反射创建类的实例化对象 
        // Person1 person = (Person1) clazz.newInstance(); // 会报错:指的是在使用反射创建类实例化对象时,类没有默认构造函数或者没有公共的构造函数。 
        // 可以创建实例的时候,同时用构造函数初始化 
        Constructor constructor = clazz.getConstructor(String.class, int.class); 
        Person1 person = (Person1) constructor.newInstance("cisco", 18); 
   
        /// 2.3 使用反射调用方法 
        // getConstructor(返回指定公开的构造方法)、getConstructors(返回所有公开的构造方法) 
        // getDeclaredConstructor(返回指定的构造方法) 、getDeclaredConstructors(返回所有构造方法) 
        Constructor[] constructors = clazz.getConstructors(); 
        System.out.println(constructors.length);    // 输出:1 
   
        // getMethod(返回指定公开的方法)、getMethods(返回所有公开的方法) 
        // getDeclaredMethod(返回指定的方法)、getDeclaredMethods(返回所有的方法) 
        Method sayHelloMethod = clazz.getMethod("sayHello"); 
        sayHelloMethod.invoke(person); // 输出:Hello, my name is cisco and I am 18 years old. 
   
        /// 2.4 反射获取字段 
        //获取该类所有公开的字段 
        Field[] fields = clazz.getFields(); 
        System.out.println(constructors.length);    // 输出:1 
        // 获取类中指定公开的字段,如果字段被private修饰,下面的代码获取字段会报错 
        Field field1 = clazz.getField("name"); 
        String name = (String) field1.get(person); 
        System.out.println(name);                  // 输出:cisco 
   
        //获取类里面的指定私有字段 
        Field field2 = clazz.getDeclaredField("age" ); 
        field2.setAccessible(true); // 设置为可以访问 
        int age = (int) field2.get(person); 
        System.out.println(age);                    // 输出:18 
        //获取类里面的所有字段 
        Field[] fields2 = clazz.getDeclaredFields(); 
        System.out.println(constructors.length);    // 输出:1 
   
        // 3.3 我们可以使用反射来读取注解,并基于它来执行特定操作,比如运行所有带有 @TestAnnotation 注解的方法 
        Method[] methods = MyTestClass.class.getMethods(); // 获取MyTestClass类中的所有公开的方法 
        for (Method method : methods) { 
            TestAnnotation testAnnotation = method.getAnnotation(TestAnnotation.class); 
            if (testAnnotation != null) { 
                System.out.println("Running test: " + testAnnotation.value());   
                method.invoke(new MyTestClass()); 
                
        
            /* 
            输出: 
            Running test: Test Method 1        invoke testMethod1       
            Running test: Test Method 2        invoke testMethod2        
            */ 
    
   
   
// 定义一个Person类 
class Person1 { 
    public String name; 
    private int age; 
    public Person1(String name, int age) { 
        this.name = name; 
        this.age = age; 
    
    public void sayHello() { 
        System.out.println("Hello, my name is " + name + " and I am " + age + " years old."); 
    
   
   
/// 3.注解 
/* 
Java中的注解是一种元数据,可以在编译时或运行时为类、方法、属性等元素添加额外的信息。 
注解的主要用途是通过反射来获取这些额外的信息。在Hook的时候把这些信息打印出来有助于我们逆向。 
@Override注解来标识某个方法是重写自父类的,这样编译器就可以检测出在子类中是否存在与父类同名的方法,如果不存在,编译器就会抛出错误。 
使用@Deprecated注解来标识某个方法已经过时,编译器会在编译时给出警告信息。 
也可以自定义注解 
注解还有很多其他用途,例如在编译时进行类型检查、生成文档、配置等。 
 */
 // 3.1 假设我们有一个 @TestAnnotation 注解,用于标记可测试的方法 
@Retention(RetentionPolicy.RUNTIME) 
@interface TestAnnotation { 
    String value() default ""
// 3.2 然后在我们的类中使用这个注解 
class MyTestClass { 
    @TestAnnotation("Test Method 1"
    public void testMethod1() { 
        System.out.println("invoke testMethod1"); 
    
   
    @TestAnnotation("Test Method 2"
    public void testMethod2() { 
        System.out.println("invoke testMethod2"); 
    
}

多线程

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
import java.util.concurrent.*; 
   
public class MultiThreaded { 
    public static void main(String[] args){ 
        Thread t = new Thread(new MyTask(1));  // 用这个MyTask类的实例创建了一个Thread对象 
        t.start();                            // 启动新线程 
        try
            Thread.sleep(1000);         // 等待1s后 
        } catch (InterruptedException e) { 
            throw new RuntimeException(e); 
        
        t.interrupt();                        // 调用 myThread.interrupt() 来终止线程 
   
        // 使用Executor框架来创建线程和管理线程池 
        /* 
        ExecutorService的submit()方法和executor.execute()方法的主要区别在于返回值。 
        submit()方法返回一个Future对象,可以通过这个对象来检查任务的状态,并在任务完成后获取返回值。 
        Future对象支持取消任务、检查任务是否完成、获取任务结果等操作。 
        而execute()方法只是向线程池提交了一个任务,不会返回任何结果。 
         */       
        Executor executor = Executors.newFixedThreadPool(3); // 创建了一个线程池,大小为3 
        executor.execute(new MyTask(11)); 
        executor.execute(new MyTask(22)); 
        executor.execute(new MyTask(33)); 
        executor.execute(new MyTask(44));  // 线程池大小为3,但是向其中提交了4个任务,可能会导致最后一个任务无法立即执行,需要等待其它任务完成之后才能执行 
        /* 
        输出: 
        start 线程:1 
        stop 线程:1 
        start 线程:11 
        start 线程:22 
        start 线程:33 
         */    } 
 
class MyTask implements Runnable { 
    public int num; 
    public MyTask(int arg){ 
        num = arg; 
    
 
    public void run() { 
        // 执行任务的代码 
        System.out.println("start 线程:" + num); 
        // 使用 while 循环来检查中断标记位,如果被设置了中断标记位,则线程会结束自己。 
        while(!Thread.currentThread().isInterrupted()) { 
            // do some work 
            // ...          
            try { 
                Thread.sleep(100); 
            } catch (InterruptedException e) { 
                Thread.currentThread().interrupt(); 
                System.out.println("stop 线程:" + num); 
            
        
    
   
 
/* 
使用标志变量来控制线程的终止。 
在线程的代码中定义一个标志变量,在线程运行时检查这个标志变量的值, 
如果标志变量的值为 true,那么线程就可以自行终止。 
class MyThread extends Thread { 
    private volatile boolean running = true; 
    public void run() {       
        while (running) {           
        // do some work       
        }   
    
    public void stopRunning() {       
        running = false;   
    }
 */
 
 
// 对于只使用1次的线程,可以简写(创建一个匿名内部类),直接定义和创建线程,并执行线程
// new Thread() { 
//     @Override 
//     public void run() {
//      // do something
//  }
// }.start();

二、smali汇编

1. Dalvik字节码

  • java字节码、Dalvik字节码、机器码之间的关系?
    • 在Android上,Java代码首先经过编译器编译成Java字节码,然后再经过dx工具转换成Dalvik字节码,并打包到apk文件中(存储在DEX(Dalvik Executable)文件中),Dalvik字节码最终转换为机器码Android设备上运行。(因为大多数Android设备都是使用ARM处理器。因此,Android系统对应用程序的代码进行编译时,生成的是与ARM处理器兼容的机器码。)
    • 编译器编译Java代码成Java字节码是为了将Java代码转换成机器无关的中间代码,使得编译后的代码可以在任何支持JVM的系统上运行,不受机器的体系结构的限制。
    • Dalvik字节码是专门为Android系统优化的字节码,它比Java字节码更加高效。Dalvik字节码在一定程度上是Java字节码的一个子集
    • Java字节码和Dalvik字节码是两种不同的字节码格式,它们的区别主要体现在文件格式(java字节码文件扩展名为.class,Dalvik字节码文件扩展名为.dex。),存储方式(Java字节码在Java虚拟机中一次性加载,并在内存中存储;Dalvik字节码是以dalvik指令为单位存储的,仅加载需要的部分。),运行方式等方面(Java字节码直接在Java虚拟机中执行,Dalvik字节码在Dalvik虚拟机中执行,但需要经过dalvik指令集解释执行(Dalvik字节码->arm机器码)。)
  • Dalvik字节码和smali的关系?
    • Smali是一种对Dalvik字节码进行编码的人类可读的汇编语言,是对Dalvik指令的高级抽象。
    • Dalvik字节码 是以二进制形式存在于 .dex 文件中,而 smali 是以文本形式存在于磁盘文件中,是将 Dalvik字节码 反编译回文本文件的结果。
  • 为什么有了ART虚拟机,为什么还需要学Dalvik字节码?
    • Dalvik虚拟机是在启动应用程序时,将Dalvik字节码动态编译成机器码来实现运行的。所以对于每次打开一个应用程序,是需要进行编译的。
    • 在Android 5.0以后的版本,ART已经取代了Dalvik作为Android的默认虚拟机。
    • ART虚拟机是在安装应用程序时,将Dalvik字节码预先编译成机器码来实现运行的。因此不再需要在每次启动应用程序时进行编译,提高运行速度。
    • 可以看到只是编译的时机不同而已。无论是Dalvik虚拟机还是ART虚拟机,dex文件是没有变的,即还是Dalvik字节码,反汇编得到Smali汇编,可以通过Smali汇编语言重写和修改应用程序的Dalvik字节码,以实现各种定制化效果和优化性能。

2. Smali汇编

寄存器命名方法:V与P

  • JAVA虚拟机使用了栈架构,Java字节码被执行时通过一个操作栈来进行解释,每个方法在运行时都有一个私有的操作栈。
  • Dalvik (ART)虚拟机基于寄存器架构,不是使用操作栈来执行字节码,而是使用寄存器,速度快,节省代码。
    • V命名法:所有变量(局部,参数都用V0,1,...命名)
    • P命名法:局部变量用V,参数用P,容易判断局部变量和参数

类型描述符

  • C 表示字符(char),用来存储 8 位字符,用一个32位的Dalvik寄存器来存储。
  • l 表示布尔型(boolean),用来存储 8 位布尔型,用一个32位的Dalvik寄存器来存储。
  • S 表示短整型(short),用来存储 16 位短整型,用一个32位的Dalvik寄存器来存储。
  • I 表示整型(int),用来存储 32 位有符号整数。用一个32位的Dalvik寄存器来存储。
  • J 表示长整型(long),用来存储 64 位有符号整数。用两个相邻的32位Dalvik寄存器来存储。
  • F 表示单精度浮点类型(float),用来存储 32 位单精度浮点数。用一个32位的Dalvik寄存器来存储。
  • D 表示双精度浮点类型(double),用来存储 64 位双精度浮点数。用两个相邻的32位Dalvik寄存器来存储。
  • v 表示无返回值的void类型
  • [ 表示数组类型,后面跟着一个 Smali 类型描述符,代表这个数组存储的数据类型。
    • [I表示int []
    • [[I表示int[][],每多一维就加一个方括号,最多可以设置255维。
  • L +对象的全限定名表示对象类型(L``Package/Name/ObjectName;注意后面分号结束)
    • 比如String,其完整名称是java.lang.String,那么其全限定名就是java/lang/String;,即java.lang.String./代替,并在末尾添加分号;做结束符.
    • 成员:Lcom/MyClass;``->``name:Ljava/lang/String;(`对象类型;->成员名:成员类型)``
    • 方法:LPackage/Name/objectName;``->``myFunc(III)Z(对象类型;->函数名:(参数类型)返回值)III表示3个int参数
  • 函数:fun(Z [I [[I Ljava/lang/String; J [Ljava/lang/Object;)Ljava/lang/String;(表示string fun(boolean, int[],int[][],String,long,Object[]))
    • 直接方法:static、构造函数、包含静态语句块(比如静态数组初始化)
    • 虚方法

指令

  • 数据定义指令
  • 数据操作指令
  • 实例操作指令
  • 数组操作指令
  • 比较指令
  • 跳转指令
  • 字段操作指令
  • 数据转换指令
  • 算术指令
  • 空指令锁指令
  • 异常指令
  • 方法调用指令
  • 返回指令

3. smali文件详解

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
# 文件头描述。<>中的内容表示必不可缺的,[]表示的是可选择的.
# 访问权限修饰符即所谓的public,protected,private即default.而非权限修饰符则指的是final,abstract.
#.class <访问权限修饰符> [非权限修饰符] <类名> 
#.super <父类名>
#.source <源文件名称>
.class public Lcom/example/helloworld/HelloWorld; # .cLass 表示当前类,public 当前这个类的修饰符,类表示方式=L+包名+类名;
.super Ljava/lang/Object# .super 表示父类
 
# 在文件头之后便是文件的正文,即类的主体部分,包括类实现的接口描述,注解描述,字段描述和方法描述四部分.
# 接口描述
# .implements <接口名称>
# 注解描述
# .annotation [注解的属性] <注解类名>
#     [注解字段=值]
#     ...
# .end
# 普通字段
# .field <访问权限修饰符> [非权限修饰符] <字段名>:<字段类型>
# 静态字段
# .field <访问权限> static [修饰词] <字段名>:<字段类型>
# 直接方法/虚方法
# .method <访问权限修饰符> [非访问权限修饰符] <方法原型>
#    <.locals>
#    [.parameter]
#    [.prologue]
#    [.line]
#    <代码逻辑>
# .end
.method public static main([Ljava/lang/String;)V  # 方法以 `.method`开始,以 `.end method` 结束
    .registers 4  # 声明总共需要使用4个寄存器.在Smali中,如果需要存储变量,必须先声明足够数量的寄存器。
    .parameter 1  # 参数使用寄存器个数是1  v开头的局部寄存器 p开头是参数寄存器
    .locals 3     # 局部使用寄存器个数是3  v开头的局部寄存器 p开头是参数寄存器.它用于声明非参数的寄存器个数(包含在registers声明的个数当中)
    .prologue     # 方法代码逻辑开始位置
    nop           # 空指令
  
    # 数据定义指令
    # const[/4、/16、/hight16] v1 xxx  # 将常量xxx赋值给v1寄存器,`/`后数字指的是指令操作数的长度,不写则默认是32位
    # const-wide[/16、/32、/hight16] v1 xxx   # 将双字型常量xxx赋值给v1寄存器,`/`后数字指的是指令操作数的长度,不写则默认是32位
    # const-string[/jumbo] v1 “aaa”  # 将字符串常量”aaa”赋给v1寄存器,过长时需要加上/jumbo(字符串长度超过 65535 个字节时)
    # const-class v1 La/b/TargetClass # 将Class常量a.b.TargetClass赋值给v1,等价于a.b.TargetClass.class
    const/16 v0, 0x8 # 将16bit的常量0x8加载到32bit寄存器v0低16bit中,Smali中的寄存器长度是32位,没有16位的。这里的16"指的是指令操作数的长度,而不是寄存器的长度。
    const/4 v1, 0x5
    const/4 v2, 0x3
    # 数据操作指令
    move v1, v2
    # 数据操作指令
    new-array v0, v0, [I # 创建一个v0长度的int数组,数组首地址存储在寄存器 v0 中
    array-length v1, v0 # 把数组的长度存储在寄存器 v1 中
    # 实例操作指令
    new-instance v1, Ljava/lang/StringBuilder; # new StringBuilder类的实例,地址存放在v1中
    # 方法调用指令 直接方法是针对特定对象的方法调用,它直接在目标对象上调用方法,并返回结果。
    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V # 构造方法
    # 跳转指令
    if-nez v0, :cond_0
    goto :goto_0
    :cond_0
    # 数据转换指令
    int-to-float v2, v2
    # 数据运算指令
    add-float v2, v2, v2
    # 比较指令
    cmpl-float v0, v2, v2 # 比较v2的值与v2的值,并将结果存储在v0中。
    # 字段操作指令
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; # v0 = System.out
    const-string v1, "Hello World" # 将字符串常量"Hello World"赋给v1寄存器
    # 方法调用指令 虚方法是一种动态调用方法的方式,它在运行时才决定调用哪个方法。
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V  # v0.println(v1)    即System.out.println(v1)
    # 返回指令
    :goto_0
    return-void
.end method

还原成java代码

1
2
3
4
5
6
7
package com.example.helloworld;
public class HelloWorld {
    // 程序入口点
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

4. 实战

实战1:Crackme

  • 真机环境:Redmin9A
  • 样本: Crackme.apk
  • 工具:jadx

#项目/工具/demo #技术栈/app逆向/逆功能/注册码 不知道flag::定位到关键代码,还原成java代码即可。

  1. 找到关键函数
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# .cLass 表示当前类、public 当前这个类的修饰符
# 类表示方式=L+包名+类名:L net/bLueLotus/tomorrow/easyandroid/Crackme
.class public Lnet/bluelotus/tomorrow/easyandroid/Crackme;
.super Ljava/lang/Object;    # .super 表示父类
.source "Crackme.java"       # .source 表示源文件 .java文件
 
# instance fields 实例字段
# field (标识字段)
# private (访问权限修饰符,表示私有的)
# str2(字段名称)
# :(目号是字段名称与字段类型的分割符)
# Ljava/Lang/String; (字段类型)
 
# instance fields
.field private str2:Ljava/lang/String;
 
# .method 表示方法开开始位置
# public 访问权限修饰符表示公有的
# constructor 表示该方法是构造方法
# <init> 构造方法统一表现形式,默认的之后只要看到<init>就是构造方法
# () 小括号里而是参数列表
# V 返回值的类型
# direct methods 直接方法
.method public constructor <init>()V
    .locals 1          # 局部使用寄存器个数是1  v开头的局部寄存器 p开头是参数寄存器
 
    .prologue          # 方法代码逻辑开始位置
    .line 22           # 源文件的行号 .java
 
    # 调用直接方法 构造方法 默认的 p0.<init>()
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
 
    .line 21 # 行号
    # 定义字符串 String v0=" cGhyYWNrICBjdGYgMjAxNg=="; base64编码算法
    const-string v0, "cGhyYWNrICBjdGYgMjAxNg=="   #phrack ctf 2016
 
    # i表示操作实例. 字段iput 表示set,设置字段的值
    # p0表示this 指向当前对象
    # Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2(str2在net/bluelotus/tomorrow/easyandroid/Crackme类里面)
    # :(目号是字段名称与字段类型的分割符)
    # L java/Lang/String; (字段类型)
    # p0.str2=v0;
    iput-object v0, p0, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2:Ljava/lang/String;
 
    .line 23 # 行号 
    #定义字符串 v0 = "sSNnx1UKbYrA1+MOrdtDTA=="; base64编码算法 解不出来
    const-string v0, "sSNnx1UKbYrA1+MOrdtDTA=="
     
    # invoke-direct 表示调用直接方法
    # {p0,v0} 需要传入的参数
    # p0.GetFlag(v0)
    invoke-direct {p0, v0}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->GetFlag(Ljava/lang/String;)Ljava/lang/String;
  
    .line 24
    return-void    # 无返回值
.end method
 
.method private GetFlag(Ljava/lang/String;)Ljava/lang/String;
    .locals 4           # 表示当前这个方法使用到的局部变量存器个数为4
    .param p1, "str"    # Ljava/lang/String;   方法的参数
 
    .prologue           # 方法代码逻辑开始位置
    const/4 v3, 0x0
 
    .line 27
    # invoke-virtual调用直接方法
    # byte [ ] v2 = p1.getBytes();
    invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B
    move-result-object v2
 
    # invoke-static 表示调用静态方法
    # {v2,v3}参数
    # byte[]  content = Base64.decoe(v2,v3)  还原成java代码时,实际上是Base64.getDecoder().decoe(v2,v3) 
    invoke-static {v2, v3}, Landroid/util/Base64;->decode([BI)[B
     
    move-result-object v0
 
    .line 29
    .local v0, "content":[B   # 给v0寄存器起一个别名content
    # 实例化对象
    new-instance v1, Ljava/lang/String;
     
    # v2 = p0.str2
    iget-object v2, p0, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2:Ljava/lang/String;
     
    # byte[] v2 = v2.getBytes()
    invoke-virtual {v2}, Ljava/lang/String;->getBytes()[B
 
    move-result-object v2
 
    # byte[] v2 = Base64.decode(v2, v3)
    invoke-static {v2, v3}, Landroid/util/Base64;->decode([BI)[B
 
    move-result-object v2
    # new String(v2)
    invoke-direct {v1, v2}, Ljava/lang/String;-><init>([B)V
 
    .line 30
    .local v1, "kk":Ljava/lang/String# 给v1寄存器起一个别名kk
    # v2 = System.out
    sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
 
    # invoke-direct 表示直接方法 调用该decrypt方法解密字节数组
    # String v3 = pθ.decrypt(v0,v1);
    invoke-direct {p0, v0, v1}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->decrypt([BLjava/lang/String;)Ljava/lang/String;
 
    move-result-object v3
    # v2.println(v3)
    invoke-virtual {v2, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
 
    .line 31
    const/4 v2, 0x0
 
    return-object v2
.end method
 
.method private decrypt([BLjava/lang/String;)Ljava/lang/String;
    .locals 8
    .param p1, "content"    # [B   字节数组
    .param p2, "password"    # Ljava/lang/String;   字符串
 
    .prologue
    .line 35
    const/4 v4, 0x0  # return "";
 
    .line 37
    .local v4, "m":Ljava/lang/String;   # 给v4寄存器起一个别名m
    :try_start_0                        # 异常捕获开始的的位置
    # byte[] KeyStr = password.fetBytes()
    invoke-virtual {p2}, Ljava/lang/String;->getBytes()[B
 
    move-result-object v3
 
    .line 38
    .local v3, "keyStr":[B
    # SecretKeySpec v2 = new SecretKeySpec()
    new-instance v2, Ljavax/crypto/spec/SecretKeySpec;
 
    const-string v7, "AES"
     
    # SecretKeySpec key = new SecretKeySpec(keyStr, AES)
    invoke-direct {v2, v3, v7}, Ljavax/crypto/spec/SecretKeySpec;-><init>([BLjava/lang/String;)V
 
    .line 39
    .local v2, "key":Ljavax/crypto/spec/SecretKeySpec;
    const-string v7, "AES/ECB/NoPadding"
 
    # Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding")
    invoke-static {v7}, Ljavax/crypto/Cipher;->getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher;
 
    move-result-object v0
 
    .line 40
    .local v0, "cipher":Ljavax/crypto/Cipher;
    const/4 v7, 0x2
 
    # cipher.init(2,key)
    invoke-virtual {v0, v7, v2}, Ljavax/crypto/Cipher;->init(ILjava/security/Key;)V
 
    .line 41
    # Byte[] result = cipher.doFinal(content)
    invoke-virtual {v0, p1}, Ljavax/crypto/Cipher;->doFinal([B)[B
 
    move-result-object v6
 
    .line 42
    .local v6, "result":[B
    new-instance v5, Ljava/lang/String;
 
    # m = String(result)
    invoke-direct {v5, v6}, Ljava/lang/String;-><init>([B)V
    :try_end_0      # 异常捕获结束
    .catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_1
    .catch Ljavax/crypto/NoSuchPaddingException; {:try_start_0 .. :try_end_0} :catch_0
    .catch Ljava/security/InvalidKeyException; {:try_start_0 .. :try_end_0} :catch_4
    .catch Ljavax/crypto/IllegalBlockSizeException; {:try_start_0 .. :try_end_0} :catch_2
    .catch Ljavax/crypto/BadPaddingException; {:try_start_0 .. :try_end_0} :catch_3
 
    .end local v4    # "m":Ljava/lang/String;
    .local v5, "m":Ljava/lang/String;
    move-object v4, v5
 
    .line 46
    .end local v0    # "cipher":Ljavax/crypto/Cipher;
    .end local v2    # "key":Ljavax/crypto/spec/SecretKeySpec;
    .end local v3    # "keyStr":[B
    .end local v5    # "m":Ljava/lang/String;
    .end local v6    # "result":[B
    .restart local v4    # "m":Ljava/lang/String;
    :goto_0
    return-object v4   # retrun m
 
    .line 43
    :catch_0
    move-exception v1
 
    .line 44
    .local v1, "e":Ljava/security/GeneralSecurityException;
    :goto_1
    invoke-virtual {v1}, Ljava/security/GeneralSecurityException;->printStackTrace()V
 
    goto :goto_0
 
    .line 43
    .end local v1    # "e":Ljava/security/GeneralSecurityException;
    :catch_1
    move-exception v1
 
    goto :goto_1
 
    :catch_2
    move-exception v1
 
    goto :goto_1
 
    :catch_3
    move-exception v1
 
    goto :goto_1
 
    :catch_4
    move-exception v1
 
    goto :goto_1
.end method
  1. 还原成java代码
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
package net.bLueLotus.tomorrow.easyandroid; 
import javax.crypto.Cipher; 
import javax.crypto.spec.SecretKeySpec; 
import java.util.Base64; 
   
public class Crackme { 
    //定义私有的成员变量 
    private String str2 ; 
    //定义无参的构造方法 
    public Crackme(){ 
        str2 = "cGhyYWNrICBjdGYgMjAxNg=="
        GetFlag("sSNnx1UKbYrA1+MOrdtDTA=="); 
    
    private String GetFlag(String str){ 
        byte[] bytes = str.getBytes(); 
        byte[] content = Base64.getDecoder().decode(bytes); 
        byte[] bytes2 = this.str2.getBytes(); 
        String kk = new String (Base64.getDecoder().decode(bytes2)); 
        String flag = decrypt (content, kk); 
        System.out.println(flag); 
        return ""
    
    private String decrypt(byte[] content, String password){ 
        try
            byte[] keyStr = password.getBytes(); 
            SecretKeySpec key = new SecretKeySpec(keyStr, "AES"); 
            Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); 
            cipher.init(2,key); 
            byte[] result = cipher.doFinal(content); 
            return new String(result); 
        } catch (Exception e) { 
            e.printStackTrace();
        
        return ""
    
    // 程序入口点 测试 
    public static void main(String[] args) { 
        new Crackme();   // 输出PCTF{Sm4LiRiver},正确 
    
}

实战2:crackme0502

  • 真机环境:Redmin9A
  • 样本: crackme0502.apk
  • 工具:jadx

#项目/工具/demo #技术栈/app逆向/逆功能/注册码 不知道flag::定位首页Activity,定位到关键代码,把还原成java代码即可。

  1. 定位首页Activity,找到关键函数
  2. 还原关键函数算法
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
.class public Lcom/droider/crackme0502/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"
 
...
 
# virtual methods
.method public isRegistered()Z
    .registers 11
    .prologue
    const/16 v9, 0x8
    const/4 v7, 0x0
    .line 88
    const/4 v4, 0x0
    .line 89
    .local v4, "result":Z  # boolean result = false;
    const/4 v0, 0x0
    .line 90
    .local v0, "ch":C   # char ch = 0;
    const/4 v6, 0x0
    .line 91
    .local v6, "sum":I  # int sum = 0;
    iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String; # MainActivity中的成员内部类SNChecker    v8 = this.SNChecker.sn
    if-eqz v8, :cond_12
    iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    invoke-virtual {v8}, Ljava/lang/String;->length()I
    move-result v8
    if-ge v8, v9, :cond_14
    :cond_12
    move v5, v4
    .line 126
    .end local v4    # "result":Z
    .local v5, "result":I
    :goto_13
    return v5
    .line 92
    .end local v5    # "result":I
    .restart local v4    # "result":Z
    :cond_14
    iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    invoke-virtual {v8}, Ljava/lang/String;->length()I
    move-result v3
    .line 93
    .local v3, "len":I
    if-ne v3, v9, :cond_39
    .line 94
    iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    invoke-virtual {v8, v7}, Ljava/lang/String;->charAt(I)C
    move-result v0
    .line 95
    sparse-switch v0, :sswitch_data_52
    .line 101
    const/4 v4, 0x0
    .line 104
    :goto_26
    if-eqz v4, :cond_33
    .line 105
    iget-object v7, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    const/4 v8, 0x3
    invoke-virtual {v7, v8}, Ljava/lang/String;->charAt(I)C
    move-result v0
    .line 106
    packed-switch v0, :pswitch_data_5c
    .line 115
    const/4 v4, 0x0
    :cond_33
    :goto_33
    move v5, v4
    .line 126
    .restart local v5    # "result":I
    goto :goto_13
    .line 98
    .end local v5    # "result":I
    :sswitch_35
    const/4 v4, 0x1
    .line 99
    goto :goto_26
    .line 112
    :pswitch_37
    const/4 v4, 0x1
    .line 113
    goto :goto_33
    .line 119
    :cond_39
    const/16 v8, 0x10
    if-ne v3, v8, :cond_33
    .line 120
    const/4 v2, 0x0
    .local v2, "i":I
    :goto_3e
    if-lt v2, v3, :cond_46
    .line 124
    rem-int/lit8 v8, v6, 0x6
    if-nez v8, :cond_50
    const/4 v4, 0x1
    :goto_45
    goto :goto_33
    .line 121
    :cond_46
    iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    invoke-virtual {v8, v2}, Ljava/lang/String;->charAt(I)C
    move-result v1
    .line 122
    .local v1, "chPlus":C
    add-int/2addr v6, v1
    .line 120
    add-int/lit8 v2, v2, 0x1
    goto :goto_3e
    .end local v1    # "chPlus":C
    :cond_50
    move v4, v7
    .line 124
    goto :goto_45
    .line 95
    :sswitch_data_52
    .sparse-switch
        0x61 -> :sswitch_35
        0x66 -> :sswitch_35
    .end sparse-switch
    .line 106
    :pswitch_data_5c
    .packed-switch 0x31
        :pswitch_37
        :pswitch_37
        :pswitch_37
        :pswitch_37
        :pswitch_37
    .end packed-switch
.end method

还原成java代码

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
package com.droider.crackme0502;
pubLic class MainActivity {
static String sn = "1234567890123456";   // 注册码正确
 
//程序入口点
public static void main(String[] args) {
    if (isRegistered()){
        System.out.printLn("注册码正确!");
    }else {
        System.out.printLn("注册码错误!");
    }
}
private static boolean isRegistered(){
    boolean result = false;
    char ch = 0;
    int sum = 0;
    if (sn != null & sn.Length() >= 8){
        int len = sn.Length();
        if (len == 8){
            char ch = sn.charat(0);
            switch (ch){
                case 'a':
                case 'f':
                    char ch2 = sn.charAt(3);
                    switch (ch2){
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                            result = true;
                            break;
                    }
                    break;
            }
        }else if(len == 16){
            for (int i = 0; i < 16; i++){
                char chPLus = sn.charAt(i);
                sum = chPLus + sum;
                if (sum % 6 == 0){
                    result=true ;
                }
            }
        }
    }
     
    return result;
}

实战3:Androideasy

  • 真机环境:Redmin9A
  • 样本: Androideasy.apk
  • 工具:jadx

#项目/工具/demo #技术栈/app逆向/逆功能/注册码 不知道flag::根据弹窗提示文字,定位到关键代码,把还原成java代码即可。

  1. 根据弹窗提示文字,找到关键函数
  2. 还原关键函数算法
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
103
104
105
106
###### Class com.a.sample.androidtest.MainActivity (com.a.sample.androidtest.MainActivity)
.class public Lcom/a/sample/androidtest/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"
 
# instance fields
.field private editText:Landroid/widget/EditText;
.field private s:[B
 
# direct methods
.method public constructor <init>()V
    .registers 2
    .prologue
    .line 10
    invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V
    .line 12
    const/16 v0, 0x1f
    new-array v0, v0, [B
    fill-array-data v0, :array_e # v0 = array_e
    iput-object v0, p0, Lcom/a/sample/androidtest/MainActivity;->s:[B
    return-void
    nop
    :array_e
    .array-data 1
        0x71t
        0x7bt
        0x76t
        0x70t
        0x6ct
        0x5et
        0x63t
        0x48t
        0x26t
        0x44t
        0x48t
        0x57t
        0x59t
        0x48t
        0x24t
        0x76t
        0x64t
        0x4et
        0x48t
        0x57t
        0x79t
        0x53t
        0x65t
        0x27t
        0x3et
        0x5et
        0x3et
        0x26t
        0x6bt
        0x73t
        0x6at
    .end array-data
.end method
 
# virtual methods
.method public check()Z
    .registers 6
    .prologue
    const/4 v2, 0x0
    .line 15
    iget-object v3, p0, Lcom/a/sample/androidtest/MainActivity;->editText:Landroid/widget/EditText;
    invoke-virtual {v3}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
    move-result-object v3
    invoke-virtual {v3}, Ljava/lang/Object;->toString()Ljava/lang/String;
    move-result-object v3
    invoke-virtual {v3}, Ljava/lang/String;->getBytes()[B
    move-result-object v0
    .line 16
    .local v0, "chars":[B
    array-length v3, v0
    iget-object v4, p0, Lcom/a/sample/androidtest/MainActivity;->s:[B
    array-length v4, v4
    if-eq v3, v4, :cond_16
    .line 22
    :cond_15
    :goto_15
    return v2
    .line 18
    :cond_16
    const/4 v1, 0x0
    .local v1, "i":I
    :goto_17
    iget-object v3, p0, Lcom/a/sample/androidtest/MainActivity;->s:[B
    array-length v3, v3
    if-ge v1, v3, :cond_2c
    array-length v3, v0
    if-ge v1, v3, :cond_2c
    .line 19
    iget-object v3, p0, Lcom/a/sample/androidtest/MainActivity;->s:[B
    aget-byte v3, v3, v1   # v3 = v3[v1] 读取一个字节
    aget-byte v4, v0, v1   # v4 = v0[v1] 读取一个字节
    xor-int/lit8 v4, v4, 0x17 # v4 = v4 ^ 0x17
    if-ne v3, v4, :cond_15
    .line 18
    add-int/lit8 v1, v1, 0x1
    goto :goto_17
    .line 22
    :cond_2c
    const/4 v2, 0x1
    goto :goto_15
   
.end method

还原成java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
    private EditText editText;
    private byte[] s = {113, 123, 118, 112, 108, 94, 99, 72, 38, 68, 72, 87, 89, 72, 36, 118, 100, 78, 72, 87, 121, 83, 101, 39, 62, 94, 62, 38, 107, 115, 106};
    public boolean check() {
        byte[] chars = this.editText.getText().toString().getBytes();
        if (chars.length != this.s.length) {
            return false;
        }
        for (int i = 0; i < this.s.length && i < chars.length; i++) {
            if (this.s[i] != (chars[i] ^ 23)) {
                return false;
            }
        }
        return true;
    }

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (2)
雪    币: 58
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
谢谢分享
2024-6-22 16:40
0
雪    币: 8196
活跃值: (2691)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
谢谢分享,新手学习。
2024-6-23 06:48
0
游客
登录 | 注册 方可回帖
返回
//