CISCN2024部分Re题复现 非常好国赛,使我的逆向旋转,frida,go,rust和cython什么玩意儿都来了,头都要大了
asm_re 上来一个汇编语言,看着就像是从IDA里面直接拖过来的东西
首先是看着像初始化的东西,可以不用管
这里可以看出是加密方法,学点汇编就大概知道是个什么意思了
这边是密文,简单写个脚本解密即可(虽然比赛过程中处处犯傻
1 2 3 4 5 6 7 8 enc = [0x1fd7, 0x21b7, 0x1e47, 0x2027, 0x26e7, 0x10d7, 0x1127, 0x2007, 0x11c7, 0x1e47, 0x1017, 0x1017, 0x11f7, 0x2007, 0x1037, 0x1107, 0x1f17, 0x10d7, 0x1017, 0x1017, 0x1f67, 0x1017, 0x11c7, 0x11c7, 0x1017, 0x1fd7, 0x1f17, 0x1107, 0xf47, 0x1127, 0x1037, 0x1e47, 0x1037, 0x1fd7, 0x1107, 0x1fd7, 0x1107, 0x2787] //密文注意小端序问题 flag = '' for i in enc: i = (((i -0x1e) ^ 0x4d) - 0x14) // 0x50 flag += chr(i) print(flag)
android_re 我勒个frida啊,配环境是一辈子的,frida是不会用的,现在只能开始恶补了(
这个是主函数,可以看到引用了inspect
来到inspect,可以看到是DES加密,需要Key和iv,
可以看到是有个lib被调用了,去找找看
上面是getiv,下面是getkey,可以看得出来这个静态分析的难度那是相当滴大(是这样的,我看了一小时看不出来半点,所以应该采用动调的方式,因为这个是so文件,只是个库,无法主动执行,所以需要用到动调(噩梦来了)
我这边利用的是unidbg,这边可供选择也挺多的,还有可爱(sb)的frida以及objection
他们都有一个共同的特点,环境难配
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 package com.ciscn; 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.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.memory.Memory; import com.github.unidbg.linux.android.dvm.DalvikModule; import com.github.unidbg.linux.android.dvm.DvmClass; import com.github.unidbg.linux.android.dvm.VM; import java.io.*; public class ciscna extends AbstractJni { private final AndroidEmulator emulator; //android 模拟器 a private final VM vm;//vm 虚拟机 private final Module module; private final Memory memory; private final DalvikModule dm; //将该类封装起来,以后直接套用模板 public ciscna(String apkFilePath, String soFilePath, String apkProcessname) throws IOException { // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验 emulator = AndroidEmulatorBuilder.for64Bit().setProcessName(apkProcessname).build(); memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); vm = emulator.createDalvikVM(new File(apkFilePath)); vm.setVerbose(false); // 打印日志,会在调用初始化 JNI_unload 打印一些信息,默认:false // 加载目标 SO dm = vm.loadLibrary(new File(soFilePath), true); // 加载 so 到虚拟内存,第二个参数:是否需要初始化 //获取本 SO 模块的句柄 module = dm.getModule(); vm.setJni(this); //设置 Jni,防止报错 //创建完后,需要调用 JNI_onload 函数 dm.callJNI_OnLoad(emulator); // 调用 JNI OnLoad,进行动态注册某些函数。如果都是静态注册,那就不用调用这个函数 vm.setVerbose(true); // Debugger debugger = emulator.attach(); // debugger.addBreakPoint(module.base + 0x1924C); // debugger.addBreakPoint(module.base + 0x19240); } public ciscna(AndroidEmulator emulator, VM vm, Module module, Memory memory, DalvikModule dm) { this.emulator = emulator; this.vm = vm; this.module = module; this.memory = memory; this.dm = dm; } public String func_getKey() { DvmClass dvmClass = vm.resolveClass("com.example.re11113.jni"); DvmObject<?> object = dvmClass.newObject(null); DvmObject<?> object1 = object.callJniMethodObject(emulator, "getkey()Ljava/lang/String;"); return object1.getValue().toString(); } public String func_getiv() { DvmClass dvmClass = vm.resolveClass("com.example.re11113.jni"); DvmObject<?> object = dvmClass.newObject(null); DvmObject<?> object1 = object.callJniMethodObject(emulator, "getiv()Ljava/lang/String;"); return object1.getValue().toString(); } //创建一个 main 函数 public static void main(String[] args) throws IOException { // 1、需要调用的 so 文件所在路径 String soFilePath = "/root/Documents/unidbg/unidbg-android/src/test/java/com/ciscn/libSecret_entrance.so"; // 2、APK 的路径 String apkFilePath = "/root/Documents/unidbg/unidbg-android/src/test/java/com/ciscn/app-debug.apk"; // 3、apk 进程名 String apkProcessname = "com.tencent.testvuln"; ciscna myapp = new ciscna(apkFilePath, soFilePath, apkProcessname); System.out.println("getKey result:" + myapp.func_getKey()); System.out.println("getiv result:" + myapp.func_getiv()); } }
看得很复杂对吧,实际上就一个模板,改改参数就行了(对我这个安卓动调苦手太好了
跑出来结果
可以看到key和iv的值都被我们成功的hook出来了
用flag{}包起来即可
当时表示用frida狠狠的报错(心态是逐渐崩溃
GoReverse 嘻嘻,加密东西写满了我整个小白板了,你个b东西
首先打开main_main函数
上面部分经过分析是反调试的东西,我谢谢你
接下来来到main__ptr_co6Pxq_Execute
函数
查看汇编发现就是一臭打招呼的,可以继续往下走了
然后来到重量级的main__ptr_B2bUPq_Execute
首先这部分代码是判断是否读取到了flag文件
中间这部分是加密(suprise
下面就是判断了
可以看得出来重要的都是一个个main为前缀的函数,一个个来吧
main_ylFyZv
首先是一大堆的代码,不过前面几行不要管,重要的是下面的for循环,可以看到是最友好的异或
这个就是我们拿来异或的密钥了
main_bytesToUint32s 这个是修改类型的,可以不用管
main_zQyveE
可以看出很明显的xxtea算法,delta值是改过的,main_KjmS3y
函数就是MX,但是顺序变了
main_Q05qm6
看到那个littleEndian了吗,这个就是这个函数的作用了
main_AkuFrt
最逆天的来了
可以看到这里用了随机数和sm4
看看汇编
可以看到了用了CTR的方法,key值我们是有了,但是iv我们却没有
那么我们的iv哪来呢,用了非常好的随机数,所以说我们需要动调(乐
首先这个程序有两处反调试,第一个我们在main_main函数的时候已经说过了
另一段在0x0438E28,也就是在调用main_main函数的runtime_main函数中,动调的时候发现执行这个call之后程序就退出了,nop掉之后程序正常运行。
随后就可以进行动调了
main_JrkmHd
这个函数则是AES加密,然后经典key值一查就有,iv一查一个不知道
要注意的是有个makeslice
函数,这个是go语言里面的切片函数,实际作用就是把你key里面的前16位拿过来然后变成了iv(乐
main_NJVCTq
最后一个base32就好了。
总体来说就是非常的套娃,头大,那么问题来了,密文呢
看看我们的最初的函数,可以看到我们需要有个flag文件,这里是复现,所以我们自己弄一个,然后远程连接一下
这不就来了吗,解密过程就不给了,也是个麻烦仙人(
whereThel1b 人生第二次碰到的cython,给了个py文件和so库,看看py
1 2 3 4 5 6 7 8 9 10 11 12 13 import whereThel1b flag = input("where is my flag:") flag = flag.encode() encry = [108, 117, 72, 80, 64, 49, 99, 19, 69, 115, 94, 93, 94, 115, 71, 95, 84, 89, 56, 101, 70, 2, 84, 75, 127, 68, 103, 85, 105, 113, 80, 103, 95, 67, 81, 7, 113, 70, 47, 73, 92, 124, 93, 120, 104, 108, 106, 17, 80, 102, 101, 75, 93, 68, 121, 26] whereThel1b.whereistheflag(flag) ret = whereThel1b.trytry(flag) if ret == encry: print("rrrrrrrrrrrright") else: print("wwwwwwwwwwwwwwwrong")
可以发现是调用了这个库,于是得对这个文件进行一个快乐的审计
首先是whereistheflag
,可以发现有base64加密,然后再往下看
发现有个random库,还有randint方法,按理说应该都会有个seed的,往下翻就没有什么其他有用的东西了,去trytry
函数
可以看到有random.seed
这个方法,那么问题来了,中间的值呢,比赛期间找到似都找不到
其实挺简单的,首先可以确定的是cython中的常量(即数字)会是__pyx_int_x
,其中x是数字,所以seed是
另外的话如果是利用变量这种东西,会以__pyx_n_s_a
这种形式出现,其中a就是我们的变量,字符串这种则是__pyx_kp_s_Hello_cython
这种形式,Hello_cython就是我们的字符串,具体的东西等我cython调试的博客有生之年出来再说(
exp:
1 2 3 4 5 6 7 8 9 10 11 12 import random import base64 encry = [108, 117, 72, 80, 64, 49, 99, 19, 69, 115, 94, 93, 94, 115, 71, 95, 84, 89, 56, 101, 70, 2, 84, 75, 127, 68, 103, 85, 105, 113, 80, 103, 95, 67, 81, 7, 113, 70, 47, 73, 92, 124, 93, 120, 104, 108, 106, 17, 80, 102, 101, 75, 93, 68, 121, 26] random.seed(0) for i in range(len(encry)): encry[i] = encry[i] ^ random.randint(0, len(encry)) flag = base64.b64decode(bytes(encry)) print(flag)