返回列表 发新帖

《安卓逆向这档事》番外实战篇2-【24春节】解题领红包启动

  [复制链接]

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-2-28 15:13:26 | 显示全部楼层 | 阅读模式  来自 福建–石狮
本帖最后由 正己 于 2024-2-28 15:15 编辑



一、课程目标

frida实战安卓初级题与中级题

二、工具

1.安卓初级题与中级题
2.jadx-gui
3.VS Code
4.jeb

三、课程内容
1.初级题1
`关键函数解析`

  1. public static String extractDataFromFile(String str) {
  2.     // 定义局部变量str2
  3.     String str2;
  4.     // 定义局部变量indexOf
  5.     int indexOf;
  6.     try {
  7.         // 创建一个用于读取文件的RandomAccessFile实例,以只读模式打开
  8.         RandomAccessFile randomAccessFile = new RandomAccessFile(str, "r");
  9.         // 获取文件的长度
  10.         long length = randomAccessFile.length();
  11.         // 从文件末尾开始向前搜索,搜索范围限制为文件末尾的30个字节内
  12.         for (long max = Math.max(length - 30, 0L); max < length; max++) {
  13.             // 定位文件指针到max位置
  14.             randomAccessFile.seek(max);
  15.             // 定义一个长度为30的字节数组用于读取数据
  16.             byte[] bArr = new byte[30];
  17.             // 从当前文件指针位置读取数据到bArr中
  18.             randomAccessFile.read(bArr);
  19.             // 将读取的字节数组转换成字符串,并检查是否包含"flag{"字符串
  20.             if (new String(bArr, StandardCharsets.UTF_8).indexOf("flag{") != -1) {
  21.                 // 如果找到"flag{",则从这个位置开始,截取到"}"为止的字符串,并拼接上"}"
  22.                 String str3 = str2.substring(indexOf).split("\\}")[0] + "}";
  23.                 // 关闭文件并返回找到的字符串
  24.                 randomAccessFile.close();
  25.                 return str3;
  26.             }
  27.         }
  28.         // 如果整个文件中都没有找到符合条件的字符串,则关闭文件并返回null
  29.         randomAccessFile.close();
  30.         return null;
  31.     } catch (Exception e) {
  32.         // 处理异常,打印异常堆栈信息
  33.         e.printStackTrace();
  34.         return null;
  35.     }
  36. }

复制代码

  1. 方法1:
  2. var ClassName=Java.use("com.zj.wuaipojie2024_1.YSQDActivity");
  3. console.log(ClassName.extractDataFromFile("/data/user/0/com.zj.wuaipojie2024_1/files/ys.mp4"));
  4. 方法2:
  5. android intent launch_activity com.zj.wuaipojie2024_1.YSQDActivity
复制代码

2.初级题2

