2014 NAGA&PIOWIND APP应用攻防竞赛 Crackme04

Java层比较简单

查看so,发现加密,依旧dump,IDA调试时未发现有反调试,不过有那么一瞬间看到了inotify,没具体看

auto fp, dex_addr, end_addr;  
fp = fopen("E:\\libcrackme.so", "wb");  
for(dex_addr = 0xA357D000; dex_addr < 0xA35DE000; dex_addr++)
    fputc(Byte(dex_addr), fp);

修复dump后的so文件头,使用IDA打开,关键的依旧是这个函数

但是我们跟入后,发现壳好像没有脱干净(后来发现其实不是没脱干净)

再次动态调试脱壳,这次我们找到校验函数,单步跟下去看看具体是什么情况

我们需要先找到校验函数的地址,使用给dvmUseJNIBridge函数下断点的方法

我们使用调试模式启动应用,IDA挂上去,找到libdvm.sodvmUseJNIBridge函数,下断点

然后把IDA跑起来,在应用界面输入账号密码,点击登录,就可以发现断在这里了,我们注意观察参数,第二个参数就是我们的crackme函数

跟过去,可以看到确实是校验函数

找到我们看到是跳转地址的地方

双击过去

再次双击过去,发现是关键加解密点了,此时我们记录一下这个地址

再次dump这个so文件

动静结合,接下来看能力了

入口的数据初始化,然后调用_Unwind_GetCFAB,这和前几题是类似的

跟入,开始做了一些参数的存储操作,然后存储了_Unwind_GetCFAB函数的指针到栈中

