-
-
[原创]猿人学app 第二题算法还原
-
发表于: 2023-8-13 08:33 2265
-
使用unidbg辅助还原一个简单的算法
java层的定位太过简单这里就直接跳过了
ida打开样本so
发现是静态注册,这里给上JNIEnv
发现两个函数 sub_AE0,sub_C8C
先进 sub_AE0 看一眼
发现经过了一系列的操作然后调用了sub_A40
再进sub_C8C看一眼
发现多个右移操作,很像base64
看一眼byte_3000, 直接就看到码表了, base64八九不离十了
现在主要的任务是把 sub_AE0 还原,先进行一波简单的手工计算
1 2 3 4 5 6 | / / 这段代码的逻辑就是将输入的值填充到 16 位,填充的数是 16 - (v4 & 0xF )也就是 0x4 if ( (v4 & 0xF ) ! = 16 ) { memset((char * )v6 + v4, 16 - (v4 & 0xF ), 16 - (v4 & 0xF )); v4 = v4 - (v4 & 0xF ) + 16 ; } |
输入的值为 2:1691846483
转换成16进制并填充后 0x32 0x3a 0x31 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4
然后是一个循环
1 2 3 4 5 6 7 8 9 10 11 12 | do { for ( i = 0LL ; i ! = 16 ; + + i ) { / / 因为v27出还没有被赋值所以 * ((_BYTE * )&v27 + i)整体就是为 0 ,也就可以得到v8 = byte_E73[(unsigned __int8)( * ((_BYTE * )v9 + i) ^ v8)],而 * ((_BYTE * )v9 + i 刚好是填充后的第i个数 v8 = * ((_BYTE * )&v27 + i) ^ byte_E73[(unsigned __int8)( * ((_BYTE * )v9 + i) ^ v8)]; * ((_BYTE * )&v27 + i) = v8; } v11 = v7 + + = = (unsigned __int64)(v4 - 1 ) >> 4 ; / / v4 就是我们的 len 所以(v4 - 1 ) >> 4 的值就是 15 >> 4 = 0 ,所以 while 循环只进行一次 + + v9; } while ( !v11 ); |
下面计算16轮循环
v8 = 0 0x23 0x3a 0x31 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4
v8 = 0xc7 0x23 0xd4 0x31 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4
v8 = 0x54 0x23 0xd4 0xd9 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4
........ 还有好几轮懒得算了,直接上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 | package com.yuanrenxue.match2022.fragment.challenge; / / 创建包 import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Emulator; import com.github.unidbg.Module; import com.github.unidbg.arm.backend.DynarmicFactory; import com.github.unidbg.arm.backend.Unicorn2Factory; import com.github.unidbg.debugger.BreakPointCallback; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.DalvikModule; import com.github.unidbg.linux.android.dvm.DvmObject; import com.github.unidbg.linux.android.dvm.StringObject; import com.github.unidbg.linux.android.dvm.VM; import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject; import com.github.unidbg.memory.Memory; import com.github.unidbg.pointer.UnidbgPointer; import com.sun.jna.Pointer; import unicorn.Arm64Const; import java.io. File ; import java.util.ArrayList; import java.util. List ; public class ChallengeTwoFragment { private final AndroidEmulator emulator; private final VM vm; public int sum = 0 ; private final Memory memory; private final Module module; public ChallengeTwoFragment(){ / / 创建模拟器 emulator = AndroidEmulatorBuilder / / 可以创建 32 , 64 .for64Bit() / / 添加动态后端 .addBackendFactory(new Unicorn2Factory(true)) .build(); / / 获取内存接口 memory = emulator.getMemory(); / / sdk版本 memory.setLibraryResolver(new AndroidResolver( 23 )); / / 创建虚拟机 vm = emulator.createDalvikVM(); / / 加载elf文件 DalvikModule dalvikModule = vm.loadLibrary(new File ( "/home/cxq/unidbg/unidbg-android/src/test/java/com/yuanrenxue/match2022/fragment/challenge/libmatch02.so" ), true); module = dalvikModule.getModule(); vm.callJNI_OnLoad(emulator, module); } public void sign(String string) DvmObject object = ProxyDvmObject.createObject(vm, this); / / 调用 DvmObject object1 = object .callJniMethodObject(emulator, "sign(Ljava/lang/String;)Ljava/lang/String;" , string); / / 获取返回值 String value = (String) object1.getValue(); System.out.println( "args is =>" + string + " result is =>" + value); System.out.println( sum ); } public static void main(String[] args) { ChallengeTwoFragment mainActivity = new ChallengeTwoFragment(); mainActivity.sign( "2:1691846483" ); } } |
使用emulator.attach().addBreakPoint(module.base + 0xbac); 打下断点,读一下x12寄存器的内容
可以看到前面几个值和我们算出来的一样
继续往下在走
到下面v28不知道是啥,在unidbg看一眼,在0xBC0处下断点 读sp+0x30处的位置得到
1 2 3 4 5 | v28 = 23 F0 E8 B9 7A F3 68 50 / / 转换后的后八个字节 v13 = v28; 0x23 v14 = BYTE1(v28); 0xf0 v15 = BYTE2(v28); 0xe8 v16 = BYTE3(v28); 0xb9 |
同理可得后面几位
1 2 3 4 | v18 = BYTE4(v28); 0x7A v19 = BYTE5(v28); 0xF3 v20 = BYTE6(v28); 0x68 v21 = HIBYTE(v28); 0x50 |
由于 _QWORD 是一个无符号 64 位整数类型,也就是八个字节
*(_QWORD *)v17 = v12; 将v12的前八字节赋值到v17的前八个位置
其实就是把转换后的数据添加到input数据后面
1 | v22 = ((unsigned __int64)(len_ + 15 ) >> 4 ) + 1 ; / / 31 >> 4 + 1 = 2 ,whiule循环循环两次 |
也就是说sub_A40会执行两次
进入sub_A40 sub_A40(&v26, &v25, &v26);
第一次进入时
1 2 3 | v25 = 0x32 0x3a 0x31 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4 v6 = (int8x16_t * )malloc( 48u ); v6[ 1 ] = * a2; 0x32 0x3a 0x31 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4 |
1 2 | v6[ 2 ] = veorq_s8(v9, * a2); / / 这个的意思就是将v9 与 a2中的内容按位 ^ 后给到v6[ 2 ],v9初始并没有赋值所以 |
v6[2] = 0x32 0x3a 0x31 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4
v6就等于
1 2 3 | 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x32 0x3a 0x31 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4 0x32 0x3a 0x31 0x36 0x39 0x31 0x38 0x34 0x36 0x34 0x38 0x33 0x4 0x4 0x4 0x4 |
同样打开unidbg验证一下 在0xA94处下断点
dump出x0寄存器的内容
与推测内容一样
1 2 3 4 5 6 | for ( i = 0LL ; i ! = 48 ; + + i ) { v8 = v6 - >n128_u8[i] ^ byte_E73[v8]; / / 化简一下得到 v8 = v6[i] ^ byte_E73[v8] v6 - >n128_u8[i] = v8; / / v6[i] = v8; } |
这个轮数太多了不好算,直接看跑完循环后v6的内容
同样打开unidbg,在0xAC8 打下断点,读取x0寄存器的值
第二次进入sub_A40(&v26, &v25, &v26);
v26里面的内容就变成了上面x0寄存器的第一行内容了
v25 变成了之前计算到的 0x23 0xD4 0xD9 0xDF 0x8E 0x08 0x04 0x04 0x23 0xF0 0xE8 0xB9 0x7A 0xF3 0x68 0x50
v6[2] = veorq_s8(v9, *a2); 就变成了0x3C 0xAC 0x40 0xE4 0xAB 0x2B 0xB5 0x1D 0x10 0xBB 0xFB 0xF0 0x10 0x8A 0x26 0x5C
继续验证一下在0xa94 下断点 ,因为是要在第二次进入下断点所以需要输入c回车跳过第一个断点
打一个结束断点观察sub_A40第二次计算的返回值
取前16个字节放入sub_C8C中,用cyberchef快速验证一下是不是base64
frida 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | function hookJava() { Java.perform(function () { var Secure = Java.use( "com.yuanrenxue.match2022.fragment.challenge.ChallengeTwoFragment" ) Secure.sign.implementation = function (str1){ console.log(str1) / / str1 = "2:1691845341" var result = this.sign(str1) console.log(result) return result } }) } function hookNative() { var baseAddr = Module.findBaseAddress( "libmatch02.so" ); console.log( "find libmatch02 addr is =>" + baseAddr) var func_addr = baseAddr.add( 0xA40 ) Interceptor.attach(func_addr, { onEnter: function (args) { this.arg1 = args[ 0 ] this.arg2 = args[ 1 ] this.arg3 = args[ 2 ] console.log( "arg1 is => \n" ) console.log(hexdump(this.arg1)) console.log( "arg2 is => \n" ) console.log(hexdump(this.arg2)) },onLeave: function (retval) { console.log( "result is => \n" + hexdump(retval)) } }) } function hookNative1() { var baseAddr = Module.findBaseAddress( "libmatch02.so" ); console.log( "find libmatch02 addr is =>" + baseAddr) var func_addr = baseAddr.add( 0xAe0 ) Interceptor.attach(func_addr, { onEnter: function (args) { this.arg1 = args[ 0 ] this.arg2 = args[ 1 ] / / this.arg3 = args[ 2 ] console.log( "arg1 is => \n" ) console.log(hexdump(this.arg1)) console.log( "arg2 is => \n" ) console.log(hexdump(this.arg2)) },onLeave: function (retval) { console.log( "result is => \n" + hexdump(retval)) } }) } function main(){ hookJava() hookNative1() hookNative() } setImmediate(main) |
2:1691846483
KQoxyQVdDxnvn2oR0ohGyA==
结果正确
使用python进行还原
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 | import string import base64 arr = [ 0x63 , 0x7C , 0x77 , 0x7B , 0xF2 , 0x6B , 0x6F , 0xC5 , 0x30 , 1 , 0x67 , 0x2B , 0xFE , 0xD7 , 0xAB , 0x76 , 0xCA , 0x82 , 0xC9 , 0x7D , 0xFA , 0x59 , 0x47 , 0xF0 , 0xAD , 0xD4 , 0xA2 , 0xAF , 0x9C , 0xA4 , 0x72 , 0xC0 , 0xB7 , 0xFD , 0x93 , 0x26 , 0x36 , 0x3F , 0xF7 , 0xCC , 0x34 , 0xA5 , 0xE5 , 0xF1 , 0x71 , 0xD8 , 0x31 , 0x15 , 4 , 0xC7 , 0x23 , 0xC3 , 0x18 , 0x96 , 5 , 0x9A , 7 , 0x12 , 0x80 , 0xE2 , 0xEB , 0x27 , 0xB2 , 0x75 , 9 , 0x83 , 0x2C , 0x1A , 0x1B , 0x6E , 0x5A , 0xA0 , 0x52 , 0x3B , 0xD6 , 0xB3 , 0x29 , 0xE3 , 0x2F , 0x84 , 0x53 , 0xD1 , 0 , 0xED , 0x20 , 0xFC , 0xB1 , 0x5B , 0x6A , 0xCB , 0xBE , 0x39 , 0x4A , 0x4C , 0x58 , 0xCF , 0xD0 , 0xEF , 0xAA , 0xFB , 0x43 , 0x4D , 0x33 , 0x85 , 0x45 , 0xF9 , 2 , 0x7F , 0x50 , 0x3C , 0x9F , 0xA8 , 0x51 , 0xA3 , 0x40 , 0x8F , 0x92 , 0x9D , 0x38 , 0xF5 , 0xBC , 0xB6 , 0xDA , 0x21 , 0x10 , 0xFF , 0xF3 , 0xD2 , 0xCD , 0xC , 0x13 , 0xEC , 0x5F , 0x97 , 0x44 , 0x17 , 0xC4 , 0xA7 , 0x7E , 0x3D , 0x64 , 0x5D , 0x19 , 0x73 , 0x60 , 0x81 , 0x4F , 0xDC , 0x22 , 0x2A , 0x90 , 0x88 , 0x46 , 0xEE , 0xB8 , 0x14 , 0xDE , 0x5E , 0xB , 0xDB , 0xE0 , 0x32 , 0x3A , 0xA , 0x49 , 6 , 0x24 , 0x5C , 0xC2 , 0xD3 , 0xAC , 0x62 , 0x91 , 0x95 , 0xE4 , 0x79 , 0xE7 , 0xC8 , 0x37 , 0x6D , 0x8D , 0xD5 , 0x4E , 0xA9 , 0x6C , 0x56 , 0xF4 , 0xEA , 0x65 , 0x7A , 0xAE , 8 , 0xBA , 0x78 , 0x25 , 0x2E , 0x1C , 0xA6 , 0xB4 , 0xC6 , 0xE8 , 0xDD , 0x74 , 0x1F , 0x4B , 0xBD , 0x8B , 0x8A , 0x70 , 0x3E , 0xB5 , 0x66 , 0x48 , 3 , 0xF6 , 0xE , 0x61 , 0x35 , 0x57 , 0xB9 , 0x86 , 0xC1 , 0x1D , 0x9E , 0xE1 , 0xF8 , 0x98 , 0x11 , 0x69 , 0xD9 , 0x8E , 0x94 , 0x9B , 0x1E , 0x87 , 0xE9 , 0xCE , 0x55 , 0x28 , 0xDF , 0x8C , 0xA1 , 0x89 , 0xD , 0xBF , 0xE6 , 0x42 , 0x68 , 0x41 , 0x99 , 0x2D , 0xF , 0xB0 , 0x54 , 0xBB , 0x16 ] arr1 = [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x4 , 0x4 , 0x4 , 0x4 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x4 , 0x4 , 0x4 , 0x4 ] arr2 = [ 0 for _ in range ( 48 )] # arr1 = [0 for _ in range(48)] def getsign(sss): for i in range ( len (sss)): arr1[i + 16 ] = ord (sss[i]) # Convert characters to their ASCII values arr1[i + 32 ] = ord (sss[i]) # and assign to arr1 x8 = 0 for i in range ( 16 ): a = hex ( int (arr1[ 16 + i])) b = arr[ int (a, 16 ) ^ x8] x8 = b arr2[i + 16 ] = x8 v8 = 0 v7 = 0 while (v7 ! = 20 ): for i in range ( 48 ): v8 = arr1[i] ^ arr[v8 % 256 ] arr1[i] = v8 v8 + = v7 v7 = v7 + 1 for i in range ( 16 ): arr2[i] = arr1[i] for i in range ( 16 ): arr2[i + 32 ] = arr2[i] ^ arr2[i + 16 ] v8 = 0 v7 = 0 while (v7 ! = 20 ): for i in range ( 48 ): v8 = arr2[i] ^ arr[v8 % 256 ] arr2[i] = v8 v8 + = v7 v7 + = 1 return arr2[: 16 ] # '2:1691845341' # oFJCNh4R3fct9cahTWpfPw== byte_data = bytes(getsign( "2:1691845341" )) # print(getsign("2:1691845341")) encoded_data = base64.b64encode(byte_data) encoded_string = encoded_data.decode( 'utf-8' ) print (encoded_string) |