`关键函数解析`

  1. // 定义一个静态字节数组o,用于与签名校验数据异或
  2. public static byte[] o = {86, -18, 98, 103, 75, -73, 51, -104, 104, 94, 73, 81, 125, 118, 112, 100, -29, 63, -33, -110, 108, 115, 51, 59, 55, 52, 77};

  3. @Override
  4. public void onCreate(Bundle bundle) {
  5.     byte[] bArr; // 定义一个字节数组变量bArr,用于存储处理结果。
  6.     Signature[] signatureArr; // 定义一个Signature数组变量signatureArr,用于存储应用签名信息。
  7.     super.onCreate(bundle);
  8.     setContentView(R.layout.activity_flag); // 设置当前Activity使用的布局。

  9.     byte[] bArr2 = o; // 将静态字节数组o的引用赋给bArr2。

  10.     try {
  11.         // 尝试获取当前应用的签名信息。
  12.         signatureArr = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES).signatures;
  13.     } catch (PackageManager.NameNotFoundException unused) {
  14.         // 如果找不到包名,将bArr初始化为一个空的字节数组。
  15.         bArr = new byte[0];
  16.     }

  17.     // 检查signatureArr是否非空且至少包含一个元素。
  18.     if (signatureArr != null && signatureArr.length >= 1) {
  19.         // 将第一个签名信息转换为字节数组。
  20.         byte[] byteArray = signatureArr[0].toByteArray();

  21.         // 分配一个ByteBuffer,大小与bArr2相同,用于存储异或操作的结果。
  22.         ByteBuffer allocate = ByteBuffer.allocate(bArr2.length);

  23.         // 对bArr2和byteArray进行异或操作,并将结果存储在allocate中。
  24.         for (int i = 0; i < bArr2.length; i++) {
  25.             allocate.put((byte) (bArr2[i] ^ byteArray[i % byteArray.length]));
  26.         }

  27.         // 将处理后的数据赋值给bArr。
  28.         bArr = allocate.array();

  29.     } else {
  30.         // 如果签名数组为空或不存在,将bArr初始化为空字节数组。
  31.         bArr = new byte[0];
  32.     }

  33.     // 构建一个包含flag信息的字符串
  34.     StringBuilder d2 = a.d("for honest players only: \n");
  35.     d2.append(new String(bArr)); // 将bArr转换为字符串并附加到提示信息后。

  36.     // 将构建好的提示信息设置给TextView显示。
  37.     ((TextView) findViewById(R.id.tvFlagHint)).setText(d2.toString());
  38. }

复制代码

  1. 方法1:
  2. android intent launch_activity com.kbtx.redpack_simple.FlagActivity

  3. 方法2:
  4. function hookTest1(){
  5.     var Arrays = Java.use("java.util.Arrays");
  6.     Java.choose("com.kbtx.redpack_simple.WishActivity", {
  7.         onMatch: function(obj){
  8.                 console.log("obj的值: " + obj);
  9.             var oAsString = Arrays.toString(obj.o.value);
  10.             console.log("o字段的值: " + oAsString);
  11.             obj.o.value = Java.array('I', [90, 90, 122]);
  12.         },
  13.         onComplete: function(){

  14.         }
  15.     });
  16. }
复制代码

3.中级题
放一张流程图帮助理解

![]()
根据logcat发现是一个错误的dex,checksum验证失败,利用DexRepair修复头文件

  1. java -jar DexRepair.jar /path/to/dex
复制代码

塞回安装包,发现还是错误,仔细检查代码,发现读不到dex数据,对C类里read方法的decode.dex修改成1.dex

`关键函数解析`

  1. public boolean checkPassword(String str) {
  2.     try {
  3.         // 打开assets目录下的"classes.dex"文件作为InputStream
  4.         InputStream open = getAssets().open("classes.dex");
  5.         // 创建一个字节数组,大小为可读取的字节数,即整个文件的大小
  6.         byte[] bArr = new byte[open.available()];
  7.         // 从InputStream中读取数据到字节数组中
  8.         open.read(bArr);
  9.         // 创建一个指向应用的内部目录("data"目录)中的"1.dex"文件的File对象
  10.         File file = new File(getDir("data", 0), "1.dex");
  11.         // 创建一个向该文件写入数据的FileOutputStream
  12.         FileOutputStream fileOutputStream = new FileOutputStream(file);
  13.         // 将字节数组bArr的内容写入到"1.dex"文件中
  14.         fileOutputStream.write(bArr);
  15.         // 关闭文件输出流
  16.         fileOutputStream.close();
  17.         // 关闭文件输入流
  18.         open.close();
  19.         // 使用DexClassLoader加载"1.dex"文件,并调用其中一个类的静态方法
  20.         // "com.zj.wuaipojie2024_2.C"是类的全路径名
  21.         // "isValidate"是方法名,它接收一个Context对象,一个String对象和一个int数组作为参数
  22.         // 调用方法并传入当前Context(this),密码字符串str,以及一个从资源数组R.array.A_offset中获取的int数组
  23.         String str2 = (String) new DexClassLoader(file.getAbsolutePath(),
  24.                                                   getDir("dex", 0).getAbsolutePath(),
  25.                                                   null,
  26.                                                   getClass().getClassLoader())
  27.                                     .loadClass("com.zj.wuaipojie2024_2.C")
  28.                                     .getDeclaredMethod("isValidate", Context.class, String.class, int[].class)
  29.                                     .invoke(null, this, str, getResources().getIntArray(R.array.A_offset));
  30.         // 检查返回的字符串是否为null或者不以"唉!"开头
  31.         if (str2 == null || !str2.startsWith("唉!")) {
  32.             // 如果是,则认为密码检查失败
  33.             return false;
  34.         }
  35.         // 如果密码检查成功,则更新UI组件tvText的文本为返回的字符串,并将myunlock组件设为不可见
  36.         this.tvText.setText(str2);
  37.         this.myunlock.setVisibility(8);
  38.         // 返回true表示密码检查成功
  39.         return true;
  40.     } catch (Exception e) {
  41.         // 捕获到异常,打印异常堆栈信息,并返回false表示密码检查失败
  42.         e.printStackTrace();
  43.         return false;
  44.     }
  45. }

  46. public static String isValidate(Context context, String str, int[] iArr) throws Exception {
  47.     try {
  48.         // 尝试从动态加载的DEX中获取并调用静态方法
  49.         // getStaticMethod是一个自定义方法,用于根据给定参数动态获取特定的静态方法
  50.         // 参数包括上下文(context),一个整型数组(iArr),类的全名("com.zj.wuaipojie2024_2.A"),方法名("d"),以及该方法的参数类型(Context.class, String.class)
  51.         // 该方法预期返回一个Method对象,该对象代表了一个静态方法,可以被调用
  52.         // invoke方法用于执行这个静态方法,传入的参数为null(因为是静态方法,所以不需要实例),上下文(context)和字符串(str)
  53.         // 方法执行的结果被强制转换为String类型,并作为isValidate方法的返回值
  54.         return (String) getStaticMethod(context, iArr, "com.zj.wuaipojie2024_2.A", "d", Context.class, String.class).invoke(null, context, str);
  55.     } catch (Exception e) {
  56.         // 如果在尝试获取或调用方法时发生异常,记录错误信息到日志,并打印堆栈跟踪
  57.         Log.e(TAG, "咦,似乎是坏掉的dex呢!");
  58.         e.printStackTrace();
  59.         // 出现异常时,方法返回一个空字符串
  60.         return "";
  61.     }
  62. }
  63. private static Method getStaticMethod(Context context, int[] iArr, String str, String str2, Class<?>... clsArr) throws Exception {
  64.     try {
  65.         // read方法用于读取原始DEX文件,然后fix方法根据提供的iArr参数和上下文来处理数据。
  66.         File fix = fix(read(context), iArr[0], iArr[1], iArr[2], context);
  67.         
  68.         // 获取应用的当前类加载器
  69.         ClassLoader classLoader = context.getClass().getClassLoader();
  70.         
  71.         // 获取或创建一个名为"fixed"的目录,用于存放处理过的DEX文件
  72.         File dir = context.getDir("fixed", 0);
  73.         
  74.         // 使用DexClassLoader动态加载修复后的DEX文件。
  75.         // fix.getAbsolutePath()是DEX文件的路径,dir.getAbsolutePath()是优化后的DEX文件存放路径。
  76.         // null是父类加载器,classLoader是应用的当前类加载器,作为新的类加载器的父加载器。
  77.         Method declaredMethod = new DexClassLoader(fix.getAbsolutePath(), dir.getAbsolutePath(), null, classLoader)
  78.                                 .loadClass(str) // 加载指定的类
  79.                                 .getDeclaredMethod(str2, clsArr); // 获取指定的方法
  80.         
  81.         // 删除处理过的DEX文件和其在"fixed"目录下的优化版本,以清理临时文件
  82.         fix.delete();
  83.         new File(dir, fix.getName()).delete();
  84.         
  85.         // 返回找到的Method对象
  86.         return declaredMethod;
  87.     } catch (Exception e) {
  88.         // 如果过程中发生任何异常,打印堆栈跟踪并返回null
  89.         e.printStackTrace();
  90.         return null;
  91.     }
  92. }
  93. private static File fix(ByteBuffer byteBuffer, int i, int i2, int i3, Context context) throws Exception {
  94.     try {
  95.         // 获取或创建应用内"data"目录
  96.         File dir = context.getDir("data", 0);
  97.         // 使用自定义的D.getClassDefData方法获取类定义数据,然后从返回的HashMap中获取"class_data_off"的值
  98.         int intValue = D.getClassDefData(byteBuffer, i).get("class_data_off").intValue();
  99.         // 获取类数据,并根据给定的索引修改指定的直接方法的访问标志
  100.         //已知i2是3,也就意味着访问的是直接方法列表中的第四个方法(因为数组索引是从0开始的)
  101.         //i3则是方法的偏移,注意要转换成ULEB128格式
  102.         HashMap<String, int[][]> classData = D.getClassData(byteBuffer, intValue);
  103.         classData.get("direct_methods")[i2][2] = i3;
  104.         // 使用自定义的D.encodeClassData方法将修改后的类数据编码回字节数组
  105.         byte[] encodeClassData = D.encodeClassData(classData);
  106.         // 将ByteBuffer的位置设置到类数据偏移处,并将修改后的类数据写回ByteBuffer
  107.         byteBuffer.position(intValue);
  108.         byteBuffer.put(encodeClassData);
  109.         // 设置ByteBuffer的位置到32,从这个位置开始读取数据,用于SHA-1哈希计算
  110.         byteBuffer.position(32);
  111.         byte[] bArr = new byte[byteBuffer.capacity() - 32];
  112.         byteBuffer.get(bArr);
  113.         // 使用自定义的Utils.getSha1方法计算数据的SHA-1哈希
  114.         byte[] sha1 = Utils.getSha1(bArr);
  115.         // 将ByteBuffer的位置设置到12,并将计算出的SHA-1哈希写入ByteBuffer
  116.         byteBuffer.position(12);
  117.         byteBuffer.put(sha1);
  118.         // 使用自定义的Utils.checksum方法计算校验和
  119.         int checksum = Utils.checksum(byteBuffer);
  120.         // 将ByteBuffer的位置设置到8,并将校验和写入ByteBuffer(注意校验和的字节顺序被反转以符合DEX文件格式)
  121.         byteBuffer.position(8);
  122.         byteBuffer.putInt(Integer.reverseBytes(checksum));
  123.         // 获取ByteBuffer中的字节数组
  124.         byte[] array = byteBuffer.array();
  125.         // 创建一个新的DEX文件,并将修改后的数据写入该文件
  126.         File file = new File(dir, "2.dex");
  127.         FileOutputStream fileOutputStream = new FileOutputStream(file);
  128.         fileOutputStream.write(array);
  129.         fileOutputStream.close();
  130.         // 返回新创建的DEX文件
  131.         return file;
  132.     } catch (Exception e) {
  133.         // 在发生异常时打印堆栈跟踪并返回null
  134.         e.printStackTrace();
  135.         return null;
  136.     }
  137. }
  138. 修复后的代码:
  139. public static String d(Context context, String str) {
  140.     MainActivity.sSS(str);//frida检测
  141.     String signInfo = Utils.getSignInfo(context);//签名校验
  142.     if (signInfo == null || !signInfo.equals("fe4f4cec5de8e8cf2fca60a4e61f67bcd3036117")) {
  143.         return "";
  144.     }
  145.         //输入的字符串与运算后的048531267进行对比
  146.     StringBuffer stringBuffer = new StringBuffer();
  147.     int i = 0;
  148.     while (stringBuffer.length() < 9 && i < 40) {
  149.         int i2 = i + 1;
  150.         String substring = "0485312670fb07047ebd2f19b91e1c5f".substring(i, i2);
  151.         if (!stringBuffer.toString().contains(substring)) {
  152.             stringBuffer.append(substring);
  153.         }
  154.         i = i2;
  155.     }
  156.         
  157.     return !str.equals(stringBuffer.toString().toUpperCase()) ? "" : "唉!哪有什么亿载沉睡的玄天帝,不过是一位被诅咒束缚的旧日之尊,在灯枯之际挣扎的南柯一梦罢了。有缘人,这份机缘就赠予你了。坐标在B.d";
  158. }
  159. public static String d(String str) {
  160.     return "机缘是{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}";
  161. }

复制代码



  1. 方法1:
  2. function hook_delete() {
  3.     Java.perform(function () {
  4.         // 获取java.io.File类的引用
  5.         var File = Java.use("java.io.File");
  6.         // 挂钩delete方法
  7.         File.delete.implementation = function () {
  8.             // 打印尝试删除的文件路径
  9.             console.log("Deleting file: " + this.getPath());
  10.             return true;
  11.         };
  12.     });
  13. }

  14. function hook_resources() {
  15.     Java.perform(function () {
  16.         // 获取android.content.res.Resources类的引用
  17.         var Resources = Java.use("android.content.res.Resources");   
  18.         // 挂钩getIntArray方法
  19.         Resources.getIntArray.overload('int').implementation = function (id) {
  20.             // 换成b方法的偏移
  21.             var replacementArray = Java.array('int', [0, 3, 8108]);   
  22.             // 打印新的返回值
  23.             console.log("Replacing getIntArray result with: " + JSON.stringify(replacementArray));   
  24.             // 返回新的数组替代原始的返回值
  25.             return replacementArray;
  26.         };
  27.     });
  28.    
  29. }
  30. 方法2:
  31. 利用python脚本算出ULEB128对应的地址,利用010editor手动修改偏移,然后在加密网站上直接跑就行,因为是标准加密
  32. https://gchq.github.io/CyberChef/#recipe=SHA1(80)MD5()&input=cGFzc3dvcmQr5L2g55qEdWlk

复制代码

`ULEB128`(Unsigned Little-Endian Base 128)是一种用于编码32位或64位无符号整数的可变长度编码方案。它主要用在编译器和二进制格式中,如DWARF调试信息和Android的DEX文件格式。ULEB128的目的是以尽可能少的字节表示一个数值,特别是对于小的数值非常有效。
dex结构体

安卓源码中的`dalvik/libdex/DexFile.h`这里可以找到dex文件的数据结构,解析后的几个结构体如下:




五、视频及课件地址


百度云
阿里云
哔哩哔哩
教程开源地址
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压

回复

使用道具 举报

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-2-28 15:15:58 | 显示全部楼层  来自 福建–石狮
本帖最后由 正己 于 2024-5-11 16:27 编辑

内容太多了,还是放二楼
六、其他章节

《安卓逆向这档事》一、模拟器环境搭建  
《安卓逆向这档事》二、初识APK文件结构、双开、汉化、基础修改  
《安卓逆向这档事》三、初识smail,vip终结者  
《安卓逆向这档事》四、恭喜你获得广告&弹窗静默卡  
《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩  
《安卓逆向这档事》六、校验的N次方-签名校验对抗、PM代{过}{滤}理、IO重定向  
《安卓逆向这档事》七、Sorry,会Hook真的可以为所欲为-Xposed快速上手(上)模块编写,常用Api  
《安卓逆向这档事》八、Sorry,会Hook真的可以为所欲为-xposed快速上手(下)快速hook  
《安卓逆向这档事》九、密码学基础、算法自吐、非标准加密对抗  
《安卓逆向这档事》十、不是我说,有了IDA还要什么女朋友?  
《安卓逆向这档事》十二、大佬帮我分析一下  
《安卓逆向这档事》番外实战篇1-某电全家桶  
《安卓逆向这档事》十三、是时候学习一下Frida一把梭了(上)
《安卓逆向这档事》十四、是时候学习一下Frida一把梭了(中)
《安卓逆向这档事》十五、是时候学习一下Frida一把梭了(下)
《安卓逆向这档事》十六、是时候学习一下Frida一把梭了(终)
《安卓逆向这档事》十七、你的RPCvs佬的RPC
《安卓逆向这档事》番外实战篇2-【2024春节】解题领红包活动,启动!
七、参考文档
dex起步探索
回复