LOAD:0002DB28 STMFD           SP!, {R4,R11,LR}
LOAD:0002DB2C ADD             R11, SP, #8
LOAD:0002DB30 SUB             SP, SP, #0x24
LOAD:0002DB34 LDR             R4, =(off_46CC8 - 0x2DB40)
LOAD:0002DB38 ADD             R4, PC, R4 ; off_46CC8 ; 神奇的地址,暂时不知道干什么的
LOAD:0002DB3C STR             R0, [R11,#var_20] ; R0 = var_20 = szUserName
LOAD:0002DB40 STR             R1, [R11,#var_24] ; R1 = var_24 = szRegCode
LOAD:0002DB44 MOV             R3, #0  ; R3 = 0
LOAD:0002DB48 STR             R3, [R11,#var_18] ; R3 = var_18 = 0
LOAD:0002DB4C LDR             R3, =dword_2CC ; R3 = 0x2CC
LOAD:0002DB50 LDR             R3, [R4,R3]
LOAD:0002DB54 LDR             R3, [R3]
LOAD:0002DB58 STR             R3, [R11,#var_14] ; var_14为_Unwind_GetCFAB函数指针

一开始并没有看出来,所以使用了动态调试来确定

接下来的操作是为了调用tdog_decrypt而做参数的计算

LOAD:0002DB5C LDR             R3, =dword_2CC ; R3 = 0x2CC
LOAD:0002DB60 LDR             R3, [R4,R3]
LOAD:0002DB64 LDR             R3, [R3] ; R3 = _Unwind_GetCFAB
LOAD:0002DB68 ADD             R3, R3, #0x14 ; R3 = _Unwind_GetCFAB + 0x14
LOAD:0002DB6C MOV             R1, R3  ; R1 = R3 = R3 = _Unwind_GetCFAB + 0x14
LOAD:0002DB70 LDR             R3, =dword_2B0 ; R3 = 0x2B0
LOAD:0002DB74 LDR             R3, [R4,R3]
LOAD:0002DB78 LDR             R3, [R3] ; 动调 : R3 = 0x104
LOAD:0002DB7C SUB             R2, R3, #0x14 ; R2 = 0xF0
LOAD:0002DB80 LDR             R3, =dword_2CC ; R3 = 0x2CC
LOAD:0002DB84 LDR             R3, [R4,R3]
LOAD:0002DB88 LDR             R3, [R3] ; R3 = _Unwind_GetCFAB
LOAD:0002DB8C ADD             R3, R3, #0x14 ; R3 = _Unwind_GetCFAB + 0x14
LOAD:0002DB90 LDR             R0, =dword_1D4 ; R0 = 0x1D4
LOAD:0002DB94 LDR             R0, [R4,R0]
LOAD:0002DB98 LDR             R0, [R0]
LOAD:0002DB9C STR             R0, [SP,#0x2C+var_2C] ; var_2C = abs_export_function_key
LOAD:0002DBA0 MOV             R0, R1  ; R0 = _Unwind_GetCFAB + 0x14
LOAD:0002DBA4 MOV             R1, R2  ; R1 = R2 = 0xF0
LOAD:0002DBA8 MOV             R2, R3  ; R3 = _Unwind_GetCFAB + 0x14
LOAD:0002DBAC LDR             R3, =dword_314 ; R3 = 0x314
LOAD:0002DBB0 LDR             R3, [R4,R3] ; 指向内存
LOAD:0002DBB4 BL              tdog_decrypt

看名字就可以猜到这个函数很重要

tdog_decrypt(_Unwind_GetCFAB + 0x14, 0xF0, _Unwind_GetCFAB + 0x14, 某内存变量指针)

双击跟入,发现其调用了一个XorArray()函数

LOAD:000387F8 STMFD           SP!, {R11,LR}
LOAD:000387FC ADD             R11, SP, #4
LOAD:00038800 SUB             SP, SP, #0x10
LOAD:00038804 ; 5:   v5 = a2;
LOAD:00038804 STR             R0, [R11,#var_8] ; R0 = var_8 = _Unwind_GetCFAB + 0x14
LOAD:00038808 STR             R1, [R11,#var_C] ; R1 = var_C = 0xF0
LOAD:0003880C ; 6:   v6 = a4;
LOAD:0003880C STR             R2, [R11,#var_10] ; R2 = var_10 = _Unwind_GetCFAB + 0x14
LOAD:00038810 STR             R3, [R11,#var_14] ; R3 = var_14 = 第四个参数,为一个指针
LOAD:00038814 ; 7:   XorArray(a5, a1, a1, a2);
LOAD:00038814 LDR             R2, [R11,#var_8] ; R2 = _Unwind_GetCFAB + 0x14
LOAD:00038818 LDR             R3, [R11,#var_8] ; R3 = _Unwind_GetCFAB + 0x14
LOAD:0003881C LDR             R0, [R11,#arg_0] ; 动调 : R0 = 0x5F7C8B38
LOAD:00038820 MOV             R1, R2  ; R1 = R2 = _Unwind_GetCFAB + 0x14
LOAD:00038824 MOV             R2, R3  ; R2 = R3 = _Unwind_GetCFAB + 0x14
LOAD:00038828 LDR             R3, [R11,#var_C] ; R3 = 0xF0
LOAD:0003882C BL              _Z8XorArrayjPhS_j ; XorArray(uint,uchar *,uchar *,uint)

XorArray函数里有一个PolyXorKey函数,用于生成秘钥,这个函数在后续的娜迦壳里面是一个比较重要的特征,后续的类抽取技术里就有用到这个函数进行秘钥的计算

我一直觉得这里加了junk code,前面有些指令反复做同样的操作时我就感觉出来了,但是加的junk code并不是很多,比如这该函数的第一个函数块后面的几句

LOAD:00038D7C STMFD           SP!, {R11,LR}
LOAD:00038D80 ADD             R11, SP, #4
LOAD:00038D84 SUB             SP, SP, #0x20
LOAD:00038D88 ; 10:   v6 = a2;
LOAD:00038D88 STR             R0, [R11,#var_18] ; var_18 = 0X5F7C8B38 //神秘变量
LOAD:00038D8C STR             R1, [R11,#var_1C] ; var_1C = _Unwind_GetCFAB + 0x14
LOAD:00038D90 ; 11:   v5 = a3;
LOAD:00038D90 STR             R2, [R11,#var_20] ; var_20 = _Unwind_GetCFAB + 0x14
LOAD:00038D94 ; 12:   v4 = a4;
LOAD:00038D94 STR             R3, [R11,#var_24] ; var_24 = 0xF0
LOAD:00038D98 ; 13:   v7 = result;
LOAD:00038D98 LDR             R3, [R11,#var_18] ; R3 = 0X5F7C8B38 //神秘,神秘
LOAD:00038D9C STR             R3, [R11,#var_14] ; var_14 = 0X5F7C8B38 //又是神秘
LOAD:00038DA0 ; 14:   v8 = &v7;
LOAD:00038DA0 SUB             R3, R11, #-var_14
LOAD:00038DA4 STR             R3, [R11,#var_10] ; R3存储的是神秘变量0X5F7C8B38的指针
LOAD:00038DA8 ; 15:   v10 = 0;
LOAD:00038DA8 MOV             R3, #0  ; R3 = 0
LOAD:00038DAC STR             R3, [R11,#var_C] ; var_C = 0
LOAD:00038DB0 MOV             R3, #0  ; R3 = 0
LOAD:00038DB4 STR             R3, [R11,#var_8] ; var_8 = 0
LOAD:00038DB8 ; 16:   for ( i = 0; v4 > i; ++i )
LOAD:00038DB8 MOV             R3, #0  ; R3 = 0
LOAD:00038DBC STR             R3, [R11,#var_C] ; R3 = 0
LOAD:00038DC0 B               loc_38E40

开始进入循环

LOAD:00038E40 loc_38E40               ;
LOAD:00038E40 LDR             R2, [R11,#var_24] ; R2 = 0xF0 = 240
LOAD:00038E44 LDR             R3, [R11,#var_C] ; R3 = i
LOAD:00038E48 CMP             R2, R3
LOAD:00038E4C MOVLE           R3, #0
LOAD:00038E50 MOVGT           R3, #1  ; 循环中使用这一句 : i < 240 --- > R3 = 1
LOAD:00038E54 AND             R3, R3, #0xFF ; 用于判断是否到达退出条件
LOAD:00038E58 CMP             R3, #0
LOAD:00038E5C BNE             loc_38DC4

两个基址获取字节数据,进行异或操作,异或后的数据,存在_Unwind_GetCFAB + 0x14 + i指向的字节

LOAD:00038DC4 ; 19:     v5[i] = v6[i] ^ *((_BYTE *)v8 + v10);
LOAD:00038DC4 loc_38DC4               ;
LOAD:00038DC4 LDR             R3, [R11,#var_C] ; R3 = i
LOAD:00038DC8 LDR             R2, [R11,#var_20] ; R2 = _Unwind_GetCFAB + 0x14
LOAD:00038DCC ADD             R3, R2, R3 ; R3 = _Unwind_GetCFAB + 0x14 + i
LOAD:00038DD0 LDR             R2, [R11,#var_C] ; R2 = i
LOAD:00038DD4 LDR             R1, [R11,#var_1C] ; R1 = _Unwind_GetCFAB + 0x14
LOAD:00038DD8 ADD             R2, R1, R2 ; R2 = _Unwind_GetCFAB + 0x14 + i
LOAD:00038DDC ; 18:     result = (int)v8;
LOAD:00038DDC LDRB            R1, [R2] ; 取第一个字节,动调 : R1 = 0xBB
LOAD:00038DE0 LDR             R2, [R11,#var_8] ; R2 = j
LOAD:00038DE4 LDR             R0, [R11,#var_10] ; R0为神秘变量指针
LOAD:00038DE8 ADD             R2, R0, R2 ; R2 = 神秘变量指针 + j
LOAD:00038DEC LDRB            R2, [R2] ; 取第一个字节,动调 : 0x38
LOAD:00038DF0 EOR             R2, R1, R2 ; 两个取出来的字节异或
LOAD:00038DF4 AND             R2, R2, #0xFF
LOAD:00038DF8 STRB            R2, [R3]
LOAD:00038DFC ; 20:     if ( v10 == 3 )
LOAD:00038DFC LDR             R3, [R11,#var_8]
LOAD:00038E00 CMP             R3, #3
LOAD:00038E04 BNE             loc_38E28

这里是在计算一个四字节的数据

我们在内存中跟随,可以看到这四个字节的数据已经修改成了83 93 00 23,不清楚的同学可以在异或的地方下个断点循环调试看看

接着是调用PolyXorKey,参数是神秘变量自身

LOAD:00038E08 ; 22:       result = PolyXorKey(v7);
LOAD:00038E08 LDR             R3, [R11,#var_14] ; 取出神秘变量
LOAD:00038E0C MOV             R0, R3  ; 神秘变量作为参数R0
LOAD:00038E10 BL              _Z10PolyXorKeyj ; PolyXorKey(

先使用异或操作对神秘变量进行修改

LOAD:00038BC8 STR             R11, [SP,#-4+var_s0]!
LOAD:00038BCC ADD             R11, SP, #0
LOAD:00038BD0 SUB             SP, SP, #0x24
LOAD:00038BD4 ; 11:   v4 = 0;
LOAD:00038BD4 STR             R0, [R11,#var_20] ; var_20 = 神秘变量
LOAD:00038BD8 MOV             R3, #0  ; R3 = 0
LOAD:00038BDC STR             R3, [R11,#var_18] ; var_18 = 0
LOAD:00038BE0 MOV             R3, #0  ; R3 = 0
LOAD:00038BE4 STR             R3, [R11,#var_14] ; var_14 = 0
LOAD:00038BE8 ; 12:   v5 = 0;
LOAD:00038BE8 MOV             R3, #0  ; R3 = 0
LOAD:00038BEC STR             R3, [R11,#var_10] ; var_10 = 0
LOAD:00038BF0 ; 13:   v6 = (char *)&v2;
LOAD:00038BF0 SUB             R3, R11, #-var_20
LOAD:00038BF4 STR             R3, [R11,#var_C] ; var_C为神秘变量指针
LOAD:00038BF8 ; 14:   v7 = 0;
LOAD:00038BF8 MOV             R3, #0  ; R3 = 0
LOAD:00038BFC STRB            R3, [R11,#var_7] ; var_7指向的byte为0
LOAD:00038C00 ; 15:   v8 = 0;
LOAD:00038C00 MOV             R3, #0
LOAD:00038C04 STRB            R3, [R11,#var_6] ; var_6指向的byte为0
LOAD:00038C08 ; 16:   v9 = 0;
LOAD:00038C08 MOV             R3, #0
LOAD:00038C0C STRB            R3, [R11,#var_5] ; var_5指向的byte为0
LOAD:00038C10 ; 17:   v2 = a1 ^ 0xDF138530;
LOAD:00038C10 LDR             R3, [R11,#var_20] ; R3 = 神秘变量
LOAD:00038C14 MOV             R2, R3  ; R2 = R3 = 神秘变量
LOAD:00038C18 LDR             R3, =0xDF138530 ; R3 = 0xDF138530
LOAD:00038C1C EOR             R3, R2, R3 ; 神秘变量异或 ---> R3 = 0x5F7C8B38 ^ 0xDF138530
LOAD:00038C20 STR             R3, [R11,#var_20] ; 修改神秘变量为0x806F0E08
LOAD:00038C24 ; 18:   v3 = 0;
LOAD:00038C24 MOV             R3, #0  ; R3 = 0
LOAD:00038C28 STR             R3, [R11,#var_18] ; var_18 = 0
LOAD:00038C2C B               loc_38D48

进入大循环,整个大循环就是循环计算神秘变量的四个字节,但是内部又有很多的循环计算

LOAD:00038D48 ; 19:   while ( v3 <= 3 )
LOAD:00038D48 loc_38D48               ; 开始循环计算
LOAD:00038D48 LDR             R3, [R11,#var_18] ; R3 = i
LOAD:00038D4C CMP             R3, #3  ; 条件判断 i < 4
LOAD:00038D50 MOVGT           R3, #0
LOAD:00038D54 MOVLE           R3, #1
LOAD:00038D58 AND             R3, R3, #0xFF
LOAD:00038D5C CMP             R3, #0
LOAD:00038D60 BNE             loc_38C30

取字节,这里的var_C会在后面自加一

LOAD:00038C30 ; 21:     v7 = *v6;
LOAD:00038C30 loc_38C30               ;
LOAD:00038C30 LDR             R3, [R11,#var_C] ; R3为神秘变量指针
LOAD:00038C34 LDRB            R3, [R3] ; 获取计算后的神秘变量的字节
LOAD:00038C38 STRB            R3, [R11,#var_7] ; var_7 = 08
LOAD:00038C3C ; 22:     v4 = 128;
LOAD:00038C3C MOV             R3, #0x80 ; '€' ; R3 = 0x80
LOAD:00038C40 STR             R3, [R11,#var_14] ; var_14 = 0x80
LOAD:00038C44 ; 23:     v5 = 7;
LOAD:00038C44 MOV             R3, #7  ; R3 = 0x7
LOAD:00038C48 STR             R3, [R11,#var_10] ; var_10 = 0x7
LOAD:00038C4C B               loc_38CE0

内部的循环

LOAD:00038CE0 ; 24:     while ( v4 > 1 )
LOAD:00038CE0
LOAD:00038CE0 loc_38CE0               ;
LOAD:00038CE0 LDR             R3, [R11,#var_14] ; R3 = 0x80
LOAD:00038CE4 CMP             R3, #1
LOAD:00038CE8 MOVLE           R3, #0
LOAD:00038CEC MOVGT           R3, #1
LOAD:00038CF0 AND             R3, R3, #0xFF
LOAD:00038CF4 CMP             R3, #0
LOAD:00038CF8 BNE             loc_38C50

接下来的循环计算可以还原出C代码,但是具体是什么数学算法之类的就不是很清楚了,可能只是个计算,这个函数最终的功能目测应该是计算一个四字节的数据作为返回值

LOAD:00038C50 ; 27:       v8 = ((signed int)(unsigned __int8)(v7 & v4) >> v5) ^ v9;
LOAD:00038C50 loc_38C50               ; 进入循环,重命名神秘变量为sec
LOAD:00038C50 LDRB            R2, [R11,#var_7] ; R2 = 0x08
LOAD:00038C54 LDR             R3, [R11,#var_14] ; R3 = 0x80
LOAD:00038C58 AND             R2, R2, R3 ; R2 = sec[i] & 0x80
LOAD:00038C5C LDR             R3, [R11,#var_10] ; R3 = 0x07
LOAD:00038C60 MOV             R3, R2,ASR R3 ; R3 = (sec[i] & 0x80) / 0x07
LOAD:00038C64 ; 26:       v9 = (v7 & v4 / 2) >> (v5 - 1);
LOAD:00038C64 STRB            R3, [R11,#var_6] ; var_6 = (sec[i] & 0x80) / 0x07
LOAD:00038C68 LDRB            R2, [R11,#var_7] ; R2 = sec[i]
LOAD:00038C6C LDR             R3, [R11,#var_14] ; R3 = 0x80
LOAD:00038C70 MOV             R1, R3,LSR#31 ; R1 = 0x00000080 >> 31 = 0
LOAD:00038C74 ADD             R3, R1, R3 ; R3 = 0x80
LOAD:00038C78 MOV             R3, R3,ASR#1 ; R3 = 0x80 / 2
LOAD:00038C7C AND             R2, R2, R3 ; R2 = sec[i] & (0x80 / 2)
LOAD:00038C80 LDR             R3, [R11,#var_10] ; R3 = 0x07
LOAD:00038C84 SUB             R3, R3, #1 ; R3 = 0x07 - 1
LOAD:00038C88 MOV             R3, R2,ASR R3 ; R3 = (sec[i] & (0x80 / 2)) / (0x07 - 1)
LOAD:00038C8C STRB            R3, [R11,#var_5] ; var_5存储计算后的结果
LOAD:00038C90 LDRB            R2, [R11,#var_6] ; R2 = (sec[i] & 0x80) / 0x07
LOAD:00038C94 LDRB            R3, [R11,#var_5] ; R3 = (sec[i] & (0x80 / 2)) / (0x07 - 1)
LOAD:00038C98 EOR             R3, R2, R3 ; 上面两个进行异或,存储到R3
LOAD:00038C9C STRB            R3, [R11,#var_6] ; 异或结果存储到var_6
LOAD:00038CA0 ; 28:       v8 <<= v5;
LOAD:00038CA0 LDRB            R2, [R11,#var_6] ; R2 = 异或结果
LOAD:00038CA4 LDR             R3, [R11,#var_10] ; R3 = 0x07
LOAD:00038CA8 MOV             R3, R2,LSL R3 ; R3 = 异或结果 << 0x07
LOAD:00038CAC STRB            R3, [R11,#var_6] ; var_6 = 异或结果 << 0x07
LOAD:00038CB0 ; 29:       v7 |= v8;
LOAD:00038CB0 LDRB            R2, [R11,#var_7] ; R2 = sec[i]
LOAD:00038CB4 LDRB            R3, [R11,#var_6] ; R3 = 异或结果 << 0x07
LOAD:00038CB8 ORR             R3, R2, R3 ; R3 = sec[i] | (异或结果 << 0x07)
LOAD:00038CBC STRB            R3, [R11,#var_7] ; var_7 = sec[i] | (异或结果 << 0x07)
LOAD:00038CC0 ; 30:       v4 /= 2;
LOAD:00038CC0 LDR             R3, [R11,#var_14] ; R3 = 0x80
LOAD:00038CC4 MOV             R2, R3,LSR#31 ; R2 = 0
LOAD:00038CC8 ADD             R3, R2, R3 ; R3 = 0x80
LOAD:00038CCC MOV             R3, R3,ASR#1 ; R3 = 0x80 / 2
LOAD:00038CD0 STR             R3, [R11,#var_14] ; var_14 = 0x80 / 2 //这里应该是这个变量循环除2
LOAD:00038CD4 ; 31:       --v5;
LOAD:00038CD4 LDR             R3, [R11,#var_10] ; 0x07-- //这里也是这个变量循环自减一结果作为下次循环的值
LOAD:00038CD8 SUB             R3, R3, #1
LOAD:00038CDC STR             R3, [R11,#var_10]

直接在最后面下个断点跑完这个函数,可以看到返回值是0x80FF1E18

这是整个大循环

回到上一层函数,这个值应该是固定的,暂时没有看到有其它参数对这个计算过程造成了影响

一边分析一边写的,估计有些地方会分析错

这个函数整个大循环是0xF0次,也就是240次,我们来验证一下PolyXorKey函数是否每次都是生成一样的数据

0xFFFCBF78
0x60FF7ED8
0xFFFCFFF8
0x60FFFED8
0xFFFCFFF8
0x60FFFED8
0xFFFCFFF8
0x60FFFED8
0xFFFCFFF8
..........
0xFFFCFFF8

开始变成两个常数的交替出现,难道是动态调试出问题了

这里非常绕,跟了好几次都没有找到关键的地方,后来半猜半想,根据调用operator new[]()的函数往回找,找到了和前几题一样的函数,虽然这里算法不一样,但是对于用户名和注册码的存储还是一样的

接下来是校验的地方,单步走一遍先,找到关键的地方,可以看到这里调用了四个函数

但是在静态时这位置我是手动找的,这个费劲,有的函数没有识别出来,红色的

其实还有非常多的函数未识别出来,不过并不是很重要

由于前面没有完整的跟过来,所以这里的一些偏移需要根据动态调试确定指向的数据是什么

那么0x34偏移指向的就是用户名

并且有长度的限制,用户名长度应该在[8, 24]之间

LOAD:00005E66 MOVS            R0, R3  ; s
LOAD:00005E68 BLX             strlen  ; 获取用户名长度
LOAD:00005E6C MOVS            R6, R0  ; R6 = R0 = 用户名长度
LOAD:00005E6E SUBS            R6, #8  ; R6 = strlen(UserName) - 8
LOAD:00005E70 MOVS            R0, R5  ; s
LOAD:00005E72 BLX             strlen  ; 获取注册码长度
LOAD:00005E76 CMP             R6, #0x16 ; 对比strlen(UserName) - 8和0x16
LOAD:00005E78 BHI             loc_5E80

偏移0x38指向的是注册码,注册码长度需要在[12, 100]之间

LOAD:00005E7A SUBS            R0, #0xC
LOAD:00005E7C CMP             R0, #0x58 ; 'X' ; 对比strlen(RegCode) - 0xC和0x58
LOAD:00005E7E BLS             loc_5E94

第二个函数比较长

获取用户名

LOAD:00005D48 PUSH            {LR}
LOAD:00005D4A SUB             SP, SP, #0x2C
LOAD:00005D4C STR             R0, [SP,#0x30+var_20] ; 结构体基址
LOAD:00005D4E LDR             R0, [R0,#0x34] ; 获取用户名
LOAD:00005D50 BLX             strlen  ; R0 = 用户名长度
LOAD:00005D54 CMP             R0, #7  ; 用户名长度与7进行对比
LOAD:00005D56 BGT             loc_5D6E ; 用户名长度需要大于7 ---> strlen(UserMame) >= 8

存储一下中间变量

LOAD:00005D84 loc_5D84
LOAD:00005D84 LDR             R3, =(dword_16AF8 - 0x5D8A)
LOAD:00005D86 ADD             R3, PC ; dword_16AF8
LOAD:00005D88 ADDS            R3, #0x30 ; '0' ; env
LOAD:00005D8A STR             R3, [SP,#0x30+env] ; 将env变量存储到栈中
LOAD:00005D8C STR             R3, [SP,#0x30+var_C] ; var_C = env

进入一个0x08 * 0x100次的循环,循环获取用户名的前八位数据

LOAD:00005D9E loc_5D9E                ;
LOAD:00005D9E LDR             R1, [SP,#0x30+var_20] ; R1为结构体基址
LOAD:00005DA0 LDR             R2, [SP,#0x30+var_28] ; R2 = 0
LOAD:00005DA2 LDR             R3, [R1,#0x34] ; R3 = pUserName
LOAD:00005DA4 LDRB            R3, [R3,R2] ; 循环取用户名的字节数据
LOAD:00005DA6 STR             R3, [SP,#0x30+var_2C] ; 获取的数据暂存栈中

获取一个关键偏移

LOAD:00005DA8 loc_5DA8                ;
LOAD:00005DA8 MOVS            R3, #0  ; R3 = 0
LOAD:00005DAA STR             R3, [SP,#0x30+var_24] ; var_24 = 0
LOAD:00005DAC LDR             R3, =(dword_16AF8 - 0x5DB4)
LOAD:00005DAE LDR             R1, =0x104B2 ; R1 = 0x104B2
LOAD:00005DB0 ADD             R3, PC ; dword_16AF8 ; 定位结构体基址
LOAD:00005DB2 ADDS            R3, #0x30 ; '0' ; env
LOAD:00005DB4 STR             R3, [SP,#0x30+var_14] ; var_14 = env
LOAD:00005DB6 STR             R1, [SP,#0x30+var_10] ; var_10 = 0x104B2

这个偏移在这里的作用是重定位一个Table,通过和用户名相同的偏移来进行数据获取,然后两者异或

LOAD:00005DC8 loc_5DC8                ;
LOAD:00005DC8 LDR             R3, [SP,#0x30+var_10] ; R3 = 0x104B2
LOAD:00005DCA LDR             R2, [SP,#0x30+var_24] ; R2 = i
LOAD:00005DCC LDR             R1, [SP,#0x30+var_2C] ; R1 = UserName[i]
LOAD:00005DCE ADD             R3, PC  ; 动调 : R3 = 0xA33E8284
LOAD:00005DD0 ADDS            R3, #0x38 ; '8'
LOAD:00005DD2 LDRB            R3, [R2,R3] ; 同一偏移取某地址字节数据
LOAD:00005DD4 EORS            R3, R1  ; 两个地址同偏移数据异或
LOAD:00005DD6 LSLS            R3, R3, #0x18 ; 这两句效果等效&0xFF
LOAD:00005DD8 LSRS            R3, R3, #0x18
LOAD:00005DDA STR             R3, [SP,#0x30+var_2C]

大概就是

var_2C = ((byte) UserName[i] ^ (byte) Table[i]) & 0xFF

最后进行次数的判断

LOAD:00005DDC loc_5DDC                ;
LOAD:00005DDC LDR             R2, [SP,#0x30+var_24] ; var_24 = i
LOAD:00005DDE MOVS            R3, #0x100 ; R3 = 0x100
LOAD:00005DE2 ADDS            R2, #1  ; i++
LOAD:00005DE4 STR             R2, [SP,#0x30+var_24]
LOAD:00005DE6 CMP             R2, R3  ; i < 0x100 //循环0x100次
LOAD:00005DE8 BNE             loc_5DB8

每个字节一共是0x100次,动态调试把整个表dump出来

1A B7 00 3A 19 B7 00 2A  20 00 9D E5 C7 97 00 AA
C6 97 00 BA 91 03 03 E0  C8 3D 00 1A C7 3D 00 0A
48 60 9D E5 03 C2 00 CA  02 C2 00 DA 3C 40 8D E5
B4 3F 00 2A B3 3F 00 3A  E7 76 27 E2 F0 31 00 6A
EF 31 00 7A DA 2C 4C E2  AE A2 00 1A AD A2 00 0A
0A 6B 86 E2 04 C1 00 4A  03 C1 00 5A B4 20 9D E5
B2 18 00 AA B1 18 00 BA  06 30 8A E0 83 0C 00 0A
82 0C 00 1A 0A 80 88 E0  FA BE 00 3A F9 BE 00 2A
0C 10 21 E0 4A 9D 00 0A  49 9D 00 1A 21 5A 8F E2
DE 5E 85 E2 00 50 95 E5  63 84 00 AA 62 84 00 BA
D8 70 9D E5 E6 09 00 CA  E5 09 00 DA 91 02 02 E0
88 6B 00 6A 87 6B 00 7A  02 20 86 E0 DE 10 00 2A
DD 10 00 3A 87 3C 83 E2  31 C2 00 9A 30 C2 00 8A
9C B0 9D E5 EE A4 00 9A  ED A4 00 8A DD 06 00 1A
8C 8A 00 3A 8B 8A 00 2A  71 25 82 E2 F0 0C 00 6A
EF 0C 00 7A FA 19 21 E2  65 AF 00 6A 64 AF 00 7A

补充一点,这个表其实不是动态生成的,静态分析时就可以dump出来

因为异或的计算比较有意思,整个表循环异或一遍其实可以等效于异或一个值,这个值我们可以通过计算来确定,输入为0x00,看输出是什么即可

在IDA里将这个表保存为文件,使用WinHex打开,拷贝存为C Source

unsigned AnsiChar data[256] = {
	0x1A, 0xB7, 0x00, 0x3A, 0x19, 0xB7, 0x00, 0x2A, 0x20, 0x00, 0x9D, 0xE5, 0xC7, 0x97, 0x00, 0xAA, 
	0xC6, 0x97, 0x00, 0xBA, 0x91, 0x03, 0x03, 0xE0, 0xC8, 0x3D, 0x00, 0x1A, 0xC7, 0x3D, 0x00, 0x0A, 
	0x48, 0x60, 0x9D, 0xE5, 0x03, 0xC2, 0x00, 0xCA, 0x02, 0xC2, 0x00, 0xDA, 0x3C, 0x40, 0x8D, 0xE5, 
	0xB4, 0x3F, 0x00, 0x2A, 0xB3, 0x3F, 0x00, 0x3A, 0xE7, 0x76, 0x27, 0xE2, 0xF0, 0x31, 0x00, 0x6A, 
	0xEF, 0x31, 0x00, 0x7A, 0xDA, 0x2C, 0x4C, 0xE2, 0xAE, 0xA2, 0x00, 0x1A, 0xAD, 0xA2, 0x00, 0x0A, 
	0x0A, 0x6B, 0x86, 0xE2, 0x04, 0xC1, 0x00, 0x4A, 0x03, 0xC1, 0x00, 0x5A, 0xB4, 0x20, 0x9D, 0xE5, 
	0xB2, 0x18, 0x00, 0xAA, 0xB1, 0x18, 0x00, 0xBA, 0x06, 0x30, 0x8A, 0xE0, 0x83, 0x0C, 0x00, 0x0A, 
	0x82, 0x0C, 0x00, 0x1A, 0x0A, 0x80, 0x88, 0xE0, 0xFA, 0xBE, 0x00, 0x3A, 0xF9, 0xBE, 0x00, 0x2A, 
	0x0C, 0x10, 0x21, 0xE0, 0x4A, 0x9D, 0x00, 0x0A, 0x49, 0x9D, 0x00, 0x1A, 0x21, 0x5A, 0x8F, 0xE2, 
	0xDE, 0x5E, 0x85, 0xE2, 0x00, 0x50, 0x95, 0xE5, 0x63, 0x84, 0x00, 0xAA, 0x62, 0x84, 0x00, 0xBA, 
	0xD8, 0x70, 0x9D, 0xE5, 0xE6, 0x09, 0x00, 0xCA, 0xE5, 0x09, 0x00, 0xDA, 0x91, 0x02, 0x02, 0xE0, 
	0x88, 0x6B, 0x00, 0x6A, 0x87, 0x6B, 0x00, 0x7A, 0x02, 0x20, 0x86, 0xE0, 0xDE, 0x10, 0x00, 0x2A, 
	0xDD, 0x10, 0x00, 0x3A, 0x87, 0x3C, 0x83, 0xE2, 0x31, 0xC2, 0x00, 0x9A, 0x30, 0xC2, 0x00, 0x8A, 
	0x9C, 0xB0, 0x9D, 0xE5, 0xEE, 0xA4, 0x00, 0x9A, 0xED, 0xA4, 0x00, 0x8A, 0xDD, 0x06, 0x00, 0x1A, 
	0x8C, 0x8A, 0x00, 0x3A, 0x8B, 0x8A, 0x00, 0x2A, 0x71, 0x25, 0x82, 0xE2, 0xF0, 0x0C, 0x00, 0x6A, 
	0xEF, 0x0C, 0x00, 0x7A, 0xFA, 0x19, 0x21, 0xE2, 0x65, 0xAF, 0x00, 0x6A, 0x64, 0xAF, 0x00, 0x7A
};

写个程序跑一下

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

unsigned char xor_table[256] = {
	0x1A, 0xB7, 0x00, 0x3A, 0x19, 0xB7, 0x00, 0x2A, 0x20, 0x00, 0x9D, 0xE5, 0xC7, 0x97, 0x00, 0xAA,
	0xC6, 0x97, 0x00, 0xBA, 0x91, 0x03, 0x03, 0xE0, 0xC8, 0x3D, 0x00, 0x1A, 0xC7, 0x3D, 0x00, 0x0A,
	0x48, 0x60, 0x9D, 0xE5, 0x03, 0xC2, 0x00, 0xCA, 0x02, 0xC2, 0x00, 0xDA, 0x3C, 0x40, 0x8D, 0xE5,
	0xB4, 0x3F, 0x00, 0x2A, 0xB3, 0x3F, 0x00, 0x3A, 0xE7, 0x76, 0x27, 0xE2, 0xF0, 0x31, 0x00, 0x6A,
	0xEF, 0x31, 0x00, 0x7A, 0xDA, 0x2C, 0x4C, 0xE2, 0xAE, 0xA2, 0x00, 0x1A, 0xAD, 0xA2, 0x00, 0x0A,
	0x0A, 0x6B, 0x86, 0xE2, 0x04, 0xC1, 0x00, 0x4A, 0x03, 0xC1, 0x00, 0x5A, 0xB4, 0x20, 0x9D, 0xE5,
	0xB2, 0x18, 0x00, 0xAA, 0xB1, 0x18, 0x00, 0xBA, 0x06, 0x30, 0x8A, 0xE0, 0x83, 0x0C, 0x00, 0x0A,
	0x82, 0x0C, 0x00, 0x1A, 0x0A, 0x80, 0x88, 0xE0, 0xFA, 0xBE, 0x00, 0x3A, 0xF9, 0xBE, 0x00, 0x2A,
	0x0C, 0x10, 0x21, 0xE0, 0x4A, 0x9D, 0x00, 0x0A, 0x49, 0x9D, 0x00, 0x1A, 0x21, 0x5A, 0x8F, 0xE2,
	0xDE, 0x5E, 0x85, 0xE2, 0x00, 0x50, 0x95, 0xE5, 0x63, 0x84, 0x00, 0xAA, 0x62, 0x84, 0x00, 0xBA,
	0xD8, 0x70, 0x9D, 0xE5, 0xE6, 0x09, 0x00, 0xCA, 0xE5, 0x09, 0x00, 0xDA, 0x91, 0x02, 0x02, 0xE0,
	0x88, 0x6B, 0x00, 0x6A, 0x87, 0x6B, 0x00, 0x7A, 0x02, 0x20, 0x86, 0xE0, 0xDE, 0x10, 0x00, 0x2A,
	0xDD, 0x10, 0x00, 0x3A, 0x87, 0x3C, 0x83, 0xE2, 0x31, 0xC2, 0x00, 0x9A, 0x30, 0xC2, 0x00, 0x8A,
	0x9C, 0xB0, 0x9D, 0xE5, 0xEE, 0xA4, 0x00, 0x9A, 0xED, 0xA4, 0x00, 0x8A, 0xDD, 0x06, 0x00, 0x1A,
	0x8C, 0x8A, 0x00, 0x3A, 0x8B, 0x8A, 0x00, 0x2A, 0x71, 0x25, 0x82, 0xE2, 0xF0, 0x0C, 0x00, 0x6A,
	0xEF, 0x0C, 0x00, 0x7A, 0xFA, 0x19, 0x21, 0xE2, 0x65, 0xAF, 0x00, 0x6A, 0x64, 0xAF, 0x00, 0x7A
};

int main()
{
	unsigned char test =0x00;
	for (int i = 0; i < 256; i++)
	{
		test ^= xor_table[i];
	}
	printf("0x%x\n", test);
	return 0;
}

可以看到整个异或表的异或效果和单独异或0x93的效果是一样的

计算完后会判断计算后的数据是否为0

LOAD:00005DFE loc_5DFE                ;
LOAD:00005DFE LDR             R1, [SP,#0x30+var_1C] ; R1 = 异或后的数据
LOAD:00005E00 CMP             R1, #0  ; 判断异或后的数据是否为0
LOAD:00005E02 BNE             loc_5E1E

如果是0,则会改为0x99

LOAD:00005E04 LDR             R0, =(dword_16AF8 - 0x5E0A)
LOAD:00005E06 ADD             R0, PC ; dword_16AF8
LOAD:00005E08 ADDS            R0, #0x30 ; '0' ; env
LOAD:00005E0A BLX             setjmp_0
LOAD:00005E0E MOVS            R2, #0x99 ; '
LOAD:00005E10 STR             R2, [SP,#0x30+var_2C] ; 如果计算后的数据为0,则改为0x99
LOAD:00005E12 CMP             R0, #0
LOAD:00005E14 BEQ             loc_5E1E

每个字节计算完成存储到栈中,一共八次

LOAD:00005E1E loc_5E1E                ;
LOAD:00005E1E LDR             R2, [SP,#0x30+var_28] ; R2 = i
LOAD:00005E20 LDR             R1, [SP,#0x30+var_20] ; R1 = 结构体基址
LOAD:00005E22 ADDS            R3, R1, R2
LOAD:00005E24 LDR             R2, [SP,#0x30+var_28]
LOAD:00005E26 MOV             R1, SP
LOAD:00005E28 LDRB            R1, [R1,#0x30+var_2C]
LOAD:00005E2A ADDS            R3, #0x5A ; 'Z' ; 0x5A为计算后数据存储偏移
LOAD:00005E2C ADDS            R2, #1
LOAD:00005E2E STRB            R1, [R3] ; 将计算后的值存储到栈中
LOAD:00005E30 STR             R2, [SP,#0x30+var_28]
LOAD:00005E32 CMP             R2, #8  ; 计算8字节,那么取的就是用户名前8位
LOAD:00005E34 BNE             loc_5D8E

最终我们可以看到生成的8字节数据

在上图的位置下个断点,数据区跟随R3,可以看到完整的生成过程

第三个函数,就一个小循环,应该比较简单

后来分析下来是我错了,它不简单,参数之类的预处理

LOAD:000060A0 PUSH            {R4-R6,LR}
LOAD:000060A2 LDR             R3, =(off_15E9C - 0x60AC)
LOAD:000060A4 SUB             SP, SP, #0x18
LOAD:000060A6 STR             R0, [SP,#0x28+var_24] ; var_24 = 结构体基址
LOAD:000060A8 ADD             R3, PC ; off_15E9C
LOAD:000060AA LDR             R3, [R3] ; __stack_chk_guard
LOAD:000060AC ADD             R0, SP, #0x28+s ; s
LOAD:000060AE MOVS            R1, #0  ; R1 = 0
LOAD:000060B0 LDR             R3, [R3]
LOAD:000060B2 MOVS            R2, #0xA ; R2 = 0x0A
LOAD:000060B4 STR             R3, [SP,#0x28+var_14] ; 栈保护
LOAD:000060B6 BLX             memset  ; memset(s, 0, 0x0A)
LOAD:000060BA LDR             R0, =(dword_16AF8 - 0x60C0)
LOAD:000060BC ADD             R0, PC ; dword_16AF8
LOAD:000060BE ADDS            R0, #0x30 ; '0' ; env
LOAD:000060C0 BLX             setjmp_0
LOAD:000060C4 SUBS            R4, R0, #0 ; R4 = 0 //影响了标志位,这里用于判断函数的执行
LOAD:000060C6 BEQ             loc_60CE

调用了一个函数,这个函数可复杂了

LOAD:000060CE loc_60CE                ;
LOAD:000060CE LDR             R0, [SP,#0x28+var_24] ; R0 = 结构体基址
LOAD:000060D0 ADDS            R0, #0x5A ; 'Z' ; 取出计算后的8字节数据
LOAD:000060D2 BL              sub_57D4

参数是计算后的8字节数据,里面有五个函数的调用,继续一个个跟

开始做参数的存储,重定位了一个Table

LOAD:000057D4 PUSH            {R0-R2,R4-R7,LR}
LOAD:000057D6 LDR             R4, =(dword_165F8 - 0x57E0)
LOAD:000057D8 MOVS            R1, R0  ; R1 = R0 = 计算后的8字节数据
LOAD:000057DA MOVS            R2, #0x40 ; '@' ; R2 = 0x40
LOAD:000057DC ADD             R4, PC ; dword_165F8

这个Table在动态调试的过程中是有值的

但是在静态分析的时候是空的,这里有一个0x30的偏移

我看到了后面一层又一层的,而且明显的跟其它数据分开了

我发现不对劲,而且这么多计算我都看不懂,于是开启猜测模式

这里应该是某加密,前面那个8字节应该是秘钥,然后后面的函数一个个看,看看有没有什么Table,现代加密算法一般都有各种Table去做计算

运气不错,发现了DES加密算法的S盒

它是八个二维数组,规格就是8 * 4 * 16

可以自行对比一下,当然也可以靠其它Table的特征

当然AES也有S盒,但是这两者的S盒是有很多区别的,比如AES的S盒如下

unsigned char sBox[] =
{ /*  0    1    2    3    4    5    6    7    8    9    a    b    c    d    e    f */
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, /*0*/ 
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, /*1*/
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, /*2*/
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, /*3*/
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, /*4*/
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, /*5*/
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, /*6*/ 
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, /*7*/
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, /*8*/
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, /*9*/
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, /*a*/
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, /*b*/
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, /*c*/
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, /*d*/
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, /*e*/
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16  /*f*/
};

首先是规模不一样,其实是数量不一样,输入输出的值也都不一样

那么这里可以确定是DES加密算法,但是它是加密还是解密就需要再考量一下了

先放着,我们接着看代码,在初始化完秘钥后,开始给两个数组进行初始化操作

LOAD:000060D6 LDR             R2, [SP,#0x28+var_24] ; R2 = 结构体基址
LOAD:000060D8 LDR             R0, [R2,#0x38] ; R0 = s1 //结构体偏移0x38,为注册码
LOAD:000060DA BLX             strlen  ; R0 = strlen(s)
LOAD:000060DE LDR             R5, [SP,#0x28+var_24] ; R5 = 结构体基址
LOAD:000060E0 LSRS            R6, R0, #4 ; R6 = strlen(s) >> 4 //长度一定是非负,等效于除16
LOAD:000060E2 MOVS            R1, R4  ; R1 = R4 = 0
LOAD:000060E4 ADDS            R5, #0x3C ; '<' ; R5 = s2 //结构体偏移0x3C
LOAD:000060E6 MOVS            R0, R5  ; R0 = R5 = s2
LOAD:000060E8 MOVS            R2, #0x1E ; R2 = 0x1E
LOAD:000060EA BLX             memset  ; memset(s2, 0, 0x1E)
LOAD:000060EE B               loc_6108

顺带把注册码分为16字节每组,每组进行循环解密

LOAD:00006108 loc_6108
LOAD:00006108 CMP             R4, R6
LOAD:0000610A BLT             loc

解密后的数据存储到s2,结构体偏移0x3C

LOAD:000060F0 loc_60F0                ;
LOAD:000060F0 LDR             R2, [SP,#0x28+var_24] ; R2 = 结构体基址
LOAD:000060F2 LSLS            R3, R4, #4 ; i << 4 //此处用于注册码偏移的跳转
LOAD:000060F4 ADD             R0, SP, #0x28+s ; R0 = s = buffer
LOAD:000060F6 LDR             R1, [R2,#0x38] ; R1 = s1 = 注册码
LOAD:000060F8 ADDS            R4, #1
LOAD:000060FA ADDS            R1, R1, R3
LOAD:000060FC BL              sub_58F8 ; 此处入口对数据进行解密
LOAD:00006100 MOVS            R0, R5  ; dest
LOAD:00006102 ADD             R1, SP, #0x28+s ; src
LOAD:00006104 BLX             strcat_0 ; 解密后的数据存储到s2

最后第四个函数就是解密后的注册码和用户名进行对比,红色表示异常分支,蓝色表示正常循环,最后由两个灰色的代码块结束循环

我们来计算一组有效的KEY

不过好像出了点问题,哪里不对的样子

因为在分析的时候我注意到了取了用户名前8位进行计算秘钥,而且后续使用了十六位进行分组解密

所以这里单纯的使用了一个八字节字符串当做用户名进行输入

再次打个断点进行调试,看看解密后的数据是个啥

首先获取注册码

然后两组计算完后,得到解密后的数据

那这个就很尴尬了,怎么会多出八位

发现用Java的加解密库计算出来的数据并不正确,其实可能是校验的过程改了

正常情况下解密出来的数据应该是这样的

既然这样,那我就不客气了,去网上找DES的C代码实现

随意找了个代码,看到了S盒,想起刚才也是S盒,会不会S盒动了手脚,于是对比了一波S盒

首先把正常DES算法的S盒准备好

static char S_Box[8][4][16] = {
	// S1 
	14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
	0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
	4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
	15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13,
	// S2 
	15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
	3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
	0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
	13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9,
	// S3 
	10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
	13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
	13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
	1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12,
	// S4 
	7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
	13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
	10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
	3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14,
	// S5 
	2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
	14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
	4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
	11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3,
	// S6 
	12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
	10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
	9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
	4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13,
	// S7 
	4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
	13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
	1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
	6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12,
	// S8 
	13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
	1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
	7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
	2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
};

然后将动态调试时的S盒dump出来

跟前面拷贝出xor_table一样,使用保存为文件,然后WinHex转为C Source

unsigned AnsiChar data[512] = {
	0x0E, 0x04, 0x0D, 0x01, 0x02, 0x0F, 0x0B, 0x08, 0x03, 0x0A, 0x06, 0x0C, 0x05, 0x09, 0x00, 0x07, 
	0x00, 0x0F, 0x07, 0x04, 0x0E, 0x02, 0x0D, 0x01, 0x0A, 0x06, 0x0C, 0x0B, 0x09, 0x05, 0x03, 0x08, 
	0x04, 0x01, 0x0E, 0x08, 0x0D, 0x06, 0x02, 0x0B, 0x0F, 0x0C, 0x09, 0x07, 0x03, 0x0A, 0x05, 0x00, 
	0x0F, 0x0C, 0x08, 0x02, 0x04, 0x09, 0x01, 0x07, 0x05, 0x0B, 0x03, 0x0E, 0x0A, 0x00, 0x06, 0x0D, 
	0x0F, 0x01, 0x08, 0x0E, 0x06, 0x0B, 0x03, 0x04, 0x09, 0x07, 0x02, 0x0D, 0x0C, 0x00, 0x05, 0x0A, 
	0x03, 0x0D, 0x04, 0x07, 0x0F, 0x02, 0x08, 0x0E, 0x0C, 0x00, 0x01, 0x0A, 0x06, 0x09, 0x0B, 0x05, 
	0x00, 0x0E, 0x07, 0x0B, 0x0A, 0x04, 0x0D, 0x01, 0x05, 0x08, 0x0C, 0x06, 0x09, 0x03, 0x02, 0x0F, 
	0x0D, 0x08, 0x0A, 0x01, 0x03, 0x0F, 0x04, 0x02, 0x0B, 0x06, 0x07, 0x0C, 0x00, 0x05, 0x0E, 0x09, 
	0x0A, 0x00, 0x09, 0x0E, 0x06, 0x03, 0x0F, 0x05, 0x01, 0x0D, 0x0C, 0x07, 0x0B, 0x04, 0x02, 0x08, 
	0x0D, 0x07, 0x00, 0x09, 0x03, 0x04, 0x06, 0x0A, 0x02, 0x08, 0x05, 0x0E, 0x0C, 0x0B, 0x0F, 0x01, 
	0x0D, 0x06, 0x04, 0x09, 0x08, 0x0F, 0x03, 0x00, 0x0B, 0x01, 0x02, 0x0C, 0x05, 0x0A, 0x0E, 0x07, 
	0x01, 0x0A, 0x0D, 0x00, 0x06, 0x09, 0x08, 0x07, 0x04, 0x0F, 0x0E, 0x03, 0x0B, 0x05, 0x02, 0x0C, 
	0x07, 0x0D, 0x0E, 0x03, 0x00, 0x06, 0x09, 0x0A, 0x01, 0x02, 0x08, 0x05, 0x0B, 0x0C, 0x04, 0x0F, 
	0x0D, 0x08, 0x0B, 0x05, 0x06, 0x0F, 0x00, 0x03, 0x04, 0x07, 0x02, 0x0C, 0x01, 0x0A, 0x0E, 0x09, 
	0x0A, 0x06, 0x09, 0x00, 0x0C, 0x0B, 0x07, 0x0D, 0x0F, 0x01, 0x03, 0x0E, 0x05, 0x02, 0x08, 0x04, 
	0x03, 0x0F, 0x00, 0x06, 0x0A, 0x01, 0x0D, 0x08, 0x09, 0x04, 0x05, 0x0B, 0x0C, 0x07, 0x02, 0x0E, 
	0x02, 0x0C, 0x04, 0x01, 0x07, 0x0A, 0x0B, 0x06, 0x08, 0x05, 0x03, 0x0F, 0x0D, 0x00, 0x0E, 0x09, 
	0x0E, 0x0B, 0x02, 0x0C, 0x04, 0x07, 0x0D, 0x01, 0x05, 0x00, 0x0F, 0x0A, 0x03, 0x09, 0x08, 0x06, 
	0x04, 0x02, 0x01, 0x0B, 0x0A, 0x0D, 0x07, 0x08, 0x0F, 0x09, 0x0C, 0x05, 0x06, 0x03, 0x00, 0x0E, 
	0x0B, 0x08, 0x0C, 0x07, 0x01, 0x0E, 0x02, 0x0D, 0x06, 0x0F, 0x00, 0x09, 0x0A, 0x04, 0x05, 0x03, 
	0x0C, 0x01, 0x0A, 0x0F, 0x09, 0x02, 0x06, 0x08, 0x00, 0x0D, 0x03, 0x04, 0x0E, 0x07, 0x05, 0x0B, 
	0x0A, 0x0F, 0x04, 0x02, 0x07, 0x0C, 0x00, 0x05, 0x06, 0x01, 0x0D, 0x0E, 0x00, 0x0B, 0x03, 0x08, 
	0x09, 0x0E, 0x0F, 0x05, 0x02, 0x08, 0x0C, 0x03, 0x07, 0x00, 0x04, 0x0A, 0x01, 0x0D, 0x0B, 0x06, 
	0x04, 0x03, 0x02, 0x0C, 0x09, 0x05, 0x0F, 0x0A, 0x0B, 0x0E, 0x01, 0x07, 0x06, 0x00, 0x08, 0x0D, 
	0x04, 0x0B, 0x02, 0x0E, 0x0F, 0x00, 0x08, 0x0D, 0x03, 0x0C, 0x09, 0x07, 0x05, 0x0A, 0x06, 0x01, 
	0x0D, 0x00, 0x0B, 0x07, 0x04, 0x00, 0x01, 0x0A, 0x0E, 0x03, 0x05, 0x0C, 0x02, 0x0F, 0x08, 0x06, 
	0x01, 0x04, 0x0B, 0x0D, 0x0C, 0x03, 0x07, 0x0E, 0x0A, 0x0F, 0x06, 0x08, 0x00, 0x05, 0x09, 0x02, 
	0x06, 0x0B, 0x0D, 0x08, 0x01, 0x04, 0x0A, 0x07, 0x09, 0x05, 0x00, 0x0F, 0x0E, 0x02, 0x03, 0x0C, 
	0x0D, 0x02, 0x08, 0x04, 0x06, 0x0F, 0x0B, 0x01, 0x0A, 0x09, 0x03, 0x0E, 0x05, 0x00, 0x0C, 0x07, 
	0x01, 0x0F, 0x0D, 0x08, 0x0A, 0x03, 0x07, 0x04, 0x0C, 0x05, 0x06, 0x0B, 0x00, 0x0E, 0x09, 0x02, 
	0x07, 0x0B, 0x04, 0x01, 0x09, 0x0C, 0x0E, 0x02, 0x00, 0x06, 0x0A, 0x0D, 0x0F, 0x03, 0x05, 0x08, 
	0x02, 0x01, 0x0E, 0x07, 0x04, 0x0A, 0x08, 0x0D, 0x0F, 0x0C, 0x09, 0x00, 0x03, 0x05, 0x06, 0x0B
};

然后跟上面正常的S盒进行循环对比,找到不同的地方

找了个C实现的DES算法代码,发现结果不对

想了想,如果S盒有问题,那么其它几个Table和盒子可能也有问题,于是开始对比了一波,最后发现PC2_Table有问题

再一次的计算,发现注册码计算还是有问题,当时场面一度很尴尬

突然,我想起了一件事,秘钥开始的时候经过了一次神奇的异或

赶紧的赶紧的,继续改代码,东平西凑,瞎改瞎改

就先这样吧,眼泪掉下来,以后再找个时间分析一下这个样本的保护技术

最后,如果是第一次接触这种动静结合分析的同学,要时刻注意指令集的切换,中间有大量的指令集切换,看指令的地址即可,通常都是三步走,断在调用处,先别跟过去,此时跟过去会断不下来的,直接效果就是和F9一样,这一点应该有体会吧,比如使用的是BL R3,在这一句下个断点,先断下来,直接在反汇编窗口跟随R3,就可以看到要执行的代码了,但是如果指令集识别有问题,需要先ALT + G,选择Thumb模式,然后按一下C转为代码模式,再按P识别函数

Last updated