app逆向之安卓native层安全逆向分析(三):ida使用+unidbg补环境+算法还原
前言
继续跟着龙哥的unidbg学习:SO逆向入门实战教程三:V2-Sign_unidbg context_白龙~的博客-CSDN博客
还是那句,我会借鉴龙哥的文章,以一个初学者的角度,加上自己的理解,把内容丰富一下,尽量做到不在龙哥的基础上画蛇添足,哈哈。感谢观看的朋友
分析
打开app,抓包,发现有个sign
这个sign就是今天的重点了,jadx打开apk,可以,没有加壳,一搜,发现很快就搜到这些了,而且也不多
问题不大,用objcetion 把这几个都hook了,看看是走的哪里,没搞多久,就看到这里,入参和返回值,感觉就是这里了
因为这个返回值的格式,根抓包看到的格式基本一致
还有一个,我们看看请求的加密和解密,objection:
感觉objection不是太好看这个结果,那么这里改用frida看看吧,虽然objection 可以改源码,然后让结果可读,但是这里感觉不是有太大的必要,直接用frida就行了。
就是这里了,直接就解密了。
ida查看
打开ida看看这个so:
卧槽,这个so文件有点硬啊,看看,jni_onload:
卧槽,确实硬啊,这就尴尬了。那直接上unidbg吧
unidbg调试
1.搭架子
先把main跑起来
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 | package com.xiaochuankeji; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.AbstractJni; import com.github.unidbg.linux.android.dvm.DalvikModule; import com.github.unidbg.linux.android.dvm.VM; import com.github.unidbg.memory.Memory; import com.weibo.international; import java.io.File; public class tieba extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; private final Module module; tieba() { // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验 emulator = AndroidEmulatorBuilder.for32Bit().setProcessName( "cn.xiaochuankeji.tieba" ).build(); // 获取模拟器的内存操作接口 final Memory memory = emulator.getMemory(); // 设置系统类库解析 memory.setLibraryResolver( new AndroidResolver( 23 )); // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作 vm = emulator.createDalvikVM( new File( "unidbg-android\\src\\test\\java\\cn\\xiaochuankeji\\right573.apk" )); // 加载目标SO DalvikModule dm = vm.loadLibrary( new File( "unidbg-android\\src\\test\\java\\com\\xiaochuankeji\\libnet_crypto.so" ), true ); // 加载so到虚拟内存 //获取本SO模块的句柄,后续需要用它 module = dm.getModule(); vm.setJni( this ); // 设置JNI vm.setVerbose( true ); // 打印日志 dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad } public static void main(String[] args) { tieba test = new tieba(); System.out.println(test); } } |
运行,看起来没啥问题
根据龙哥的文章,说如果这里设置为false:
结果就会乱码。原因是这个so样本,做了【字符串的混淆或加密】 ,以此来对抗分析人员,但字符串总是要解密的
这个解密一般发生在Init array节或者JNI OnLoad中,又或者是该字符串使用前的任何一个时机,而本例呢,就发生在Init array节中,Shift+F7快捷键查看节区验证这一点
Init array节内有大量函数,解密就发生在其中。当我们使用Unidbg模拟执行时,如果加载SO时配置为不执行Init相关函数,这导致整个SO中的字符串都没有被解密,自然输出就是一团”乱码“。
由此还可以衍生出一个小话题——如果样本中的字符串被加密了,如何还原?使得分析者可以愉快的用IDA静态分析?
- 从内存中Dump出解密后的SO或者字符串(可以用Frida/IDA 脚本/ adb 等),将结果回填或者说修复本身SO。
- 使用Unicorn或基于Unicorn的模拟执行工具(Unidbg、ExAndroidNativeemu等)运行SO,dump解密后的虚拟内存,回填修复SO
2.执行native_init
为啥要执行native_init,看jadx,他注册so的时候,同时执行了这个方法,那么我们要完全的模拟安卓环境,那就也要先执行这个方法,再执行核心的方法
再看看,刚才的main函数执行打印的日志,刚好,已经有了native的方法地址
用ida看看是thumb还是arm,看这个应该是thumb
执行下这个方法:
卧槽,这个啥情况,意思是没有这个方法,那不急,不加1看看
现在好像可以了,这个就尴尬了,跟咱们之前说的规则对不上啊,这就奇怪了。
根据大佬的解答,我们这里拿的地址是unidbg打印的地址,而不是像之前在ida看到的地址,unidbg的地址是真实的地址,如果是arm的话,就会自动+1
再仔细看看这个,好像是报错:
它提示,意思是不支持这个方法,可以的,终于到了补环境的环节
3.unidbg补环境
点进第一个报错的地方看看:
确实有补了很多java环境,但是差我们这个,那我们就自己补了,怎么补,关于补法,有两种实践方法都很有道理
- 全部在用户类中补,防止项目迁移或者Unidbg更新带来什么问题,这样做代码的移植性比较好。
- 样本的自定义JAVA方法在用户类中补,通用的方法在AbstractJNI中补,这样做的好处是,之后运行的项目如果调用通用方法,就不用做重复的修补工作
那么这里我就直接在自己定义的地方补了,防止对unidbg源码修改过多,没啥意义。再看,这个报错,它要的是一个context,那么给他一个context就行。context怎么来,在第二篇有讲过了:
vm.resolveClass("android/content/Context").newObject(null)
再运行看看,卧槽,内存不够了,这么猛的吗
没事,查阅资料:
unidbg/SignalTest.java at 56c1397c639716817896766df9bbce9c3a0a15b9 · zhkl0228/unidbg (github.com)
加一行这个就行:
再次运行,现在好像没报错了:
4.执行sign方法
解析来开始执行加密方法sign了:
点进去,
照着补了
运行,又有新的环境缺失:
捋一下完整的流程,在com/izuiyou/common/base/BaseApplication中调用getAppContext方法,获得一个Context上下文,然后getClass获取它的类,最后查看它的类名。类名就是这一系列操作的最终目的,我们前面几步都只浅浅的补了一下,只能说类型给对了,别的都没给。但只要最后的类名给它返回正确的字符串,就没问题。
用objection 的wallerbreaker 看看:
看这个,说明class就是这个AppController了,补完接着跑:
它要一个文件的地址,补完接着跑,
它要绝对路径,其实还是上面那个,接着补,新的报错:
查看这个是否在调试,这里,按正常的逻辑,肯定是给false
又有新的报错,继续补:
卧槽,好像终于出结果了!!!!,不容易啊。
为了保险起见,还是验证下,跟hook到的值,是否能保持一致
5.验证值
先用frida 重新hook下这个方法:
本来想把这个bytearray转成字符串看看的,试了以下几种方法,都不行:
console.log('sign arg2===》', ByteString.of(arg2).utf8())
console.log('sign arg2===》', ByteString.of(arg2).toString())
console.log('sign arg2===》', hex2str(ByteString.of(arg2).hex()))
console.log('sign arg2===》', jbhexdump(arg2))
console.log('sign arg2===》', Java.use("java.lang.String").$new(Java.array('byte', arg2)))
console.log(Java.use("java.lang.String").$new(arg2))
那没法了,直接复制这个byte array吧
unidbg里稍微调一下:
牛逼,结果对上了。舒服
完整代码:
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 | package com.xiaochuankeji; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.linux.android.dvm.api.AssetManager; import com.github.unidbg.linux.android.dvm.array.ByteArray; import com.github.unidbg.memory.Memory; import com.weibo.international; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; public class tieba extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; private final Module module; tieba() { // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验 emulator = AndroidEmulatorBuilder.for32Bit().setProcessName( "cn.xiaochuankeji.tieba" ).build(); // 获取模拟器的内存操作接口 final Memory memory = emulator.getMemory(); emulator.getSyscallHandler().setEnableThreadDispatcher( true ); // 设置系统类库解析 memory.setLibraryResolver( new AndroidResolver( 23 )); // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作 vm = emulator.createDalvikVM( new File( "unidbg-android\\src\\test\\java\\com\\xiaochuankeji\\right573.apk" )); // 加载目标SO DalvikModule dm = vm.loadLibrary( new File( "unidbg-android\\src\\test\\java\\com\\xiaochuankeji\\libnet_crypto.so" ), true ); // 加载so到虚拟内存 //获取本SO模块的句柄,后续需要用它 module = dm.getModule(); vm.setJni( this ); // 设置JNI vm.setVerbose( true ); // 打印日志 dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad } public static void main(String[] args) { tieba test = new tieba(); // System.out.println(test); test.native_init(); test.sign(); } public void native_init() { List<Object> list = new ArrayList<>( 10 ); list.add(vm.getJNIEnv()); // arg1,env list.add( 0 ); // arg2,jobject module.callFunction(emulator, 0x4a069 , list.toArray()); } @Override public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) { switch (signature) { case "com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;" : return vm.resolveClass( "android/content/Context" ).newObject( null ); } return super .callStaticObjectMethodV(vm, dvmClass, signature, vaList); } @Override public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) { switch (signature) { case "android/content/Context->getClass()Ljava/lang/Class;" : return dvmObject.getObjectType(); case "java/lang/Class->getSimpleName()Ljava/lang/String;" : return new StringObject(vm, "AppController" ); case "android/content/Context->getFilesDir()Ljava/io/File;" : case "java/lang/String->getAbsolutePath()Ljava/lang/String;" : return new StringObject(vm, "/data/user/0/cn.xiaochuankeji.tieba/files" ); } return super .callObjectMethodV(vm, dvmObject, signature, vaList); } @Override public boolean callStaticBooleanMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) { switch (signature) { case "android/os/Debug->isDebuggerConnected()Z" : return false ; } return super .callStaticBooleanMethodV(vm, dvmClass, signature, vaList); } @Override public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) { switch (signature) { case "android/os/Process->myPid()I" : return emulator.getPid(); } return super .callStaticIntMethodV(vm, dvmClass, signature, vaList); } public String sign() { List<Object> list = new ArrayList<>( 10 ); list.add(vm.getJNIEnv()); // arg1,env list.add( 0 ); // arg2,jobject list.add(vm.addLocalObject( new StringObject(vm, "https://api.izuiyou.com/index/recommend" ))); // arg3 byte [] ss = new byte []{- 61 , 8 , 81 , 112 ,- 113 , 62 , 93 , 124 ,- 101 , 59 ,- 104 , 72 , 103 ,- 122 ,- 91 , 69 ,- 101 , 86 , 18 , 29 , 118 ,- 53 ,- 110 ,- 34 ,- 35 , 100 , 9 , 114 ,- 24 ,- 56 , 84 , 20 ,- 62 , 117 ,- 95 , 76 , 90 ,- 53 , 66 , 85 ,- 128 ,- 104 , 126 , 114 ,- 91 , 13 , 111 , 11 , 121 , 12 , 77 , 68 , 101 , 52 ,- 60 , 25 , 61 ,- 30 , 38 ,- 59 , 118 , 31 , 110 , 9 , 109 ,- 87 , 119 , 83 ,- 12 , 48 ,- 111 , 41 , 116 ,- 114 , 103 , 98 , 43 ,- 12 ,- 84 , 122 ,- 9 ,- 91 , 28 ,- 26 ,- 79 ,- 45 ,- 74 ,- 52 , 6 , 56 , 63 ,- 32 , 108 , 106 , 3 ,- 23 ,- 67 , 56 , 35 ,- 100 , 91 ,- 42 ,- 22 ,- 68 ,- 112 ,- 83 ,- 79 , 39 , 49 , 92 , 112 ,- 111 , 100 , 103 ,- 127 , 16 ,- 112 ,- 10 ,- 12 , 83 , 46 ,- 108 ,- 121 ,- 54 ,- 16 , 26 , 82 ,- 102 , 83 , 97 ,- 115 ,- 2 , 118 ,- 103 ,- 52 ,- 46 ,- 118 ,- 120 , 9 , 63 , 59 , 77 , 31 , 9 ,- 14 , 68 , 69 ,- 125 ,- 16 , 105 ,- 88 , 103 ,- 80 , 61 , 78 , 115 , 112 ,- 4 , 10 , 101 ,- 21 , 53 , 39 ,- 78 , 57 , 92 , 56 ,- 77 , 20 ,- 17 , 79 ,- 76 ,- 82 ,- 12 , 17 ,- 4 , 2 , 84 , 115 ,- 71 , 20 , 115 ,- 58 ,- 105 , 116 ,- 102 ,- 103 ,- 43 ,- 105 , 96 , 95 ,- 35 ,- 53 ,- 35 ,- 56 ,- 7 , 104 , 7 , 72 , 20 ,- 113 ,- 109 , 37 , 117 , 107 , 64 , 37 , 41 ,- 107 , 70 , 10 ,- 12 ,- 127 ,- 37 ,- 24 , 74 , 21 , 66 ,- 82 , 57 ,- 7 , 76 , 97 ,- 37 ,- 19 ,- 18 , 4 , 60 ,- 49 ,- 15 , 120 ,- 24 , 12 ,- 23 , 3 ,- 63 ,- 55 ,- 12 ,- 70 , 65 ,- 16 , 117 , 9 ,- 90 , 35 ,- 3 , 110 ,- 86 , 49 ,- 97 , 24 , 85 , 60 , 38 , 16 ,- 36 ,- 64 , 24 , 117 ,- 55 , 106 , 9 , 67 ,- 5 ,- 115 ,- 74 , 20 ,- 83 , 32 , 5 , 18 , 54 , 17 , 93 , 83 ,- 18 ,- 19 , 56 , 78 , 87 , 80 , 57 , 46 ,- 105 , 10 , 49 ,- 109 , 97 , 4 ,- 104 , 48 , 69 , 73 , 119 , 45 , 9 ,- 57 , 74 ,- 44 , 3 ,- 97 ,- 101 , 57 , 124 , 44 ,- 17 ,- 54 , 66 ,- 94 , 32 ,- 64 ,- 46 ,- 30 , 87 ,- 104 , 17 ,- 68 ,- 85 ,- 69 , 36 , 46 ,- 42 , 23 , 16 , 0 ,- 117 ,- 72 , 117 ,- 20 ,- 96 , 3 ,- 115 , 104 ,- 11 ,- 26 ,- 35 ,- 32 , 127 , 24 , 37 , 21 , 77 , 16 , 66 ,- 75 , 124 , 101 , 42 , 117 , 118 , 89 , 26 ,- 86 , 62 , 45 ,- 106 ,- 111 , 6 ,- 74 ,- 83 , 13 , 0 , 32 ,- 120 ,- 94 ,- 56 , 44 , 0 ,- 12 ,- 51 , 58 , 105 , 1 ,- 74 , 8 , 105 , 65 , 22 ,- 98 , 112 ,- 90 ,- 66 ,- 13 ,- 126 ,- 96 ,- 122 ,- 113 ,- 97 ,- 85 ,- 100 ,- 126 , 123 , 47 ,- 89 , 109 , 14 , 93 , 15 , 35 ,- 61 , 46 , 76 , 40 , 63 , 119 , 52 , 87 ,- 19 ,- 28 , 41 ,- 11 , 68 , 17 , 69 ,- 101 , 21 ,- 60 ,- 73 , 2 ,- 72 ,- 97 ,- 95 ,- 98 ,- 20 ,- 97 , 65 ,- 8 ,- 29 ,- 4 , 78 ,- 118 , 25 ,- 31 ,- 16 ,- 105 , 90 , 91 , 38 ,- 100 , 77 , 24 , 34 ,- 28 ,- 23 , 81 , 74 , 44 ,- 78 , 68 ,- 62 , 115 , 125 ,- 109 ,- 76 ,- 97 , 88 , 51 ,- 13 , 100 ,- 38 , 110 , 110 ,- 51 , 17 , 51 , 5 , 91 ,- 24 , 10 ,- 102 , 40 , 60 ,- 27 , 106 , 71 , 97 ,- 125 , 37 ,- 75 , 40 , 40 ,- 43 , 101 ,- 8 , 41 ,- 124 ,- 8 , 93 ,- 18 ,- 128 , 8 ,- 50 ,- 128 ,- 128 ,- 99 ,- 23 , 97 ,- 25 ,- 118 ,- 25 ,- 23 , 24 ,- 125 ,- 47 ,- 91 , 108 ,- 66 , 4 , 73 , 19 , 31 ,- 34 , 62 ,- 78 , 122 ,- 81 , 5 , 41 , 11 , 66 ,- 26 ,- 116 , 81 , 107 ,- 14 ,- 106 ,- 110 , 35 , 78 ,- 9 ,- 88 , 20 ,- 41 ,- 111 ,- 37 , 39 ,- 104 , 82 , 71 ,- 36 ,- 116 ,- 47 , 29 , 24 ,- 107 , 24 , 32 ,- 7 ,- 90 ,- 52 , 124 ,- 94 ,- 107 , 57 ,- 7 , 117 , 38 ,- 99 ,- 37 , 50 ,- 126 ,- 68 , 45 ,- 32 ,- 25 ,- 118 ,- 69 , 20 , 55 ,- 24 , 40 , 17 ,- 116 , 111 ,- 36 ,- 15 , 109 , 102 , 37 ,- 7 ,- 118 , 63 , 88 , 52 , 46 , 58 ,- 91 , 111 , 33 ,- 23 , 102 , 116 ,- 77 ,- 100 , 102 ,- 124 , 8 , 7 , 92 ,- 84 ,- 33 ,- 26 , 54 ,- 4 , 75 , 56 , 100 ,- 5 ,- 59 ,- 66 , 51 , 127 ,- 126 ,- 31 , 39 ,- 20 ,- 81 , 72 , 106 , 64 ,- 71 , 8 , 88 , 9 ,- 18 ,- 50 ,- 118 ,- 109 , 31 ,- 55 , 11 , 89 , 0 ,- 49 ,- 69 ,- 54 ,- 118 , 14 ,- 78 , 109 , 36 , 95 ,- 6 , 68 , 21 , 58 ,- 86 ,- 103 , 55 , 94 , 4 ,- 23 ,- 75 , 106 ,- 60 ,- 20 , 72 , 3 ,- 66 , 54 , 78 , 12 ,- 9 ,- 70 ,- 13 , 106 ,- 112 ,- 51 , 100 , 62 ,- 101 , 28 ,- 63 , 8 ,- 120 ,- 67 , 24 ,- 10 , 50 , 41 , 114 ,- 41 , 42 ,- 108 ,- 19 ,- 116 ,- 62 ,- 45 , 17 , 112 , 81 , 91 , 18 ,- 108 ,- 113 , 108 , 108 ,- 88 ,- 18 , 57 ,- 40 , 117 ,- 64 , 38 , 65 , 36 ,- 24 ,- 90 , 53 ,- 61 ,- 37 ,- 45 ,- 70 ,- 14 ,- 41 , 109 , 3 ,- 59 ,- 7 ,- 101 ,- 87 , 84 ,- 39 , 25 , 85 ,- 91 ,- 111 , 17 , 35 , 77 ,- 57 , 75 ,- 105 ,- 97 , 65 ,- 37 ,- 103 ,- 112 ,- 62 , 51 ,- 42 , 40 , 59 , 70 , 79 , 50 ,- 83 , 63 ,- 23 ,- 6 , 58 , 29 ,- 5 ,- 3 ,- 33 ,- 29 ,- 91 ,- 59 ,- 36 , 55 , 34 , 24 , 74 , 38 ,- 72 , 99 , 125 , 84 ,- 124 , 60 , 97 ,- 111 , 47 , 40 ,- 29 ,- 85 ,- 76 ,- 119 ,- 89 , 106 ,- 1 , 38 , 17 ,- 17 ,- 123 , 94 ,- 104 ,- 85 ,- 10 , 13 , 53 , 110 , 103 , 100 , 123 ,- 21 ,- 14 , 107 , 11 , 45 ,- 47 ,- 22 ,- 40 , 7 ,- 102 , 26 , 59 , 98 , 120 ,- 28 ,- 27 , 123 , 55 , 97 , 18 , 23 ,- 111 , 109 ,- 127 ,- 14 , 27 , 88 ,- 19 ,- 93 , 59 ,- 118 ,- 118 , 114 ,- 17 ,- 55 ,- 99 , 86 ,- 87 ,- 73 ,- 80 , 17 , 71 ,- 32 ,- 37 ,- 50 , 53 , 29 ,- 112 ,- 81 ,- 124 , 89 , 11 , 83 , 41 ,- 41 , 24 ,- 75 , 6 , 27 , 54 ,- 121 , 12 ,- 19 , 110 ,- 73 , 35 ,- 111 , 51 ,- 118 , 115 , 30 , 106 ,- 56 , 32 , 99 , 39 ,- 75 , 67 ,- 104 ,- 77 ,- 124 , 71 , 7 ,- 36 ,- 38 , 75 , 1 ,- 8 ,- 96 ,- 44 , 99 , 115 ,- 7 , 28 ,- 128 , 38 , 39 ,- 74 ,- 43 ,- 24 ,- 35 }; ByteArray inbarr = new ByteArray(vm, ss); list.add(vm.addGlobalObject(inbarr)); //arg4 Number number = module.callFunction(emulator, 0x4a28d , list.toArray()); String result = vm.getObject(number.intValue()).getValue().toString(); return result; } } |
python调用
有没有想过,把这个项目,打包成jar后,给py调用呢,这样不就可以直接模拟调用了?有点意思对吧,上一章是直接给java调用,好像还没有给python调用过
先改一下这两个文件的路径,然后打包
打包流程就省略了,前面有
用python调用,记得把apk和so也放到这个py所在的路径下,因为打包的时候写的相对路径,谁调用,谁就是主程序,就要把这两个文件放到主程序文件所在同级目录:
这里这个test.py就是我的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # coding:utf- 8 import os import subprocess import jpype import time def query_by_java_jar(jar_path, param=None): if param: execute = "java -jar {} '{}'" .format(jar_path, param) else : execute = "java -jar {}" .format(jar_path) print(execute) output = subprocess.Popen(execute, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) res = output.stdout.readlines() print( 'result' , res) return res if __name__ == '__main__' : query_by_java_jar(r "E:\work\myproject\unidbg\out\artifacts\unidbg_jar\unidbg.jar" ) |
运行结果:
我试了下那个jpype,感觉各种问题,还是这个subproess方便点,直接出结果
算法还原
用findhash,一顿搜索
然后找到这个函数
鼠标直接点过去:
按tab键
光标放上去,然后键盘按【h】,它就变成这样了
把这4个都这么操作下:
又由,先看看md5的源码:
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 | import binascii SV = [ 0xd76aa478 , 0xe8c7b756 , 0x242070db , 0xc1bdceee , 0xf57c0faf , 0x4787c62a , 0xa8304613 , 0xfd469501 , 0x698098d8 , 0x8b44f7af , 0xffff5bb1 , 0x895cd7be , 0x6b901122 , 0xfd987193 , 0xa679438e , 0x49b40821 , 0xf61e2562 , 0xc040b340 , 0x265e5a51 , 0xe9b6c7aa , 0xd62f105d , 0x2441453 , 0xd8a1e681 , 0xe7d3fbc8 , 0x21e1cde6 , 0xc33707d6 , 0xf4d50d87 , 0x455a14ed , 0xa9e3e905 , 0xfcefa3f8 , 0x676f02d9 , 0x8d2a4c8a , 0xfffa3942 , 0x8771f681 , 0x6d9d6122 , 0xfde5380c , 0xa4beea44 , 0x4bdecfa9 , 0xf6bb4b60 , 0xbebfbc70 , 0x289b7ec6 , 0xeaa127fa , 0xd4ef3085 , 0x4881d05 , 0xd9d4d039 , 0xe6db99e5 , 0x1fa27cf8 , 0xc4ac5665 , 0xf4292244 , 0x432aff97 , 0xab9423a7 , 0xfc93a039 , 0x655b59c3 , 0x8f0ccc92 , 0xffeff47d , 0x85845dd1 , 0x6fa87e4f , 0xfe2ce6e0 , 0xa3014314 , 0x4e0811a1 , 0xf7537e82 , 0xbd3af235 , 0x2ad7d2bb , 0xeb86d391 ] # 根据ascil编码把字符转成对应的二进制 def binvalue(val, bitsize): binval = bin (val)[ 2 :] if isinstance (val, int ) else bin ( ord (val))[ 2 :] if len (binval) > bitsize: raise ( "binary value larger than the expected size" ) while len (binval) < bitsize: binval = "0" + binval return binval def string_to_bit_array(text): array = list () for char in text: binval = binvalue(char, 8 ) array.extend([ int (x) for x in list (binval)]) return array # 循环左移 def leftCircularShift(k, bits): bits = bits % 32 k = k % ( 2 * * 32 ) upper = (k << bits) % ( 2 * * 32 ) result = upper | (k >> ( 32 - (bits))) return (result) # 分块 def blockDivide(block, chunks): result = [] size = len (block) / / chunks for i in range ( 0 , chunks): result.append( int .from_bytes(block[i * size:(i + 1 ) * size], byteorder = "little" )) return result # F函数作用于“比特位”上 # if x then y else z def F(X, Y, Z): compute = ((X & Y) | ((~X) & Z)) return compute # if z then x else y def G(X, Y, Z): return ((X & Z) | (Y & (~Z))) # if X = Y then Z else ~Z def H(X, Y, Z): return (X ^ Y ^ Z) def I(X, Y, Z): return (Y ^ (X | (~Z))) # 四个F函数 def FF(a, b, c, d, M, s, t): result = b + leftCircularShift((a + F(b, c, d) + M + t), s) return (result) def GG(a, b, c, d, M, s, t): result = b + leftCircularShift((a + G(b, c, d) + M + t), s) return (result) def HH(a, b, c, d, M, s, t): result = b + leftCircularShift((a + H(b, c, d) + M + t), s) return (result) def II(a, b, c, d, M, s, t): result = b + leftCircularShift((a + I(b, c, d) + M + t), s) return (result) # 数据转换 def fmt8(num): bighex = "{0:08x}" . format (num) binver = binascii.unhexlify(bighex) result = "{0:08x}" . format ( int .from_bytes(binver, byteorder = 'little' )) return (result) # 计算比特长度 def bitlen(bitstring): return len (bitstring) * 8 def md5sum(msg): # 计算比特长度,如果内容过长,64个比特放不下。就取低64bit。 msgLen = bitlen(msg) % ( 2 * * 64 ) # 先填充一个0x80,其实是先填充一个1,后面跟对应个数的0,因为一个明文的编码至少需要8比特,所以直接填充 0b10000000即0x80 msg = msg + b '\x80' # 0x80 = 1000 0000 # 似乎各种编码,即使是一个字母,都至少得1个字节,即8bit才能表示,所以不会出现原文55bit,pad1就满足的情况?可是不对呀,要是二进制文件呢? # 填充0到满足要求为止。 zeroPad = ( 448 - (msgLen + 8 ) % 512 ) % 512 zeroPad / / = 8 msg = msg + b '\x00' * zeroPad + msgLen.to_bytes( 8 , byteorder = 'little' ) # 计算循环轮数,512个为一轮 msgLen = bitlen(msg) iterations = msgLen / / 512 # 初始化变量 # 算法魔改的第一个点,也是最明显的点 A = 0x67452301 B = 0xefcdab89 C = 0x98badcfe D = 0x10325476 # MD5的主体就是对abcd进行n次的迭代,所以得有个初始值,可以随便选,也可以用默认的魔数,这个改起来毫无风险,所以大家爱魔改它,甚至改这个都不算魔改。 # main loop for i in range ( 0 , iterations): a = A b = B c = C d = D block = msg[i * 64 :(i + 1 ) * 64 ] # 明文的处理,顺便调整了一下端序 M = blockDivide(block, 16 ) # Rounds a = FF(a, b, c, d, M[ 0 ], 7 , SV[ 0 ]) d = FF(d, a, b, c, M[ 1 ], 12 , SV[ 1 ]) c = FF(c, d, a, b, M[ 2 ], 17 , SV[ 2 ]) b = FF(b, c, d, a, M[ 3 ], 22 , SV[ 3 ]) a = FF(a, b, c, d, M[ 4 ], 7 , SV[ 4 ]) d = FF(d, a, b, c, M[ 5 ], 12 , SV[ 5 ]) c = FF(c, d, a, b, M[ 6 ], 17 , SV[ 6 ]) b = FF(b, c, d, a, M[ 7 ], 22 , SV[ 7 ]) a = FF(a, b, c, d, M[ 8 ], 7 , SV[ 8 ]) d = FF(d, a, b, c, M[ 9 ], 12 , SV[ 9 ]) c = FF(c, d, a, b, M[ 10 ], 17 , SV[ 10 ]) b = FF(b, c, d, a, M[ 11 ], 22 , SV[ 11 ]) a = FF(a, b, c, d, M[ 12 ], 7 , SV[ 12 ]) d = FF(d, a, b, c, M[ 13 ], 12 , SV[ 13 ]) c = FF(c, d, a, b, M[ 14 ], 17 , SV[ 14 ]) b = FF(b, c, d, a, M[ 15 ], 22 , SV[ 15 ]) a = GG(a, b, c, d, M[ 1 ], 5 , SV[ 16 ]) d = GG(d, a, b, c, M[ 6 ], 9 , SV[ 17 ]) c = GG(c, d, a, b, M[ 11 ], 14 , SV[ 18 ]) b = GG(b, c, d, a, M[ 0 ], 20 , SV[ 19 ]) a = GG(a, b, c, d, M[ 5 ], 5 , SV[ 20 ]) d = GG(d, a, b, c, M[ 10 ], 9 , SV[ 21 ]) c = GG(c, d, a, b, M[ 15 ], 14 , SV[ 22 ]) b = GG(b, c, d, a, M[ 4 ], 20 , SV[ 23 ]) a = GG(a, b, c, d, M[ 9 ], 5 , SV[ 24 ]) d = GG(d, a, b, c, M[ 14 ], 9 , SV[ 25 ]) c = GG(c, d, a, b, M[ 3 ], 14 , SV[ 26 ]) b = GG(b, c, d, a, M[ 8 ], 20 , SV[ 27 ]) a = GG(a, b, c, d, M[ 13 ], 5 , SV[ 28 ]) d = GG(d, a, b, c, M[ 2 ], 9 , SV[ 29 ]) c = GG(c, d, a, b, M[ 7 ], 14 , SV[ 30 ]) b = GG(b, c, d, a, M[ 12 ], 20 , SV[ 31 ]) a = HH(a, b, c, d, M[ 5 ], 4 , SV[ 32 ]) d = HH(d, a, b, c, M[ 8 ], 11 , SV[ 33 ]) c = HH(c, d, a, b, M[ 11 ], 16 , SV[ 34 ]) b = HH(b, c, d, a, M[ 14 ], 23 , SV[ 35 ]) a = HH(a, b, c, d, M[ 1 ], 4 , SV[ 36 ]) d = HH(d, a, b, c, M[ 4 ], 11 , SV[ 37 ]) c = HH(c, d, a, b, M[ 7 ], 16 , SV[ 38 ]) b = HH(b, c, d, a, M[ 10 ], 23 , SV[ 39 ]) a = HH(a, b, c, d, M[ 13 ], 4 , SV[ 40 ]) d = HH(d, a, b, c, M[ 0 ], 11 , SV[ 41 ]) c = HH(c, d, a, b, M[ 3 ], 16 , SV[ 42 ]) b = HH(b, c, d, a, M[ 6 ], 23 , SV[ 43 ]) a = HH(a, b, c, d, M[ 9 ], 4 , SV[ 44 ]) d = HH(d, a, b, c, M[ 12 ], 11 , SV[ 45 ]) c = HH(c, d, a, b, M[ 15 ], 16 , SV[ 46 ]) b = HH(b, c, d, a, M[ 2 ], 23 , SV[ 47 ]) a = II(a, b, c, d, M[ 0 ], 6 , SV[ 48 ]) d = II(d, a, b, c, M[ 7 ], 10 , SV[ 49 ]) c = II(c, d, a, b, M[ 14 ], 15 , SV[ 50 ]) b = II(b, c, d, a, M[ 5 ], 21 , SV[ 51 ]) a = II(a, b, c, d, M[ 12 ], 6 , SV[ 52 ]) d = II(d, a, b, c, M[ 3 ], 10 , SV[ 53 ]) c = II(c, d, a, b, M[ 10 ], 15 , SV[ 54 ]) b = II(b, c, d, a, M[ 1 ], 21 , SV[ 55 ]) a = II(a, b, c, d, M[ 8 ], 6 , SV[ 56 ]) d = II(d, a, b, c, M[ 15 ], 10 , SV[ 57 ]) c = II(c, d, a, b, M[ 6 ], 15 , SV[ 58 ]) b = II(b, c, d, a, M[ 13 ], 21 , SV[ 59 ]) a = II(a, b, c, d, M[ 4 ], 6 , SV[ 60 ]) d = II(d, a, b, c, M[ 11 ], 10 , SV[ 61 ]) c = II(c, d, a, b, M[ 2 ], 15 , SV[ 62 ]) b = II(b, c, d, a, M[ 9 ], 21 , SV[ 63 ]) A = (A + a) % ( 2 * * 32 ) B = (B + b) % ( 2 * * 32 ) C = (C + c) % ( 2 * * 32 ) D = (D + d) % ( 2 * * 32 ) result = fmt8(A) + fmt8(B) + fmt8(C) + fmt8(D) return result if __name__ = = "__main__" : data = str ( "https://api.izuiyou.com/index/recommend" ).encode( "UTF-8" ) print ( "plainText: " , data) print ( "result: " , md5sum(data)) |
对比在线cyberchef:
https://ctf.mzy0.com/CyberChef3
对比下那四个iv;
看样子,就是改了4个iv:
ok的,这个md5的算法,搞定
知识点总结
1.看so的调用逻辑,如果有loadlibrary的同时有调用某个方法,unidbg模拟执行的时候也要先调用这个方法
2.如果加载so文件的时候,给定的第二个参数是false,加上so文件有字符串加密和混淆的话就会乱码,所以这里最好给为true,ida里,shift+F7,可以看
3.补环境的时候,要根据报错,最核心的部分进行补环境,同时也要结合实际的执行逻辑
4.构造context,用vm.resolveClass("android/content/Context").newObject(null)
5.dvmObject.getObjectType()可以获取当前类的class
6.emulator.getPid(),获取pid
7.6种 frida里打印byte[]的方法
console.log('sign arg2===》', ByteString.of(arg2).utf8())
console.log('sign arg2===》', ByteString.of(arg2).toString())
console.log('sign arg2===》', hex2str(ByteString.of(arg2).hex()))
console.log('sign arg2===》', jbhexdump(arg2))
console.log('sign arg2===》', Java.use("java.lang.String").$new(Java.array('byte', arg2)))
console.log(Java.use("java.lang.String").$new(arg2))
8.ida,光标放到上面,按【h】,可以转为16进制
9.unidbg打印的地址是真实的地址,如果是arm的话,就会自动+1
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探