-
-
[原创]Android赛区题解
-
发表于: 2015-10-19 12:07 6646
-
1> Cobb的记忆
下载apk后解包反编译,反编译出的check.smali代码有4万多行,有3个函数:
access$_T11306:解析字符串,会在check函数里调用。
access$_T15566:也是解析字符串,会在check里被反射调用。
check:这个就是验证函数了,会调用上面两个access函数处理加密过的字符串,这些字符串应该是反射调用的函数名。
看了一圈,没有找到计算结果的代码片段,感觉4万多行代码没办法逆算法,于是尝试插入log代码打包,把关键信息打印出来。
搜索return,可以发现check函数里面只有一个return点,在12348行(修改过的代码,位置可能跟原来的代码位置有出入,下同)左右的
可以看到v4是一个boolean型,那么这个值应该就是比较的结果了。再搜索一下goto_0,在36184行左右看到代码:
那么这儿就是与正确key比较的地方了,在这里打上log(感谢蒸米@wooyun的代码:http://drops.wooyun.org/papers/6045)。如下:
这儿需要注意,java内置的long型是不能作为Object型传给log函数的,因此需要先转换为BigInteger型。另外,smali指令中一个long型是用两个寄存器存储的,在传参的时候要指定两个寄存器,如上面的v4,v5、v10,v11,否则运行时会报错。打印出的寄存器值如下图:
对于不同的输入,v4总是固定的520676,那么这个就是正确的key了。
得到了正确的key,还是不知道算法,于是往上面搜索v10,在29043行左右找到计算v10的算法,同样插入log,如下:
这样就可以打印出每轮计算后的值,如下:
通过每一轮计算的结果,可以发现一个规律:
输入1111,第一轮计算结果是1112。1112-1111 = 1;
第二轮是1117,1117-1112 = 5;
第三轮是1126,1126-1117 = 9;
第4轮是1139,1139-1126 13;
...
每一轮与上一轮的差值是一个等差数列,因此很容易有算法解出答案:
解出结果为395926。
2> Satio的要求
这个题最后是解掉了,但是没来得及提交答案比赛就结束了,比较可惜。
java层的逻辑比较简单,程序启动后会输出一个logcat,是"wojiushidaan"的莫斯码,这个并没有什么用。程序注册的onClick函数调用一个java层的Ch.a函数,它首先检查包的MD5值,如果不对就直接return false。这个步骤是用来防止重打包的,修改smali代码中的比较值就可以绕过去了。然后程序会调用libwbox.so中的ch函数,key的算法实际是存放在这里的。
取出这个so,拖进ida,简单看一下就能找到反调试的地方。sub_1284这个函数会读取/proc/pid/status中的TracerPid,如果发现不为0说明被调试,就发送信号杀死自己。so的.init_array中有个函数会创建一个线程循环调用这个函数。另外,在sub_1DE0中,算法开始之前也会调用一次这个函数。还有就是在算法自解密(详见下文)的过程中,也会查询这个值,如果不为0就会乱写一段代码,造成执行失败。
由上述分析,其实所有的反调试手段都依赖于/proc/pid/status中的TracerPid,只要保证它为0,就不会触发任何的反调试。因此可以通过修改内核的方式,使得这个值始终为0。修改kernel代码目录中的fs/proc/array.c,task_state是处理/proc/pid/status文件的函数,将191行的tpid修改为0,那么无论有没有被调试,/proc/pid/status中的TracerPid都会始终为0,这样就可以绕过反调试了。
绕过反调试之后,就尝试解密算法。程序本身的代码有一部分是加密的,在执行的过程中会一点点解密,然后返回的时候再次加密回去。在合适的地方下断点,dump内存,就可以获取解密后的代码了。过程中的主要函数如下:
相对偏移0x1DE0:这个函数会初始化一个16字节的内存,内容为0x00, 0x01, 0x02, ..., 0x0f,然后将用户的输入复制到这个地方。输入的有效位数是16字节,超出16字节的部分不参与运算。如果不足16字节,那么会被初始化的内容填充到16字节。随后这个函数就解密0x24C8处的代码,然后调用解密后的函数。调用0x24C8的指令地址为0x2198,在这个位置下断点,就可以获取解密后的0x24C8函数。
0x24C8这个函数首先处理input,伪代码:
随后像0x1DE0一样,解密下一个函数0x14A4。在0x2984处下断点,可以dump出解密后的0x14A4。
0x14A4这个函数同样首先处理input,这里用到了一个固定值的数组,地址为0x1DE0,内容如下:
伪代码:
然后,函数mmap一块内存,赋予执行权限,将算法代码复制进去解密并调用。算法的输入是上面处理过的input。然后与加密后的key值比较,将比较结果层层返回。加密后的key值是写死在代码里的,如下:
真正的算法实际是在这个mmap的内存中,在0x1944处下断点,可以dump出解密后的算法代码。
这个算法才是真正的开始,前面的反打包、反调试、代码的自解密跟算法一比根本不值一提。
IDA中F5后的代码如下:
代码大致可以分为两个循环,第一个循环用来初始化一个int型数组,数组为定值。在第二个循环中这个数组会参与运算。随后将输入的16字节按大字端产生4个int值,暂且称之为a,b,c,d。
这4个值会首先与4个定值进行异或,然后进入第二个循环,共9轮。
整个算法如下(略去产生数组的第一层循环,可以认为是一个常量):
逆出算法之后,觉得眼一黑:这个算法(我觉得)是无法逆运算的!只能采用暴力破解的方式。
仔细看“最后的处理”部分的代码,从af,bf,cf,df逆运算得到最后一轮的a,b,c,d比较简单,因为af的每个字节只与上轮的a,b,c,d中的一个值有关,有
b1(af ^ X[36]) = l(M[b1(a)])。这样只需要一层循环,就可以获得最后一轮a中的某一位。
依次类推,我们可以获取最后一轮的a、b、c、d。
对于9轮循环,问题就比较麻烦,因为a中一个字节与上轮循环中的a、b、c、d都有关系,虽然说有了正向的算法后可以通过4层循环来跑,但是4层循环太慢了。当时我测试了一下,在我机器上跑一个字节就要好几分钟,一轮跑4个字节,9轮,速度太慢了,感觉不应该这么粗暴的解。
仔细观察每轮循环中的算法,可以发现一个规律,这里以a为例说明。
循环问题解决后,其他的算法就是加加减减和异或,不再赘述。
最后放上我的解密代码,求得结果为kboloy0
下载apk后解包反编译,反编译出的check.smali代码有4万多行,有3个函数:
access$_T11306:解析字符串,会在check函数里调用。
access$_T15566:也是解析字符串,会在check里被反射调用。
check:这个就是验证函数了,会调用上面两个access函数处理加密过的字符串,这些字符串应该是反射调用的函数名。
看了一圈,没有找到计算结果的代码片段,感觉4万多行代码没办法逆算法,于是尝试插入log代码打包,把关键信息打印出来。
搜索return,可以发现check函数里面只有一个return点,在12348行(修改过的代码,位置可能跟原来的代码位置有出入,下同)左右的
move-result-object v4 check-cast v4, Ljava/lang/Boolean; invoke-virtual {v4}, Ljava/lang/Boolean;->booleanValue()Z :try_end_68 .catch Ljava/lang/reflect/InvocationTargetException; {:try_start_68 .. :try_end_68} :catch_c move-result v4 :goto_0 return v4
可以看到v4是一个boolean型,那么这个值应该就是比较的结果了。再搜索一下goto_0,在36184行左右看到代码:
const-wide/32 v4, 0xf4240 rem-long v4, v8, v4 const-wide/32 v6, 0x1e74e add-long/2addr v4, v6 cmp-long v4, v4, v10 # 比较v4和v10 if-nez v4, :cond_3 # 不相等跳到 cond_3: const/4 v4, 0x0 # v4 = false # goto/16 :goto_0 const/4 v4, 0x1 # v4 = true goto/16 :goto_0
那么这儿就是与正确key比较的地方了,在这里打上log(感谢蒸米@wooyun的代码:http://drops.wooyun.org/papers/6045)。如下:
const-wide/32 v6, 0x1e74e add-long/2addr v4, v6 invoke-static {v4, v5}, Ljava/math/BigInteger;->valueOf(J)Ljava/math/BigInteger; move-result-object v7 invoke-static {v7}, Lk2015/mzheng/MZLog;->Log(Ljava/lang/Object;)V cmp-long v4, v4, v10 invoke-static {v10, v11}, Ljava/math/BigInteger;->valueOf(J)Ljava/math/BigInteger; move-result-object v10 invoke-static {v10}, Lk2015/mzheng/MZLog;->Log(Ljava/lang/Object;)V if-nez v4, :cond_3 const/4 v4, 0x1 goto/16 :goto_0
这儿需要注意,java内置的long型是不能作为Object型传给log函数的,因此需要先转换为BigInteger型。另外,smali指令中一个long型是用两个寄存器存储的,在传参的时候要指定两个寄存器,如上面的v4,v5、v10,v11,否则运行时会报错。打印出的寄存器值如下图:
对于不同的输入,v4总是固定的520676,那么这个就是正确的key了。
得到了正确的key,还是不知道算法,于是往上面搜索v10,在29043行左右找到计算v10的算法,同样插入log,如下:
add-int/lit8 v7, v17, 0x4 move-object/from16 v16, v5 move-object v9, v14 move-wide/from16 v10, v20 invoke-static {v10, v11}, Ljava/math/BigInteger;->valueOf(J)Ljava/math/BigInteger; move-result-object v10 # v10转换成BigInteger型 invoke-static {v10}, Lk2015/mzheng/MZLog;->Log(Ljava/lang/Object;)V # 打印log invoke-virtual {v10}, Ljava/math/BigInteger;->longValue()J move-result-wide v10 # 把v10还原为long型 move/from16 v17, v7 move-object v7, v15 goto/16 :goto_1
这样就可以打印出每轮计算后的值,如下:
通过每一轮计算的结果,可以发现一个规律:
输入1111,第一轮计算结果是1112。1112-1111 = 1;
第二轮是1117,1117-1112 = 5;
第三轮是1126,1126-1117 = 9;
第4轮是1139,1139-1126 13;
...
每一轮与上一轮的差值是一个等差数列,因此很容易有算法解出答案:
i = 997 n = 520676 while i >= 1: n = n - i i = i - 4 print n
解出结果为395926。
2> Satio的要求
这个题最后是解掉了,但是没来得及提交答案比赛就结束了,比较可惜。
java层的逻辑比较简单,程序启动后会输出一个logcat,是"wojiushidaan"的莫斯码,这个并没有什么用。程序注册的onClick函数调用一个java层的Ch.a函数,它首先检查包的MD5值,如果不对就直接return false。这个步骤是用来防止重打包的,修改smali代码中的比较值就可以绕过去了。然后程序会调用libwbox.so中的ch函数,key的算法实际是存放在这里的。
取出这个so,拖进ida,简单看一下就能找到反调试的地方。sub_1284这个函数会读取/proc/pid/status中的TracerPid,如果发现不为0说明被调试,就发送信号杀死自己。so的.init_array中有个函数会创建一个线程循环调用这个函数。另外,在sub_1DE0中,算法开始之前也会调用一次这个函数。还有就是在算法自解密(详见下文)的过程中,也会查询这个值,如果不为0就会乱写一段代码,造成执行失败。
由上述分析,其实所有的反调试手段都依赖于/proc/pid/status中的TracerPid,只要保证它为0,就不会触发任何的反调试。因此可以通过修改内核的方式,使得这个值始终为0。修改kernel代码目录中的fs/proc/array.c,task_state是处理/proc/pid/status文件的函数,将191行的tpid修改为0,那么无论有没有被调试,/proc/pid/status中的TracerPid都会始终为0,这样就可以绕过反调试了。
绕过反调试之后,就尝试解密算法。程序本身的代码有一部分是加密的,在执行的过程中会一点点解密,然后返回的时候再次加密回去。在合适的地方下断点,dump内存,就可以获取解密后的代码了。过程中的主要函数如下:
相对偏移0x1DE0:这个函数会初始化一个16字节的内存,内容为0x00, 0x01, 0x02, ..., 0x0f,然后将用户的输入复制到这个地方。输入的有效位数是16字节,超出16字节的部分不参与运算。如果不足16字节,那么会被初始化的内容填充到16字节。随后这个函数就解密0x24C8处的代码,然后调用解密后的函数。调用0x24C8的指令地址为0x2198,在这个位置下断点,就可以获取解密后的0x24C8函数。
0x24C8这个函数首先处理input,伪代码:
for (i = 0; i < 16; i++) input[i] = input[i] + i;
随后像0x1DE0一样,解密下一个函数0x14A4。在0x2984处下断点,可以dump出解密后的0x14A4。
0x14A4这个函数同样首先处理input,这里用到了一个固定值的数组,地址为0x1DE0,内容如下:
(gdb) x/16xb $r0 x/16xb $r0 0x63e1fde0: 0x1f 0xbc 0xda 0xff 0xe6 0x4c 0xbc 0x44 0x63e1fde8: 0xf5 0xb8 0x13 0xc8 0xec 0xa8 0xcd 0xbd
伪代码:
for (i = 0; i < 16; i++) input[i] = input[i] + array[i];
然后,函数mmap一块内存,赋予执行权限,将算法代码复制进去解密并调用。算法的输入是上面处理过的input。然后与加密后的key值比较,将比较结果层层返回。加密后的key值是写死在代码里的,如下:
x/20xw 0x63E23D10 0x63e23d10: 0x2f77da5c 0x393ec6a3 0xedf3f0b6 0x86995a51
真正的算法实际是在这个mmap的内存中,在0x1944处下断点,可以dump出解密后的算法代码。
这个算法才是真正的开始,前面的反打包、反调试、代码的自解密跟算法一比根本不值一提。
IDA中F5后的代码如下:
int __fastcall sub_64da8000(char *a1, char *a2) { out = a2; in = (int *)a1; v2 = 0x6BCDC67A; v3 = &v79; v4 = 0; v5 = (unsigned int)&v79 | 4; v80 = 0x6BCDC67A; *(_DWORD *)(v5 + 4) = 0x6B2B7C9D; *(_DWORD *)(v5 + 8) = 0x8DA459B1; v6 = 0xAB9D0680; v83 = 0xAB9D0680; while ( 1 ) { if ( v4 & 3 ) { v7 = (int)&v3[v4]; v6 ^= v2; } else { v8 = v3; v9 = *(_WORD *)(int_64DA8758() + (((unsigned int)v6 >> 15) & 0x1FE)); v10 = (*(_WORD *)(int_64DA8758() + (((unsigned int)v6 >> 7) & 0x1FE)) << 16) & 0xFF0000 | (v9 << 24); v11 = v10 | (*(_WORD *)(int_64DA8758() + 2 * (unsigned __int8)v6) << 8) & 0xFF00; v12 = (v11 | *(_BYTE *)(int_64DA8758() + (((unsigned int)v6 >> 23) & 0x1FE))) ^ v2; v13 = sub_64DA8958(); v3 = v8; v6 = v12 ^ *(_DWORD *)(v13 + ((v4 + ((unsigned int)(v4 >> 31) >> 30)) & 0xFFFFFFFC)); v7 = (int)&v8[v4]; } *(_DWORD *)(v7 + 20) = v6; if ( v4 == 39 ) break; v2 = v3[v4++ + 2]; } v79 = 10; xor_4 = _byteswap_ulong(in[3]) ^ v83; // 0x80741682 xor_3 = _byteswap_ulong(in[2]) ^ v82; // 0xbb5517b4 xor_2 = _byteswap_ulong(in[1]) ^ v81; // 0x74ac851e v16 = v3 + 8; // // xor_1 = _byteswap_ulong(*in) ^ v80; // 0x3b22c94c v18 = 0; do { ina = v16; // 0x64d88e58 v19 = xor_1; out_4 = xor_3; out_8 = xor_2; out_12 = v18; v23 = (xor_1 >> 23) & 0x1FE; v24 = int_64DA8758(); v25 = xor_2; v26 = ((*(_WORD *)(v24 + v23) & 0xFF) << 8) | (*(_WORD *)(v24 + v23) << 16) | *(_WORD *)(v24 + v23) & 0xFF ^ ((unsigned int)*(_WORD *)(v24 + v23) >> 8); v27 = int_64DA8758() + ((xor_2 >> 15) & 0x1FE); v28 = (*(_WORD *)v27 & 0xFF | (*(_WORD *)v27 << 8) | ((*(_WORD *)v27 ^ ((unsigned int)*(_WORD *)v27 >> 8)) << 24)) ^ v26; v29 = *(_WORD *)(int_64DA8758() + ((xor_3 >> 7) & 0x1FE)); v30 = *(_WORD *)(int_64DA8758() + 2 * (unsigned __int8)xor_4); at = v28 ^ *(ina - 3) ^ (v29 | (((unsigned __int8)v29 ^ (v29 >> 8) | (v29 << 8)) << 16)) ^ (((((unsigned __int8)v30 << 8) | (v30 << 16) | (unsigned __int8)v30 ^ (v30 >> 8)) << 8) | (v30 >> 8)); v31 = int_64DA8758(); v32 = ((*(_WORD *)(v31 + ((v25 >> 23) & 0x1FE)) & 0xFF) << 8) | (*(_WORD *)(v31 + ((v25 >> 23) & 0x1FE)) << 16) | *(_WORD *)(v31 + ((v25 >> 23) & 0x1FE)) & 0xFF ^ ((unsigned int)*(_WORD *)(v31 + ((v25 >> 23) & 0x1FE)) >> 8); v33 = *(_WORD *)(int_64DA8758() + ((xor_3 >> 15) & 0x1FE)); v34 = ((unsigned __int8)v33 | (v33 << 8) | ((v33 ^ (v33 >> 8)) << 24)) ^ v32; v35 = *(_WORD *)(int_64DA8758() + (((unsigned int)xor_4 >> 7) & 0x1FE)); v36 = *(_WORD *)(int_64DA8758() + 2 * (unsigned __int8)v19); bt = v34 ^ *(ina - 2) ^ (v35 | (((unsigned __int8)v35 ^ (v35 >> 8) | (v35 << 8)) << 16)) ^ (((((unsigned __int8)v36 << 8) | (v36 << 16) | (unsigned __int8)v36 ^ (v36 >> 8)) << 8) | (v36 >> 8)); v37 = *(_WORD *)(int_64DA8758() + ((xor_3 >> 23) & 0x1FE)); v38 = ((unsigned __int8)v37 << 8) | (v37 << 16) | (unsigned __int8)v37 ^ (v37 >> 8); v39 = *(_WORD *)(int_64DA8758() + (((unsigned int)xor_4 >> 15) & 0x1FE)); v40 = ((unsigned __int8)v39 | (v39 << 8) | ((v39 ^ (v39 >> 8)) << 24)) ^ v38; v41 = *(_WORD *)(int_64DA8758() + ((v19 >> 7) & 0x1FE)); v42 = *(_WORD *)(int_64DA8758() + 2 * (unsigned __int8)out_8); ct = v40 ^ *(ina - 1) ^ (v41 | (((unsigned __int8)v41 ^ (v41 >> 8) | (v41 << 8)) << 16)) ^ (((((unsigned __int8)v42 << 8) | (v42 << 16) | (unsigned __int8)v42 ^ (v42 >> 8)) << 8) | (v42 >> 8)); v44 = *(_WORD *)(int_64DA8758() + (((unsigned int)xor_4 >> 23) & 0x1FE)); v45 = ((unsigned __int8)v44 << 8) | (v44 << 16) | (unsigned __int8)v44 ^ (v44 >> 8); v46 = int_64DA8758(); v47 = (*(_WORD *)(v46 + ((v19 >> 15) & 0x1FE)) & 0xFF | (*(_WORD *)(v46 + ((v19 >> 15) & 0x1FE)) << 8) | ((*(_WORD *)(v46 + ((v19 >> 15) & 0x1FE)) ^ ((unsigned int)*(_WORD *)(v46 + ((v19 >> 15) & 0x1FE)) >> 8)) << 24)) ^ v45; v48 = *(_WORD *)(int_64DA8758() + ((out_8 >> 7) & 0x1FE)); v49 = int_64DA8758(); v16 = ina + 4; v50 = v48 | (((unsigned __int8)v48 ^ (v48 >> 8) | (v48 << 8)) << 16); xor_3 = ct; v51 = *(_WORD *)(v49 + 2 * out_4); xor_4 = v47 ^ *ina ^ v50 ^ (((((unsigned __int8)v51 << 8) | (v51 << 16) | (unsigned __int8)v51 ^ (v51 >> 8)) << 8) | (v51 >> 8)); xor_2 = bt; v18 = out_12 + 1; xor_1 = at; } while ( out_12 + 1 < v79 - 1 ); v52 = ina[1]; v53 = *(_WORD *)(int_64DA8758() + (((unsigned int)at >> 23) & 0x1FE)); v54 = (*(_WORD *)(int_64DA8758() + (((unsigned int)bt >> 15) & 0x1FE)) << 16) & 0xFF0000 | (v53 << 24); v55 = v54 | (*(_WORD *)(int_64DA8758() + (((unsigned int)ct >> 7) & 0x1FE)) << 8) & 0xFF00; v56 = (v55 | *(_BYTE *)(int_64DA8758() + 2 * (unsigned __int8)xor_4)) ^ v52; *out = BYTE3(v56); out[1] = (unsigned int)v56 >> 16; out[2] = BYTE1(v56); out[3] = v56; v57 = ina[2]; v58 = *(_WORD *)(int_64DA8758() + (((unsigned int)bt >> 23) & 0x1FE)); v59 = (*(_WORD *)(int_64DA8758() + (((unsigned int)ct >> 15) & 0x1FE)) << 16) & 0xFF0000 | (v58 << 24); v60 = v59 | (*(_WORD *)(int_64DA8758() + (((unsigned int)xor_4 >> 7) & 0x1FE)) << 8) & 0xFF00; v61 = (v60 | *(_BYTE *)(int_64DA8758() + 2 * (unsigned __int8)at)) ^ v57; out[4] = BYTE3(v61); out[5] = (unsigned int)v61 >> 16; out[6] = BYTE1(v61); out[7] = v61; v62 = ina[3]; v63 = *(_WORD *)(int_64DA8758() + (((unsigned int)ct >> 23) & 0x1FE)); v64 = (*(_WORD *)(int_64DA8758() + (((unsigned int)xor_4 >> 15) & 0x1FE)) << 16) & 0xFF0000 | (v63 << 24); v65 = v64 | (*(_WORD *)(int_64DA8758() + (((unsigned int)at >> 7) & 0x1FE)) << 8) & 0xFF00; v66 = (v65 | *(_BYTE *)(int_64DA8758() + 2 * (unsigned __int8)bt)) ^ v62; out[8] = BYTE3(v66); out[9] = (unsigned int)v66 >> 16; out[10] = BYTE1(v66); out[11] = v66; v67 = *v16; v68 = *(_WORD *)(int_64DA8758() + (((unsigned int)xor_4 >> 23) & 0x1FE)); v69 = (*(_WORD *)(int_64DA8758() + (((unsigned int)at >> 15) & 0x1FE)) << 16) & 0xFF0000 | (v68 << 24); v70 = v69 | (*(_WORD *)(int_64DA8758() + (((unsigned int)bt >> 7) & 0x1FE)) << 8) & 0xFF00; v71 = (v70 | *(_BYTE *)(int_64DA8758() + 2 * (unsigned __int8)ct)) ^ v67; out[12] = BYTE3(v71); out[13] = (unsigned int)v71 >> 16; out[14] = BYTE1(v71); out[15] = v71; return 0; }
代码大致可以分为两个循环,第一个循环用来初始化一个int型数组,数组为定值。在第二个循环中这个数组会参与运算。随后将输入的16字节按大字端产生4个int值,暂且称之为a,b,c,d。
这4个值会首先与4个定值进行异或,然后进入第二个循环,共9轮。
整个算法如下(略去产生数组的第一层循环,可以认为是一个常量):
约定b1(a)为取a数学意义上的最高两位,如b1(0x12345678) = 0x12。b2,b3,b4依次类推。 约定":"为数字的连接,如0x12:0x34:0x56:0x78 = 0x12345678。 约定h(a)为取一个short型数字的高8位,如h(0x1234) = 0x12;l(a)为取一个short型数字的低8位,如l(0x1234) = 0x34。 其中每轮有4种算法,称之为m1,m2,m3,m4。其中: m1(t) = h(t) : l(t) : l(t) : (h(t)^l(t)) m2(t) = (h(t)^l(t)) : h(t) : l(t) : l(t) m3(t) = l(t) : (h(t)^l(t)) : h(t) : l(t) m4(t) = l(t) : l(t) : (h(t)^l(t)) : h(t) 算法存在一个定值数组,为256个short型,共512字节,记为M。 同时存在第一个循环初始化过的定值数组,为40个int型,记为X。 /* 大字端扩展 */ a = in[0] : in[1] : in[2] : in[3]; b = in[4] : in[5] : in[6] : in[7]; c = in[8] : in[9] : in[10] : in[11]; d = in[12] : in[13] : in[14] : in[15]; a = a ^ 0x6bcdc67a; b = b ^ 0x6b2b7c9d; c = c ^ 0x8da459b1; d = d ^ 0xab9d0680; i = 0; do { at = m1(M[b1(a)]) ^ m2(M[b2(b)]) ^ m3(M[b3(c)]) ^ m4(M[b4(d)]) ^ X[i]; bt = m1(M[b1(b)]) ^ m2(M[b2(c)]) ^ m3(M[b3(d)]) ^ m4(M[b4(a)]) ^ X[i+1]; ct = m1(M[b1(c)]) ^ m2(M[b2(d)]) ^ m3(M[b3(a)]) ^ m4(M[b4(b)]) ^ X[i+2]; dt = m1(M[b1(d)]) ^ m2(M[b2(a)]) ^ m3(M[b3(b)]) ^ m4(M[b4(c)]) ^ X[i+3]; a = at; b = bt; c = ct; d = dt; i++; } while (i < 9); /* 最后的处理 */ af = l(M[b1(a)]) : l(M[b2(b)]) : l(M[b3(c)]) : l(M[b4(d)]); af = af ^ X[36]; bf = l(M[b1(b)]) : l(M[b2(c)]) : l(M[b3(d)]) : l(M[b4(a)]); bf = bf ^ X[37]; cf = l(M[b1(c)]) : l(M[b2(d)]) : l(M[b3(a)]) : l(M[b4(b)]); cf = cf ^ X[38]; df = l(M[b1(d)]) : l(M[b2(a)]) : l(M[b3(b)]) : l(M[b4(d)]); df = df ^ X[39]; /* 保存到out,同样是大字端 */ out[0] = b1(af); out[1] = b2(af); out[2] = b3(af); out[3] = b4(af); out[4] = b1(bf); out[5] = b2(bf); out[6] = b3(bf); out[7] = b4(bf); out[8] = b1(cf); out[9] = b2(cf); out[10] = b3(cf); out[11] = b4(cf); out[12] = b1(df); out[13] = b2(df); out[14] = b3(df); out[15] = b4(df);
逆出算法之后,觉得眼一黑:这个算法(我觉得)是无法逆运算的!只能采用暴力破解的方式。
仔细看“最后的处理”部分的代码,从af,bf,cf,df逆运算得到最后一轮的a,b,c,d比较简单,因为af的每个字节只与上轮的a,b,c,d中的一个值有关,有
b1(af ^ X[36]) = l(M[b1(a)])。这样只需要一层循环,就可以获得最后一轮a中的某一位。
for i in range(len(M)): if l(M[i]) = b1(af ^ x[36]): #此时的i就是上轮a的第一个字节,b1(a)
依次类推,我们可以获取最后一轮的a、b、c、d。
对于9轮循环,问题就比较麻烦,因为a中一个字节与上轮循环中的a、b、c、d都有关系,虽然说有了正向的算法后可以通过4层循环来跑,但是4层循环太慢了。当时我测试了一下,在我机器上跑一个字节就要好几分钟,一轮跑4个字节,9轮,速度太慢了,感觉不应该这么粗暴的解。
仔细观察每轮循环中的算法,可以发现一个规律,这里以a为例说明。
at = m1(M[b1(a)]) ^ m2(M[b2(b)]) ^ m3(M[b3(c)]) ^ m4(M[b4(d)]) ^ X[i]; 首先跟定值X[i]异或,结果以ax代替,即 ax = m1(M[b1(a)]) ^ m2(M[b2(b)]) ^ m3(M[b3(c)]) ^ m4(M[b4(d)]); 然后展开其中的m,对于h(M[b1(a)]),用ha代替,有 ax = (ha:la:la:ha^la) ^ (hb^lb:hb:lb:lb) ^ (lc:hc^lc:hc:lc) ^ (lc:lc:hc^lc:hc) 对于ax的每一个字节,记为a1, a2, a3, a4。于是, a1 = b1(ax) = ha ^ (hb ^ lb) ^ lc ^ ld a2 = b2(ax) = la ^ hb ^ (hc ^ lc) ^ ld a3 = b3(ax) = la ^ lb ^ hc ^ (hd ^ ld) a4 = b4(ax) = (ha ^ la) ^ lb ^ lc ^ hd 对于异或运算,它满足交换律和结合律,即 x ^ y = y ^ x (x ^ y) ^ z = x ^ (y ^ z) 因此对于x1 ^ x2 ^ x3 ^ ... ^ xn,其顺序可以任意改变而不会应该其结果。 另外,异或还有这样的性质: x ^ x = 0 x ^ 0 = x 因此,有下面的等式, t1 = a1 ^ a2 ^ a4 = (ha ^ (hb ^ lb) ^ lc ^ ld) ^ (la ^ hb ^ (hc ^ lc) ^ ld) ^ ((ha ^ la) ^ lb ^ lc ^ hd) = (ha ^ la ^ ha ^ la) ^ (hb ^ lb ^ hb ^ lb) ^ (lc ^ hc ^ lc ^ lc) ^ (ld ^ ld ^ hd) = hc ^ lc ^ hd 同理,有 t2 = a1 ^ a3 ^ a4 = hb ^ lb ^ hc t3 = a2 ^ a3 ^ a4 = ha ^ la ^ hb t4 = a1 ^ a2 ^ a3 = ha ^ hd ^ ld 这样,就有了4个约束条件。 于是代码就可以这样写: for i in range(len(M)): for j in range(len(M)): if h(M[i]) ^ l(M[i]) ^ h(M[j]) == t3: for k in range(len(M)): if h(M[j]) ^ l(M[j]) ^ h(M[k]) == t2: for q in range(len(M)): if h(M[k]) ^ l(M[k]) ^ h(M[q]) == t1: if h(M[i]) ^ h(M[q]) ^ l(M[q]) == t4: # 上轮中a、b、c、d中的某一位 at1 = i bt2 = j ct3 = k dt4 = q 这样在第二、三层循环中就加入了约束条件,大大减少循环次数。在我机器上,9轮循环,一轮4个字节,4.5秒左右就可以跑完。 依次类推到本轮的b、c、d,就可以获得其他字节,组合就得到了上轮的a、b、c、d。
循环问题解决后,其他的算法就是加加减减和异或,不再赘述。
最后放上我的解密代码,求得结果为kboloy0
import time matrix = [ 0xC663 , 0xF87C , 0xEE77 , 0xF67B , 0xFFF2 , 0xD66B , 0xDE6F , 0x91C5 , 0x6030 , 0x201 , 0xCE67 , 0x562B , 0xE7FE , 0xB5D7 , 0x4DAB , 0xEC76 , 0x8FCA , 0x1F82 , 0x89C9 , 0xFA7D , 0xEFFA , 0xB259 , 0x8E47 , 0xFBF0 , 0x41AD , 0xB3D4 , 0x5FA2 , 0x45AF , 0x239C , 0x53A4 , 0xE472 , 0x9BC0 , 0x75B7 , 0xE1FD , 0x3D93 , 0x4C26 , 0x6C36 , 0x7E3F , 0xF5F7 , 0x83CC , 0x6834 , 0x51A5 , 0xD1E5 , 0xF9F1 , 0xE271 , 0xABD8 , 0x6231 , 0x2A15 , 0x804 , 0x95C7 , 0x4623 , 0x9DC3 , 0x3018 , 0x3796 , 0xA05 , 0x2F9A , 0xE07 , 0x2412 , 0x1B80 , 0xDFE2 , 0xCDEB , 0x4E27 , 0x7FB2 , 0xEA75 , 0x1209 , 0x1D83 , 0x582C , 0x341A , 0x361B , 0xDC6E , 0xB45A , 0x5BA0 , 0xA452 , 0x763B , 0xB7D6 , 0x7DB3 , 0x5229 , 0xDDE3 , 0x5E2F , 0x1384 , 0xA653 , 0xB9D1 , 0 , 0xC1ED , 0x4020 , 0xE3FC , 0x79B1 , 0xB65B , 0xD46A , 0x8DCB , 0x67BE , 0x7239 , 0x944A , 0x984C , 0xB058 , 0x85CF , 0xBBD0 , 0xC5EF , 0x4FAA , 0xEDFB , 0x8643 , 0x9A4D , 0x6633 , 0x1185 , 0x8A45 , 0xE9F9 , 0x402 , 0xFE7F , 0xA050 , 0x783C , 0x259F , 0x4BA8 , 0xA251 , 0x5DA3 , 0x8040 , 0x58F , 0x3F92 , 0x219D , 0x7038 , 0xF1F5 , 0x63BC , 0x77B6 , 0xAFDA , 0x4221 , 0x2010 , 0xE5FF , 0xFDF3 , 0xBFD2 , 0x81CD , 0x180C , 0x2613 , 0xC3EC , 0xBE5F , 0x3597 , 0x8844 , 0x2E17 , 0x93C4 , 0x55A7 , 0xFC7E , 0x7A3D , 0xC864 , 0xBA5D , 0x3219 , 0xE673 , 0xC060 , 0x1981 , 0x9E4F , 0xA3DC , 0x4422 , 0x542A , 0x3B90 , 0xB88 , 0x8C46 , 0xC7EE , 0x6BB8 , 0x2814 , 0xA7DE , 0xBC5E , 0x160B , 0xADDB , 0xDBE0 , 0x6432 , 0x743A , 0x140A , 0x9249 , 0xC06 , 0x4824 , 0xB85C , 0x9FC2 , 0xBDD3 , 0x43AC , 0xC462 , 0x3991 , 0x3195 , 0xD3E4 , 0xF279 , 0xD5E7 , 0x8BC8 , 0x6E37 , 0xDA6D , 0x18D , 0xB1D5 , 0x9C4E , 0x49A9 , 0xD86C , 0xAC56 , 0xF3F4 , 0xCFEA , 0xCA65 , 0xF47A , 0x47AE , 0x1008 , 0x6FBA , 0xF078 , 0x4A25 , 0x5C2E , 0x381C , 0x57A6 , 0x73B4 , 0x97C6 , 0xCBE8 , 0xA1DD , 0xE874 , 0x3E1F , 0x964B , 0x61BD , 0xD8B , 0xF8A , 0xE070 , 0x7C3E , 0x71B5 , 0xCC66 , 0x9048 , 0x603 , 0xF7F6 , 0x1C0E , 0xC261 , 0x6A35 , 0xAE57 , 0x69B9 , 0x1786 , 0x99C1 , 0x3A1D , 0x279E , 0xD9E1 , 0xEBF8 , 0x2B98 , 0x2211 , 0xD269 , 0xA9D9 , 0x78E , 0x3394 , 0x2D9B , 0x3C1E , 0x1587 , 0xC9E9 , 0x87CE , 0xAA55 , 0x5028 , 0xA5DF , 0x38C , 0x59A1 , 0x989 , 0x1A0D , 0x65BF , 0xD7E6 , 0x8442 , 0xD068 , 0x8241 , 0x2999 , 0x5A2D , 0x1E0F , 0x7BB0 , 0xA854 , 0x6DBB , 0x2C16 , ] xor_m = [ 0x34a20b18, 0x5f897785, 0xd22d2e34, 0x79b028b4 , 0xd19686ae, 0x8e1ff12b, 0x5c32df1f, 0x2582f7ab , 0xc6fee491, 0x48e115ba, 0x14d3caa5, 0x31513d0e , 0x1fd94f56, 0x57385aec, 0x43eb9049, 0x72baad47 , 0xfb4cef16, 0xac74b5fa, 0xef9f25b3, 0x9d2588f4 , 0xe4885048, 0x48fce5b2, 0xa763c001, 0x3a4648f5 , 0xfedab6c8, 0xb626537a, 0x1145937b, 0x2b03db8e , 0x0563af39, 0xb345fc43, 0xa2006f38, 0x8903b4b6 , 0x65eee19e, 0xd6ab1ddd, 0x74ab72e5, 0xfda8c653 , 0x915a0cca, 0x47f11117, 0x335a63f2, 0xcef2a5a1 , ] def b1(n): return (n >> 24) & 0xff; def b2(n): return (n >> 16) & 0xff; def b3(n): return (n >> 8) & 0xff; def b4(n): return n & 0xff; def m1(t): th = (t >> 8) & 0xff tl = t & 0xff return th << 24 | tl << 16 | tl << 8 | (th ^ tl) def m2(t): th = (t >> 8) & 0xff tl = t & 0xff return (th ^ tl) << 24 | th << 16 | tl << 8 | tl def m3(t): th = (t >> 8) & 0xff tl = t & 0xff return tl << 24 | (th ^ tl) << 16 | th << 8 | tl def m4(t): th = (t >> 8) & 0xff tl = t & 0xff return tl << 24 | tl << 16 | (tl ^ th) << 8 | th def geth(t): return (t >> 8) & 0xff def getl(t): return t & 0xff def find_l(l): for i in range(len(matrix)): if matrix[i] & 0xff == l: print hex(i) return i def find_h(h): for i in range(len(matrix)): if (matrix[i] & 0xff00) >> 8 == h: print hex(i) return i def merge_int(a1,a2,a3,a4): return a1 << 24 | a2 << 16 | a3 << 8 | a4 # 加密后的正确key af = 0x2f77da5c bf = 0x393ec6a3 cf = 0xedf3f0b6 df = 0x86995a51 ''' # 输入1234567890123456的计算结果 af = 0x128b387f bf = 0x489b84af cf = 0x4940ab58 df = 0x858b3ae5 ''' af = b4(af) << 24 | b3(af) << 16 | b2(af) << 8 | b1(af) bf = b4(bf) << 24 | b3(bf) << 16 | b2(bf) << 8 | b1(bf) cf = b4(cf) << 24 | b3(cf) << 16 | b2(cf) << 8 | b1(cf) df = b4(df) << 24 | b3(df) << 16 | b2(df) << 8 | b1(df) print hex(af),hex(bf),hex(cf),hex(df) af = af ^ xor_m[36] bf = bf ^ xor_m[37] cf = cf ^ xor_m[38] df = df ^ xor_m[39] at1 = find_l(b1(af)) bt1 = find_l(b1(bf)) ct1 = find_l(b1(cf)) dt1 = find_l(b1(df)) bt2 = find_l(b2(af)) ct2 = find_l(b2(bf)) dt2 = find_l(b2(cf)) at2 = find_l(b2(df)) ct3 = find_l(b3(af)) dt3 = find_l(b3(bf)) at3 = find_l(b3(cf)) bt3 = find_l(b3(df)) dt4 = find_l(b4(af)) at4 = find_l(b4(bf)) bt4 = find_l(b4(cf)) ct4 = find_l(b4(df)) i = 8 start = time.time() while i >= -1: print "=====================================" am = merge_int(at1, at2, at3, at4) bm = merge_int(bt1, bt2, bt3, bt4) cm = merge_int(ct1, ct2, ct3, ct4) dm = merge_int(dt1, dt2, dt3, dt4) print hex(am),hex(bm),hex(cm),hex(dm) if (i == -1): print "done." break am = am ^ xor_m[i*4] bm = bm ^ xor_m[i*4 +1] cm = cm ^ xor_m[i*4 + 2] dm = dm ^ xor_m[i*4 + 3] print hex(am),hex(bm),hex(cm),hex(dm) # hd ^ hc ^ lc t1 = b1(am) ^ b2(am) ^ b4(am) # hb ^ lb ^ hc t2 = b1(am) ^ b3(am) ^ b4(am) # ha ^ la ^ hb t3 = b2(am) ^ b3(am) ^ b4(am) # ha ^ hd ^ ld t4 = b1(am) ^ b2(am) ^ b3(am) for j in range(256): for k in range(256): if geth(matrix[j]) ^ getl(matrix[j]) ^ geth(matrix[k]) == t3: for l in range(256): if geth(matrix[k]) ^ getl(matrix[k]) ^ geth(matrix[l]) == t2: for m in range(256): if geth(matrix[m]) ^ geth(matrix[l]) ^ getl(matrix[l]) == t1: if geth(matrix[j]) ^ geth(matrix[m]) ^ getl(matrix[m]) == t4: at1 = j bt2 = k ct3 = l dt4 = m break # a d d t1 = b1(bm) ^ b2(bm) ^ b4(bm) # c c d t2 = b1(bm) ^ b3(bm) ^ b4(bm) # b b c t3 = b2(bm) ^ b3(bm) ^ b4(bm) # b a a t4 = b1(bm) ^ b2(bm) ^ b3(bm) for j in range(256): for k in range(256): if geth(matrix[j]) ^ getl(matrix[j]) ^ geth(matrix[k]) == t3: for l in range(256): if geth(matrix[k]) ^ getl(matrix[k]) ^ geth(matrix[l]) == t2: for m in range(256): if geth(matrix[m]) ^ geth(matrix[l]) ^ getl(matrix[l]) == t1: if geth(matrix[j]) ^ geth(matrix[m]) ^ getl(matrix[m]) == t4: bt1 = j ct2 = k dt3 = l at4 = m break # b a a t1 = b1(cm) ^ b2(cm) ^ b4(cm) # d d a t2 = b1(cm) ^ b3(cm) ^ b4(cm) # c c d t3 = b2(cm) ^ b3(cm) ^ b4(cm) # c b b t4 = b1(cm) ^ b2(cm) ^ b3(cm) for j in range(256): for k in range(256): if geth(matrix[j]) ^ getl(matrix[j]) ^ geth(matrix[k]) == t3: for l in range(256): if geth(matrix[k]) ^ getl(matrix[k]) ^ geth(matrix[l]) == t2: for m in range(256): if geth(matrix[m]) ^ geth(matrix[l]) ^ getl(matrix[l]) == t1: if geth(matrix[j]) ^ geth(matrix[m]) ^ getl(matrix[m]) == t4: ct1 = j dt2 = k at3 = l bt4 = m break # c b b t1 = b1(dm) ^ b2(dm) ^ b4(dm) # a a b t2 = b1(dm) ^ b3(dm) ^ b4(dm) # d d a t3 = b2(dm) ^ b3(dm) ^ b4(dm) # d c c t4 = b1(dm) ^ b2(dm) ^ b3(dm) for j in range(256): for k in range(256): if geth(matrix[j]) ^ getl(matrix[j]) ^ geth(matrix[k]) == t3: for l in range(256): if geth(matrix[k]) ^ getl(matrix[k]) ^ geth(matrix[l]) == t2: for m in range(256): if geth(matrix[m]) ^ geth(matrix[l]) ^ getl(matrix[l]) == t1: if geth(matrix[j]) ^ geth(matrix[m]) ^ getl(matrix[m]) == t4: dt1 = j at2 = k bt3 = l ct4 = m break i = i - 1 print "time: " + str(time.time() - start) am = am ^ 0x6bcdc67a bm = bm ^ 0x6b2b7c9d cm = cm ^ 0x8da459b1 dm = dm ^ 0xab9d0680 print hex(am),hex(bm),hex(cm),hex(dm) sub_a1 = b1(am) sub_a2 = b2(am) sub_a3 = b3(am) sub_a4 = b4(am) sub_b1 = b1(bm) sub_b2 = b2(bm) sub_b3 = b3(bm) sub_b4 = b4(bm) sub_c1 = b1(cm) sub_c2 = b2(cm) sub_c3 = b3(cm) sub_c4 = b4(cm) sub_d1 = b1(dm) sub_d2 = b2(dm) sub_d3 = b3(dm) sub_d4 = b4(dm) subee = [0x1f,0xbc,0xda,0xff,0xe6,0x4c,0xbc,0x44,0xf5,0xb8,0x13,0xc8,0xec,0xa8,0xcd,0xbd] sub_list = [sub_a1, sub_a2, sub_a3, sub_a4, sub_b1, sub_b2, sub_b3, sub_b4, sub_c1, sub_c2, sub_c3, sub_c4, sub_d1, sub_d2, sub_d3, sub_d4] sub_re = [] for i in range(len(sub_list)): print hex(sub_list[i]), sub_re.append((sub_list[i] + 0x100 - subee[i]) & 0xff) print "" passwd = [] for i in range(len(sub_re)): passwd.append(chr(sub_re[i] - i)) print "passwd: " + str(passwd)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
他的文章
看原图
赞赏
雪币:
留言: