参加比赛的破解题目解析

[复制链接]
查看1025 | 回复2 | 2025-1-6 15:34:25 | 显示全部楼层 |阅读模式

立即注册,突破游客限制,与技术大牛交流切磋,畅享海量资源,让你的技术之路不再孤单!

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 洋咩咩 于 2025-1-6 15:36 编辑

题目要求
截图202501061524555550.png
分析过程初步分析
首先对于apk题目,二话不说肯定是Jadx开始梭:
于是乎
截图202501061525216924.png
这一堆都是什么破玩意儿,看样子是被什么混淆影响了jadx的反编译,看看smali代码:
截图202501061525399080.png
一大片的 goto语句,似乎使用了BlackObfuscator类似的混淆。
可以尝试把这个发给chatgpt试试
截图202501061525587793.png
效果很不错,甚至gpt还帮我们解了一下字符串混淆。
另外虽然说jadx反编译不了,我们还可以试一试jeb,毕竟jeb的反编译能力是要强于jadx的,我们看看jeb的反编译结果:
截图202501061526115323.png
截图202501061526193868.png
可以发现jeb也可以正常看到逻辑,还能看到大量的类似于控制流平坦化的内容。
根据两边结果,cmp逻辑在Utils类中,那么主要逻辑就在这个cmp了
进一步分析处理逻辑
我们查看Utils中的tmp。
截图202501061526312377.png
发现其通过new了一个class,然后调用了这个class里面的cmp方法,返回这个cmp的结果。
其实遇到这种动态调用,稍微有一点经验,我们就应该知道这个大概率在assets里面,但是不难发现一个细节,如果说真的在assets并且未加密的话,我们的jeb或者jadx也是同样可以识别到这个类的,这里并没有识别到,因此我们甚至不需要去看assets,他肯定是加密的,我们只需要查找他在哪儿加载的就可以了。
既然知道了,动态加载那么肯定离不开dexloader,我们直接搜索dexClassLoader:
截图202501061526455159.png
既然加密了,那肯定有解密,类里面看到了一个decode,我们直接hook看看:
  1. Java.perform(function () {
  2.     try {
  3.         let Utils = Java.use("com.crackme.happylock.Utils");
  4.         let decodeOverloads = Utils.decode.overloads;
  5.         console.log(`Found ${decodeOverloads.length} overload(s) for decode method`);
  6.         Utils.decode.overload('[B').implementation = function (data) {
  7.             console.log(`Utils.decode(byte[]) is called`);
  8.             let dataArray = Java.array('byte', data);
  9.             console.log(`Input data: ${dataArray}`);
  10.             let result = this.decode(data);
  11.             console.log(`Utils.decode result: ${result}`);
  12.             return result;
  13.         };
  14.     } catch (err) {
  15.         console.error(`Error hooking decode method: ${err}`);
  16.     }
  17. });
复制代码
截图202501061527332953.png
直接就发现了dex头,当然我们还可以通过hook defineClass来通杀所有的动态加载类大致方法如下:
截图202501061527469655.png
从这里我们就可以看到其参数中是带有dexfile的,我们只需要hook上了之后解析就好了
  1. Interceptor.attach(addr_DefineClass, {
  2.     onEnter: function (args) {
  3.         var dex_file = args[5]; var base = ptr(dex_file).add(Process.pointerSize).readPointer(); var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt(); if (dex_maps[base] == undefined) {
  4.             dex_maps[base] = size; var magic = ptr(base).readCString(); if (magic.indexOf("dex") == 0) {
  5.                 var process_name = get_self_process_name(); if (process_name != "-1") {
  6.                     var dex_dir_path = "/data/data/" + process_name + "/files/dump_dex_" + process_name; mkdir(dex_dir_path); var dex_path = dex_dir_path + "/class" + (dex_count == 1 ? "" : dex_count) + ".dex"; console.log("[find dex]:", dex_path); var fd = new File(dex_path, "wb"); if (fd && fd != null) {
  7.                         dex_count++; var dex_buffer = ptr(base).readByteArray(size); fd.write(dex_buffer); fd.flush(); fd.close(); console.log("[dump dex]:", dex_path)
  8.                     }
  9.                 }
  10.             }
  11.         }
  12.     }
  13.     , onLeave: function (retval) { }
  14. })
复制代码

截图202501061528458964.png
接下来我们需要分析动态加载的这个dex
截图202501061528561416.png
好像字节码有问题,但是能看到有一个native方法。
接下来就要分析我们的Jni了
Native分析
截图202501061529165500.png
最开始看到这个JniOnload,没找到register也没想太多 ,想着三下五除二直接上板子hook Register看看偏移,结果发生了如下事情:
截图202501061529287565.png
欸嘿,还真hook不到,当时认为是自实现的register,也没想太多看看代码
截图202501061529401720.png
这个很像是在Register,hook看看参数
  1. const ModuleAddr = Module.findBaseAddress('libhappylock.so');
  2. console.log(ModuleAddr)
  3. Interceptor.attach(ModuleAddr.add(0x12830), {
  4.     onEnter: function (args) {
  5.         console.log('arg0:', (args[0].readCString()));
  6.         console.log('arg1:', (args[1].readCString()));
  7.         console.log('arg2:', (args[2].readPointer()));
  8.         console.log('arg3:', (args[3].readPointer()));
  9.         //args[1] = ptr(0);
  10.     },
  11.     onLeave: function (retval) {
  12.     }
  13. });
复制代码
截图202501061530003887.png
奇怪,怎么是ClassLinker,(其实这个时候已经初步展露鸡脚了)。
但当时在做题的我没想太多,以为是我分析错了,用大佬的trace_so脚本梭了一把。
截图202501061530264351.png
结果发现,在启动完之后,再也无法触发native的逻辑了。
对本题还有的疑问就是,他的log似乎也通过某种手段关闭了。
截图202501061530404527.png
既然这样我们直接在调用logprint前看看参数,就能避免掉他用hook手段关闭log
插装一个log看看到底输出的啥:
  1. Interceptor.attach(ModuleAddr.add(0x127BC), {
  2.     onEnter: function (args) {
  3.         this.priority = args[0].toInt32();
  4.         this.tagPtr = args[1];
  5.         this.msgPtr = args[2];
  6.         this.debugPtr = args[3];
  7.         this.debugPtr2 = args[4];
  8.         this.debugPtr3 = args[5];
  9.         this.tag = safeReadCString(this.tagPtr);
  10.         this.msg = safeReadCString(this.msgPtr);
  11.         this.debug = safeReadCString(this.debugPtr);
  12.         this.debug2 = safeReadCString(this.debugPtr2);
  13.          
  14.         console.log("[*] _android_log_print called:");
  15.         console.log("    Priority: " + this.priority);
  16.         console.log("    Tag: " + this.tag);
  17.         console.log("    Message: " + this.msg);
  18.         console.log("    Message: " + this.debug);
  19.         console.log("    Message: " + this.debug2);
  20.       //  console.log("    Message: " + (this.debugPtr3 - ModuleAddr));
  21.     },
  22.     onLeave: function (retval) {
  23.       
  24.     }
  25. });
复制代码
截图202501061531064775.png
好家伙,shadowhook,这下就能回想到
截图202501061531204988.png
这个玩意实际上是在注册Hook了,那么根据之前分析的,他其实实现了一个类替换的过程,在defineClass前。
直接启动调试:
截图202501061531316704.png
可以看到shadowhook 做 inlineHook的痕迹
基本到这里,我们就可以直接对如下classes.dex段做dump了
截图202501061531439397.png
IDAPYTHON:
  1. import idautils
  2. import idc
  3. def dump_segment(segment_name, output_file):
  4.     """
  5.     导出指定段名的内存内容到文件。
  6.     :param segment_name: 要导出的段名(字符串)
  7.     :param output_file: 输出文件的路径(字符串)
  8.     """
  9.     for seg_ea in idautils.Segments():
  10.         seg = idaapi.getseg(seg_ea)
  11.         if seg is None:
  12.             continue
  13.         name = idc.get_segm_name(seg_ea)
  14.         if name == segment_name:
  15.             start = seg.start_ea
  16.             end = seg.end_ea
  17.             size = end - start
  18.             data = idc.get_bytes(start, size)
  19.             if data is None:
  20.                 print(f"无法读取段 {segment_name} 的数据。")
  21.                 return
  22.             try:
  23.                 with open(output_file, 'wb') as f:
  24.                     f.write(data)
  25.                 print(f"段 {segment_name} 已成功导出到 {output_file}")
  26.             except IOError as e:
  27.                 print(f"写入文件失败: {e}")
  28.             return
  29.     print(f"未找到段名为 {segment_name} 的段。")
  30. # 使用示例
  31. dump_segment("classes.dex", r"E:\wechat\WeChat Files\wxid_sxslbee4x0m522\FileStorage\File\2025-01\classes.dex.dump")
复制代码
然后这里注意使用Jadx会报错(保存原因后续分析),我们使用jeb反编译,就能看见逻辑。
截图202501061532163319.png
或者我们直接根据之前dump下来的dex:
截图202501061532318503.png
其中有一个大小是0x3ac
截图202501061532429851.png
那么我们也可以手动填充需要填充的字符串
截图202501061532533431.png
也就修复好了。
EXP
也就是说
截图202501061533049636.png
异或一下再字符串输出就是我们的flag了
  1. def xor_with_key(cmp, key):
  2.     # 将key转换为字节数组
  3.     key_bytes = key.encode('utf-8')
  4.     # 存储结果
  5.     result = []
  6.     # 遍历cmp数组并与key数组的字节进行异或操作
  7.     for i in range(len(cmp)):
  8.         # 使用key的字节,按循环方式访问
  9.         key_byte = key_bytes[i % len(key_bytes)]
  10.         cmp_byte = cmp[i]
  11.         # 异或操作
  12.         xor_result = key_byte ^ cmp_byte
  13.         # 将异或结果转换为字符并添加到结果中
  14.         result.append(chr(xor_result))
  15.     # 返回最终的字符串
  16.     return ''.join(result)
  17. # cmp数组
  18. cmp = [
  19.     0x76, 0x11, 0x02, 0x50, 0x09, 0x7d, 0x06, 0x16, 0x71, 0x42,
  20.     0x00, 0x51, 0x5e, 0x29, 0x57, 0x14, 0x7a, 0x41, 0x58, 0x05,
  21.     0x5e, 0x29, 0x07, 0x13, 0x76, 0x16, 0x03, 0x02, 0x5a, 0x29,
  22.     0x57, 0x47, 0x75, 0x44, 0x04, 0x07, 0x5f, 0x74, 0x04, 0x43
  23. ]
  24. # 密钥
  25. key = "CrackMe!CrackMe!"
  26. # 调用函数并打印结果
  27. result_string = xor_with_key(cmp, key)
  28. print(f"XOR result as string: {result_string}")
复制代码
截图202501061533225537.png
算法助手验证是否正确:
截图202501061533486143.png




这个人还没有签名
nixiang2024 | 2025-1-7 10:02:17 | 显示全部楼层
感谢分享
这个人还没有签名
nixiang2024 | 2025-1-7 10:04:45 | 显示全部楼层
请问safeReadCString这个函数有具体的代码不
这个人还没有签名
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则