使用道具 举报

85

主题

7249

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
777
好评
96
信誉
147

考神MT论坛活跃会员MT论坛新人MT论坛最佳新人MT论坛帅哥MT论坛侠客挂机大佬

发表于 2024-2-28 15:18:54 来自手机  | 显示全部楼层  来自 新疆
学习学习
回复

使用道具 举报

1

主题

3656

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
7754
好评
24
信誉
241
发表于 2024-2-28 15:21:26 来自手机  | 显示全部楼层  来自 广东
学习一下
回复

使用道具 举报

23

主题

4709

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
5490
好评
2
信誉
107

MT论坛新人MT论坛最佳新人MT论坛帅哥考神

发表于 2024-2-28 15:21:55 来自手机  | 显示全部楼层  来自 广西
学习一下知识内容
回复

使用道具 举报

152

主题

7921

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
11462
好评
10
信誉
104

MT论坛最佳新人考神MT论坛新人MT论坛帅哥

发表于 2024-2-28 15:21:58 来自手机  | 显示全部楼层  来自 云南
看看隐藏
回复

使用道具 举报

66

主题

3388

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
7491
好评
62
信誉
105

考神MT论坛活跃会员

发表于 2024-2-28 15:22:01 来自手机  | 显示全部楼层  来自 江西
正己大佬我来了!
回复

使用道具 举报

51

主题

2971

回帖

8672

积分

硕士生

看看

Rank: 6Rank: 6

金币
130
好评
8
信誉
140

考神MT论坛帅哥MT论坛最佳新人MT论坛新人MT论坛活跃会员

发表于 2024-2-28 15:24:04 来自手机  | 显示全部楼层  来自 河南
看看
回复

使用道具 举报

90

主题

3227

回帖

7464

积分

硕士生

Rank: 6Rank: 6

金币
1814
好评
0
信誉
93
发表于 2024-2-28 15:31:39 来自手机  | 显示全部楼层  来自 湖南
看看隐藏在说
回复

使用道具 举报

5

主题

2370

回帖

5264

积分

硕士生

Rank: 6Rank: 6

金币
3833
好评
0
信誉
90
发表于 2024-2-28 15:33:05 来自手机  | 显示全部楼层  来自 浙江
感谢!
回复

使用道具 举报

2

主题

24

回帖

98

积分

小学生

Rank: 2

金币
57
好评
0
信誉
100
发表于 2024-2-28 15:33:43 来自手机  | 显示全部楼层  来自 河南
姥姥
回复

使用道具 举报

1

主题

6029

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
8129
好评
0
信誉
100
发表于 2024-2-28 15:41:29 来自手机  | 显示全部楼层  来自 江苏
感谢大佬分享
回复

使用道具 举报

25

主题

6262

回帖

1万

积分

博士生

小白一个

Rank: 7Rank: 7Rank: 7

金币
2777
好评
2
信誉
95

MT论坛新人考神MT论坛最佳新人

发表于 2024-2-28 15:43:10 来自手机  | 显示全部楼层  来自 辽宁
学习学习
回复

使用道具 举报

40

主题

5888

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
5107
好评
35
信誉
112

MT论坛帅哥考神MT论坛最佳新人

发表于 2024-2-28 15:43:41 来自手机  | 显示全部楼层  来自 湖南
感谢分享
回复

使用道具 举报

52

主题

4718

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
3649
好评
97
信誉
1072

考神MT论坛新人

发表于 2024-2-28 15:44:58 来自手机  | 显示全部楼层  来自 广西
本帖最后由 Kcrs 于 2024-2-28 15:46 编辑

学习学习
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表