-
-
[原创]java和smali汇编
-
发表于: 2024-6-22 11:23 2733
-
本公众号分享的所有技术仅用于学习交流,如作他用所承受的法律责任一概与作者无关。如有错漏,欢迎留言交流。预计阅读全文耗时: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
->.class
- 不记得概念,回头看即可
- 遇到陌生的api,查JDK_API文档即可
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( "未知类型" ); } } } |
应用数据类型与基本数据类型之间的转换
- 应用数据类型(或称为包装类型)是对基本数据类型的封装,应用数据类型是
对象
,而基本数据类型是数据值
。- 抽象类
Number
是BigDecimal
、BigInteger
、Byte
(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机器码)。)
- 在Android上,
- 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字节码,以实现各种定制化效果和优化性能。
- Dalvik虚拟机是在
2. Smali汇编
寄存器命名方法:V与P
- JAVA虚拟机使用了
栈架构
,Java字节码被执行时通过一个操作栈来进行解释,每个方法在运行时都有一个私有的操作栈。 - Dalvik (ART)虚拟机基于
寄存器架构
,不是使用操作栈
来执行字节码,而是使用寄存器,速度快,节省代码。- V命名法:所有变量(局部,参数都用
V0,1,...
命名) - P命名法:
局部变量用V
,参数用P
,容易判断局部变量和参数
- V命名法:所有变量(局部,参数都用
类型描述符
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参数
- 比如String,其完整名称是
- 函数: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 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 |
- 还原成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代码即可。
- 定位首页Activity,找到关键函数
- 还原关键函数算法
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 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 ; } |
赞赏
他的文章
- [原创]java和smali汇编 2734
- [原创]Android逆向前期准备(下) 4543
- [原创]native层逆向分析(上篇) 14042
- [原创]Java层逆向分析方法和技巧 7280
看原图
赞赏
雪币:
留言: