前5个加密字符还原, 请看龙哥的文章
SO逆向入门实战教程九——blackbox
https://blog.csdn.net/qq_38851536/article/details/118115569
打开ida, 从sub_3B3C方法继续向下分析
我们一步一步看:
首先是v19的赋值
uint8x8_t v19; // d16 v19.n64_u32[1] = *(_DWORD *)"6789BCDFGHJKRTVWMNPQ567";
uint8x8_t 的结构体如下:
这里用到了 ARM NEON 编程
https://www.cnblogs.com/xylc/p/5410517.html
DCD 4个字节
DCW 2个字节
DCB 1个字节
DCQ 8个字节
// v35就是前5个加密字节 v19.n64_u32[0] = *(_DWORD *)&v35[1];
这一步将上面得到的后4个字节的加密值赋值给 n64_u32[0]
就是上边的CD2D
备注: CD2D 是从上一部分内容获取的, 请查看前半部分的内容
https://blog.csdn.net/qq_38851536/article/details/118115569
uint32x4_t v34; // [sp+20h] [bp-168h] BYREF //寄存器中的每个元素的长度都扩展为原来的两倍,u8扩展为u16 v34 = vmovl_u16((uint16x4_t)vmovl_u8(v19).n128_u64[0]);
分为以下4步:
- vmovl_u8对读取的uint8x8进行宽度扩展
vmovl_u8() 将uint8x8_t --> uint16x8_t
这句的作用是 //convert to 16-bit and move to 128-bit reg
CD2D 由原来的每个字符一个字节, 变成一个字符2个字节, 高位补0
- 然后取前n128_u64[0], 即取前64位数据
.n128_u64[0]”这个地方,根据IDA的解析规则,这代表一个128位的数据只取前64位,如果是“.n128_u64[1]”的话就是取后64位
- 然后 使用(uint16x4_t) 强转成 uint16x4_t 类型
这句的作用是 //get low 64 bit and move them to 64-bit reg
- 最后 vmovl_u16 对读取的uint16x4进行宽度扩展
vmovl_u16() 将uint16x4_t --> uint32x4_t
CD2D 由原来的每个字符2个字节, 变成一个字符4个字节, 高位补0
参考文档:
- https://stackoverflow.com/questions/17808769/neon-loading-uint8-t-array-into-128-bit-register
- https://iosre.com/t/ida-neon/17347
- https://zhuanlan.zhihu.com/p/24702989
然后将拓展后的数据传入 sub_194C() 方法
在unidbg debugger 传到sub_194C方法是这样的
分析 sub_194C()
看上去调用了很多方法, 其实也没有什么 直接用java还原即可
下面放出代码:
public static int[] sub_194C(int[] a1) { int v1; int v3; int v4; int v5; int v6; int v7; int v8; int v9; int v18; int v23; int v24; int v22; int v21; int v20; int v19; int v10; int v11; int v12; int v13; int v14; int v15; int v16; v1 = a1[0]; v3 = sub_191E(v1); v4 = a1[1]; v24 = v3; v5 = (2 * v4) ^ 0x1B; if ( (v4 & 0x80) == 0 ){ v5 = 2 * v4; } v18 = v5 ^ v4; v23 = sub_18F8(v4); v6 = a1[2]; v22 = sub_18D4(v6); v7 = a1[3]; v21 = sub_191E(v4); v20 = sub_18F8(v6); v19 = sub_18D4(v7); v8 = v18 ^ sub_18D4(v1); v9 = v8 ^ sub_191E(v6); v10 = v9 ^ sub_18F8(v7); v11 = sub_18F8(v1); v12 = sub_18D4(v4); v13 = sub_191E(v7); a1[2] = v10 & 0xff; v14 = (2 * v1) ^ 0x1B; if ( (v1 & 0x80) == 0 ){ v14 = 2 * v1; } a1[1] = (v14 ^ v1 ^ v21 ^ v20 ^ v19) & 0xff; v15 = (2 * v7) ^ 0x1B; if ( (v7 & 0x80) == 0 ){ v15 = 2 * v7; } a1[0] = (v15 ^ v24 ^ v23 ^ v22 ^ v7) & 0xff; v16 = (2 * v6) ^ 0x1B; if ( (v6 & 0x80) == 0 ){ v16 = 2 * v6; } a1[3] = (v13 ^ v16 ^ v6 ^ v11 ^ v12) & 0xff; return a1; } public static int sub_191E(int a1) { boolean v2; int v3; int v4; v2 = (a1 & 0x80) != 0; v3 = (2 * a1) ^ 0x1b; if(!v2){ v3 = 2 * a1; } v4 = v3 ^ a1 ^ sub_18F8(a1); return sub_18D4(a1) ^ v4; } public static int sub_18F8(int a1) { int v1; int v2; v1 = (2 * a1) ^ 0x1b; if((a1 & 0x80) == 0){ v1 = 2 * a1; } v2 = (2 * v1) ^ 0x1b; if((v1 & 0x80) == 0){ v2 = 2 * v1; } return sub_18D4(v2 ^ v1); } public static int sub_18D4(int a1) { int v1; int v2; v1 = (2 * a1) ^ 0x1b; if((a1 & 0x80) == 0){ v1 = 2 * a1; } v2 = (2 * v1) ^ 0x1b; if((v1 & 0x80) == 0){ v2 = 2 * v1; } return v2 ^ v1; }
这里要注意的是:
算出来的数只取 低8位, 这个是通过调试知道的
调用sub_194C方法前:
CD2D
调用sub_194C方法后:
继续向下分析:
调用了sub_3864 方法
因为参数a2固定是100, 其实是调用了sub_37A4方法
sub_37A4 方法前面实现过了
继续向下走:
查看汇编流发现, sub_37A4 计算的结果还做了 乘法和减法的操作
对应的java代码:
传入参数是sub_194C的返回值
public static String getLast2(int[] v25){ // int[] v25 = new int[]{0x58,0xEB,0x45,0xF6}; int v26 = 0; int v28; int v29; String v29Str; for ( int i = 0; i != 4; ++i ) { v28 = v25[i]; v26 += v28; } System.out.println(v26); // 算出ida中 sprintf的v29, 这一步要看汇编 int a2 = 100; v29 = v26 - sub_37A4(v26, a2) * a2; // System.out.println(Integer.toHexString(v29)); // 处理v29 if (v29 > 100){ v29 = v29 % 100; } if (v29 < 10){ v29Str = "0" + v29; }else{ v29Str = "" + v29; } return v29Str; }
至此算法还原完成
下面验证算法
对比unidbg调用so生成的加密值和还原算法的加密值, 看是否相等
public static void main(String[] args) throws Exception { // 打印详细日志 // Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.DEBUG); // Logger.getLogger("com.github.unidbg.unix.UnixSyscallHandler").setLevel(Level.DEBUG); // Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG); // Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG); // Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG); // Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG); // PrintStream out = null; CrackBlackBox obj = new CrackBlackBox(); // 加密的key的明文 String keyText = "r0env"; for(int i = 0; i<10000; i += 1){ double d = Math.random(); int temp = (int)(d*1000000); if(! obj.callEncode(temp, keyText).equals(utils.encode(temp, keyText))){ System.out.println("两种方法的加密值不一样!!!!!!!!!!!!!!"); }; } obj.destroy(); }
日志如下:
unidbg调用so生成的加密值是: G7WJW18 [0, 0, 0, 0, 0, 3, -44, 7] hmacsha1加密值: 4e83863c0704078a26eF1Fb36b23905685ed2Fa4 索引值是: 4 前5个加密字节是: G7WJW 518 后2个加密字节是: 18 本地还原方法的最终加密值是: G7WJW18 unidbg调用so生成的加密值是: QRDH338 [0, 0, 0, 0, 0, 11, 25, 82] hmacsha1加密值: 1875d19268Fc156d09e0b4726e5bedFFbFca37ca 索引值是: 10 前5个加密字节是: QRDH3 638 后2个加密字节是: 38 本地还原方法的最终加密值是: QRDH338 unidbg调用so生成的加密值是: WXHGV98 [0, 0, 0, 0, 0, 8, 28, -87] hmacsha1加密值: 6d78a4050698aa9Fb20764321ad0b23bbFF53a0c 索引值是: 12 前5个加密字节是: WXHGV 398 后2个加密字节是: 98 本地还原方法的最终加密值是: WXHGV98 ....
算法验证成功