-
-
全国高校大学生软件创新大赛-软件系统安全赛 HappyLock复现
-
发表于: 2025-1-7 13:46 2606
-
前言
复现该题以学习师傅们的解题思路,Java层是个人思路,后续是复现部分
涉及frida java/native层 hook, dump dex/so, bindiff恢复符号, dex文件结构等知识
附件: attachments.zip 包括题目附件,和dump相关文件
Java层分析
可以发现StringFog包内的StringObf.decode方法,直接hook查看结果
1 2 3 4 5 6 7 | let StringObf = Java.use( "StringFog.StringObf" ); StringObf[ "decode" ].implementation = function (str) { let result = this [ "decode" ](str); console.log(`StringObf.decode(${str})=${result}`); //showStacks(); return result; }; |
hook后滑动手势,打印部分信息如下
其中com.crackme.happylock.Check类比较可疑,但无法直接找到,可能有热加载dex操作
搜索字符串decode前的值,MainActivity中可以定位到PatternLockUtils.enc和Utils.cmp
hook PatternLockUtils.enc
1 2 3 4 5 6 7 | let PatternLockUtils = Java.use( "com.andrognito.patternlockview.utils.PatternLockUtils" ); PatternLockUtils[ "enc" ].implementation = function (arg1,arg2) { console.log(`PatternLockUtils.enc is called: null =${arg1}, null =${arg2}`); let result = this [ "enc" ](arg1,arg2); console.log(`PatternLockUtils.enc result=${result}`); return result; }; |
可以发现和预期结果一致,该函数用于计算手势对应哈希
Utils.cmp通过反射调用clz.cmp方法进行验证
继续hook clz,打印相关信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | let Utils = Java.use( "com.crackme.happylock.Utils" ); let clz = Utils.clz.value; printInstance(clz); console.log( "clz: " ,clz); console.log( "==========Methods==========" ) var methods=clz.getMethods(); for (let i=0;i<methods.length;i++){ console.log(methods[i]); } console.log( "==========Fields==========" ) var fields=clz.getDeclaredFields(); if (fields.length===0) console.log( "No fields!" ) else { for (let i=0;i<fields.length;i++){ console.log(fields[i]); } } |
其中prinInstance是自己封装的实例打印函数
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 | function printClassFields(Class){ var fields=Class.class.getDeclaredFields(); //字段 console.log( "\n==========fields==========" ); for ( var i=0;i<fields.length;i++){ var fieldName=fields[i].getName(); var fieldType=fields[i].getType(); console.log(fieldType,fieldName); } } function printClassMethods(Class){ var methods=Class.class.getDeclaredMethods(); //方法 console.log( "\n==========methods==========" ); for ( var i=0;i<methods.length;i++){ var methodParams=methods[i].getParameterTypes() var methodReturnType=methods[i].getReturnType() var methodName=methods[i].getName(); console.log(methodReturnType+ " " +methodName+ "(" +methodParams+ ")" ); } } function printInstanceMethods(Instance){ printClassMethods(Instance) } function printInstanceFields(Instance){ var fields=Instance.class.getDeclaredFields(); //字段 console.log( "\n==========fields==========" ); for ( var i=0;i<fields.length;i++){ var fieldName=fields[i].getName(); var fieldType=fields[i].getType(); var fieldValue=Instance[fieldName].value; console.log(fieldType,fieldName, "=" ,fieldValue); } } //打印实例详细信息 function printInstance(Instance){ printInstanceFields(Instance) printInstanceMethods(Instance) } //打印类的详细信息 function printClass(Class){ printClassFields(Class) printClassMethods(Class) } |
printInstance可以发现clz对应的ClassLoader,指明了dex文件路径
clz正是上面可疑的Check类,Utils.cmp实际是调用了Check.cmp
但无法直接使用frida hook到Check类,开始分析so层
so的init_array中可以发现多个函数进行字符串解密操作
JNI_OnLoad没找到关键逻辑,hook RegisterNatives也没有发现注册函数
接下来可以尝试dump内存中的dex和so,内存中的so已经解密了字符串,并且内存中的dex可能有Check类
Dump Dex
使用frida-dexdump得到的dex文件中,无法找到com.crackme.happylock.Check类
maps
查看app的内存映射情况进行观察
首先使用adb shell ps或frida-ps搜索app的pid
再使用maps查看内存映射情况
其中classes.dex标注了deleted, 下面开始dump dex文件
GDA dump
使用GDA可以,连接设备后搜索包名,在dex栏中可以定位,之后输入dex的起始地址和大小即可dump
如果无法正确dump可以查看文末问题解决
dump文件输出在GDA.exe同级目录dump/内,拖入jeb可以看到Check代码
getflag
一段简单的异或,可以getflag
1 2 3 4 5 6 7 8 | encrypted_data = [ 0x76 , 17 , 2 , 80 , 9 , 0x7D , 6 , 22 , 0x71 , 66 , 0 , 81 , 94 , 41 , 87 , 20 , 0x7A , 65 , 88 , 5 , 94 , 41 , 7 , 19 , 0x76 , 22 , 3 , 2 , 90 , 41 , 87 , 71 , 0x75 , 68 , 4 , 7 , 0x5F , 0x74 , 4 , 67 ] key = b "CrackMe!CrackMe!" decrypted_bytes = [] for i in range ( len (encrypted_data)): decrypted_byte = encrypted_data[i] ^ key[i % len (key)] decrypted_bytes.append(decrypted_byte) decrypted_text = bytes(decrypted_bytes).decode() print (decrypted_text) |
下面分析Native层做了什么操作
Native层
Dump SO
GDA dump操作同dump dex类似,但是需要手动使用388K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6r3z5p5I4q4c8W2c8Q4x3V1k6e0L8@1k6A6P5r3g2J5i4@1f1@1i4@1u0r3i4@1q4q4i4@1f1#2i4@1p5@1i4K6S2p5M7$3!0Q4c8e0k6Q4z5e0k6Q4z5o6N6Q4c8e0c8Q4b7V1u0Q4b7U0j5`.
dump_so.py脚本可以自动dump并修复
恢复符号
分析dump并修复的so,可以发现shadowhook相关字符串,这是字节跳动的一个开源inline hook框架ae7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6T1P5i4c8W2k6r3q4F1j5$3g2Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0V1K9h3&6D9K9h3&6W2i4K6u0V1K9r3!0G2K9H3`.`.
下载抖音apk,提取libshadowhook.so,使用bindiff恢复符号(注意所有文件路径不能有中文):
ida打开libshadowhook.so,保存得到idb文件
ida打开dump出的libhappylock.so文件
ctrl+6调用bindiff,选择Diff Database,选择libshadowhook.so的idb文件
在Matched Functions窗口中ctrl+a选中所有匹配符号
再右键或者按ctrl+6,选择Import Symbols/Comments,设置匹配度
恢复符号后,JNI_OnLoad可以发现shadowhook相关操作
逻辑分析
参考ShadowHook 手册, shadowhook_hook_sym_name函数声明如下
1 2 3 | #include "shadowhook.h" // 参数: 目标库名,目标符号名,代理函数地址,返回原函数地址(可为kong) void *shadowhook_hook_sym_name( const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr); |
hook_libc_execve
shadowhook_hook_func_addr中调用了shadowhook_hook_sym_name
hook了libc的execve
1 2 3 4 | __int64 shadowhook_hook_func_addr() { return shadowhook_hook_sym_name(aLibcSo_1, aExecve, sub_121E4, &off_44758); } |
代理函数sub_121E4中判断系统调用是否为dex2oat,如果是则不执行,如果不是则执行
即执行除dex2oat外的系统调用
hook_libart_ClassLinker::LoadMethod
shadowhook_hook_sym_name_callback 中也调用了shadowhook_hook_sym_name
1 2 3 4 5 6 7 | __int64 shadowhook_hook_sym_name_callback() { __int64 v0; // x0 v0 = __android_log_print(); return shadowhook_hook_sym_name(aLibartSo, v0, sub_12270, &qword_44770); } |
参考SWDD的[2025软件系统安全赛]HappyLock 直接hook shadowhook_hook_sym_name(0x12830)
注意要配合hook dlopen使用,保证加载so时立刻hook
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 | function hook_0x12380(){ var ModuleAddr= Module.findBaseAddress( 'libhappylock.so' ); console.log( "libhappylock.so: " +ModuleAddr) Interceptor.attach(ModuleAddr.add(0x12830), { onEnter: function (args) { console.log( 'lib_name:' , args[0].readCString()); console.log( 'sym_name:' , args[1].readCString()); console.log(`proxy_func: ${args[2]} (libhappylock.so+0x${(args[2]-ModuleAddr).toString(16)})`); console.log(`orig_addr: ${args[3]} (libhappylock.so+0x${(args[3]-ModuleAddr).toString(16)})\n`); }, onLeave: function (retval) { } }); } function hook_dlopen_and_myhook() { var isHooking= false ; var dlopen=Module.findExportByName( null , "dlopen" ); var android_dlopen_ext=Module.findExportByName( null , "android_dlopen_ext" ); console.log(`dlopen: ${dlopen}, android_dlopen_ext: ${android_dlopen_ext}`); if (android_dlopen_ext){ Interceptor.attach(android_dlopen_ext,{ onEnter: function (args){ var libPath=args[0].readCString(); if (libPath !== undefined && libPath != null &&libPath.indexOf( "libhappylock" ) !== -1) { this .isCanHook = true ; } },onLeave: function (args) { if ( this .isCanHook&&!isHooking){ console.log( "android_dlopen_ext libhappylock.so" ); isHooking= true ; hook_0x12380(); } } }) } } setImmediate(hook_dlopen_and_myhook) |
使用c++filt恢复函数符号名后,可以得知libart.so被hook的函数是ClassLinker::LoadMethod
1 | art::ClassLinker::LoadMethod(art::DexFile const&, art::ClassAccessor::Method const&, art::Handle<art::mirror::Class>, art::ArtMethod * ) |
代理函数sub_12270 检测dex文件大小,如果匹配到目标dex则执行回填操作
(此处a1应是this指针,pdex是DexFile的引用,先调用了0x44770处的原始LoadMethod加载方法后再回填代码)
其中+0x217处是key的起始地址,后续为字符串表保存的字符串
+0x178处是Check.cmp的代码
综上所述,JNI_OnLoad没有注册Native方法,而是hook了libc和libart
当加载到目标dex文件时,回填代码
GDA无法正确dump问题
如果发现GDA dump无法正常使用(看不到app的内存映射情况等)
首先参考GDA关于android脱壳的问题说明,查看GDump是否正确推送至手机
其次GDA自带的adb可能会与系统adb冲突,将GDA自带的adb目录设置到系统adb环境变量之上即可
1 | 默认在 C:\Users\<User>\AppData\Roaming\GDA\gadtmp 目录 |
为了简化操作,可编写bat脚本,启动GDA时自动将gda自带的adb目录设置到环境变量最上方
@echo off set "DIR_TO_ADD=C:\Users\admin\AppData\Roaming\GDA\gdatmp" set "Path=%DIR_TO_ADD%;%Path%" start "" "E:\Tools\MobileTools\Decompilers\GDA\GDA4.11.exe"
但使用时依然要注意: 其他shell中运行adb命令仍然会kill GDA的adb server
References
感谢以下师傅的指导:
Shangwendada
PangBai
P1umH0
赞赏
- 全国高校大学生软件创新大赛-软件系统安全赛 HappyLock复现 2607
- Dex文件结构-ReadDex解析器实现 15431
- [原创] ELF文件结构浅析-解析器和加载器实现 9262
- PE文件解析基础 13964