难,这题真是难,ida附加,一堆的乱序指令、变形BL和花指令,一不小心就跑飞了,需要大量的体力去恢复,所以时间关系大家都止步于此;不过,此题对我等小鸟来说,很有研究价值;好了,废话不多说,下面看我的分析吧:
正确座位号:L6^a9>z<81Li!*M
一,定位check函数,方法很多,说下,我自己的方法,看下安卓源码,调用jni函数,都是通过 libdvm.so 中的 dvmPlatformInvoke 函数调用的,所以直接hook这个函数,就能打印出check函数;
二,说下乱序指令和变形BL:
3种乱序指令:
1种:
PUSH {R0,R1,LR}
LDR R0, XXXXXXXX
BL XXXXXXXX
相当于BL lr + Dword(2 * (lr >> 1) + 4 * r0 + 4) - 1;
2种:
PUSH {R0,R1,LR}
BL XXXXXXXX
相当于BL lr + Dword(2 * (lr >> 1)) - 1;
这种指令有种变形,直接 BL 模块基地址+0x2A5B4
3种:
SUB SP, SP, #8
PUSH {R0,R1,LR}
BL XXXXXXXX
相当于push {Dword(2 * (lr >> 1) + Dword(2 * (lr >> 1)))};
然后 BL lr + 4 - 1;
后面一般跟 POP Rx
然后跟
CMP Rx,Rx
BXX XXXXXXXX
这种指令,可能重复n多次,比较难以处理,我的方法是RunTo(BXX的PC)那,然后StepOver();
1种变形BL:
BL XXXXXXXX
相当于BL Dword(2 * (lr >> 1) + Dword(2 * (lr >> 1)))
这种属于调用函数的变形BL,可以隐藏api函数
这些乱序指令和变形BL,使得单步调试很容易就跑飞了,并且IDA的F5功能也全完失效;不过知道了它的特征,可以写个idc脚本,可以把真实的指令给跑出来,idc脚本见附件。
三,check函数的逆向分析:
1、效验偏移0x1F08,大小0x286DC字节的代码CRC32效验值;
2、比较座位长度是否等于0xF;
3、反调试的检测,通过调用r_debug->r_brk函数触发SIGILL异常;
4、经过2个循环,得到一个0x100字节的表,接着通过raise(0x5)和raise(0xB)抛出SIGTRAP和SIGSEGV异常,然后在SIGSEGV异常的接管函数里会循环和V值进行异或,最后循环和CRC32效验值异或,最后得到一个0x100字节的表;
5、最后得到的0x100字节的表和正确坐标的表比较,相等就验证通过;
说的有点乱,下面看IDC跑出的代码,这里我只贴关键代码:
先看下函数大的流程:
4BEDB510: PUSH {R4-R7,LR}
4BEDB512: ADD R7, SP, #0xC
4BEDB514: SUB SP, SP, #0x5C
.........
4BEDB0DC: MOV R2, R3
4BEDAF1C: BL sub_4BEDF3C0 //得到座位 R6,#0x24
4BEDAF24: STR R0, [R6,#0x24]
4BEDAEC4: BL sub_4004112C //strlen
4BEDAECC: STR R0, [R6,#0x14]
4BEDAECE: ADDS R4, R0, #1
4BEDB1F0: MOV R0, R4
4BEDB1F2: NOP
4BEDB1F4: BL malloc
4BEDB1FC: MOV R5, R0
4BEDB1DC: LDR R1, [R6,#0x24]
4BEDB1DE: MOV R2, R4
4BEDB1E0: LDR R4, [R6,#0x18]
4BEDB404: BL sub_4BECD86C //memcpy
......
4BEDB1C8: BL lrand48
.........
4BED962C: BL sleep
.........
4BED968C: LDR R0, [R5]
4BED9640: BL sub_4004112C //strlen
//循环开始
4BEDA370: LDR R0, [R5]
.........
4BEDA116: BLT unk_4BEDA124 //R2==64 跳出循环
.........
4BEDA1CC: LDR R0, [R4]
//循环结束
//循环开始
4BED982A: LDR R0, [R6,#0x18]
.........
4BEDA7D4: BEQ unk_4BEDA7E0 //R1=0跳出循环
.........
4BEDAD62: ADDS R1, R2, R1 //这里能看到R1 = CRC32 table
........
4BED9BD4: STR R1, [R6]
/循环结束 这里计算偏移0x1F08,大小0x286DC字节的代码CRC32效验值
.......
4BED97C8: STR R2, [R6,#0x18] //R2 = R2 ^ 0xFFFFFFFF 得到最后的CRC效验值
//4BED97CA: push {[0x4BEDB5E0]} -->1279E//4BED97D8: POP {R0}
4BED97DA: ADD R0, PC
//4BED9F78: push {[0x4BEDB5C0]} -->FFFFFF58//4BED9F84: POP {R1}
4BED9EC8: ADDS R0, R1, R0
4BED9ECA: LDR R0, [R0]
4BED9ECC: MOVS R1, #0x80
4BED9F38: BL sub_4BEE01F0
4BED9F40: LDR R1, [R6,#0x28]
4BED9EDC: LDR R1, [R1]
4BEDA0B8: ADDS R2, R1, #1
4BED9EA8: CMP R0, #0
4BED9EAA: BEQ unk_4BED9EB8
4BED9EB8:
4BED9EB8: MOV R2, R1
4BEDA900: STR R2, [R6,#0x14]
4BEDA902: MOV R0, R2 //座位字符串
4BEDA904: LDR R1, [R6,#0x18] //CRC效验值
4BEDA934: BL sub_4BECE6F8 //验证座位的关键函数
4BEDA93C: STR R0, [R6,#0x18]
4BED9E80: LDR R0, [R6,#0x14]
4BED9E82: NOP
4BED9E84: BL free
.............
下面看sub_4BECE6F8 函数
4BED7F14: PUSH {R4-R7,LR}
4BED7F16: ADD R7, SP, #0xC
4BED7F18: SUB SP, SP, #0x114
.......
4BED889C: STR R1, [R6,#0x48] //保存CRC32效验值
4BED89A0: STR R0, [R6,#0x4C] //保存座位地址
.......
4BED3B66: NOP
4BED3B68: BL __aeabi_memset8
.......
4BED3C10: LDR R2, [R6,#0x24]
4BED3BE8: BL __aeabi_memset8
..........
4BED5130: LDR R0, [R6,#0x4C]
4BED5132: NOP
4BED5134: BL sub_4004112C //strlen
.........
//循环开始
4BED8610: LDR R0, [R6,#0x64]
........
4BED6056: CMP R2, #0xFF
4BED6038: MOV R2, R0
4BED603A: BGT unk_4BED6048 //跳出循环
........
4BED6BA6: LDR R1, [R0,#0x20]
4BED6D10: LDR R0, [R0,#0x5C]
4BED6D12: NOP
4BED6D14: BL sub_4BEE7F78 //R0 = R0 % R1
...........
4BED1384: STR R1, [R6,#0x60]
4BED1386: NOP
//循环结束 这里和KeyTable表生成有关系,后面还有一段循环算法
4BED188A: CMP R2, #0x3C //0x3C是由座位长度算出来的
这里要说下如果长度不等于0xF,就返回,不执行下面的反调试,
4BED17CC: BNE unk_4BED17D8 //判断长度是否==0xF
//循环开始
4BED3A38: STR R0, [R6,#0x44]
............
4BED780A: LDR R1, [R0,#0x20] //R1 = 长度
4BED77B0: LDR R0, [R0,#0x64]
4BED77B2: NOP
4BED77B4: BL sub_4BEE7F78 //R0 = R0 % R1
4BED77BC: LDR R1, [R6,#0x4C]
4BED7818: ADDS R0, R1, R0 //坐标地址++
........
4BED7E14: LDR R0, [R6,#0x24] //取[座位地址]
4BED7E16: LSRS R0, R1 //循环右移n&7位
4BED7E18: STR R0, [R6,#0x20]
.......
4BED3B2A: ADDS R0, #1
//循环结束,这段循环相当于下面的功能,lpString 座位字符串 lpBuffer 最后得到一个0x100字节的表
for (i=0;i<0x100;i++)
{
lpBuffer[i] = cror(lpString[i%0xF],i&7);
}
4BED17E0: MOV R0, R1
//4BED2A94: push {[0x4BED26BC]} -->119E6FC1//4BED2AA0: POP {R1}//4BED2C90: CMP R0, R1//4BED2AB0: BNE unk_4BED2ABC
4BED2AC4: BL sub_4BECBD90 //获取 r_debug->r_brk 函数
........
4BED5498: MOV LR, R0
4BED549A: MOV R0, LR
4BED549C: BL sub_4BEE95D4 //调用r_debug->r_brk 函数,调试状态下,会触发 SIGILL异常,导致程序终止;
........
//循环开始
4BED637C: STR R0, [R6,#0x54]
.....
4BED3C4E: CMP R2, #0xFF
....
4BED7CC4: MOVS R1, #5
4BED7D80: LDR R0, [R3,#0x6C]
4BED7D82: NOP
4BED7D84: BL sub_4BEE7F78 //R0 = R0 % 5
.....
4BED7D58: ADDS R0, #1
/循环结束 这里和KeyTable表生成有关系,由于是固定的,直接扣内存
........
4BED28DC: MOVS R0, #5
4BED2BDC: BL sub_400440E4 //raise(5) SIGTRAP 接管函数 4BEC10A4
........
//循环开始
4BED34DC: LDR R0, [R6,#0x64]
..........
4BED27CA: CMP R2, #0
4BED2800: MOV R2, R0
4BED2780: BNE unk_4BED278C //R2 == 1 跳出循环
..........
4BED343E: LDR R1, [R3,#0x1C] //KeyTable
4BED344C: ADDS R1, R1, R2 //R1 =KeyTable + ([KeyTable+n-1]+[KeyTable+n]) & 0xFF
4BED342C: LDRB R2, [R1]
4BED3400: LDR R0, [R6,#0x20]
4BED3402: STRB R2, [R0] //[KeyTable+n] = [KeyTable+[KeyTable+n]]
4BED31D4: LDR R2, [R6,#0x24]
4BED31D6: STRB R2, [R1] //[KeyTable+[KeyTable+n]] = [KeyTable+n] 以上代码 相当于互换值
.............
4BED3394: LDR R0, [R6,#0x24]
4BED3384: LDR R1, [R6,#0x18]
4BED3308: ADDS R0, R1, R0 //前面对换的值相加
...........
4BED37B4: LDR R0, [R6,#0x20]
4BED37B6: LSRS R0, R0, #5
4BED3774: MOVS R1, #6
4BED3776: ANDS R1, R0
4BED3778: BICS R3, R0
4BED4B9C: ORRS R3, R1
4BED4B9E: EORS R3, R2 //相当于 [lpBuffer+ n] 循环向左移动3位
...............
4BED07E4: LDRB R1, [R0] //取 [lpBuffer+ n] 循环向左移动3位 的值
4BED07E6: LDR R0, [R6,#0x1C]
4BED07E8: STRB R1, [R0] //[lpBuffer+ n] = [lpBuffer+ n] 循环向左移动3位 的值
4BECFEF4: MOVS R2, #0x53
4BED0660: STR R2, [R6,#0x24]
4BED04F4: MVNS R0, R2
4BED04F6: MOV R3, R0
4BECFEE4: BICS R3, R1
4BECFEE6: ANDS R1, R2
4BECFEC0: ORRS R1, R3
4BECFEC2: LDR R2, [R6,#0x64]
4BECFEC4: LDR R3, [R2,#0x1C] //取KeyTable地址
4BED06F0: LDR R2, [R6,#0x28] //取之前相加的值
4BED0600: ADDS R3, R3, R2
4BED0602: LDRB R3, [R3] //取 KeyTable[取之前相加的值]
4BED0604: LDR R2, [R6,#0x24]
4BECFEB0: ANDS R2, R3
4BED0764: BICS R0, R3
4BED0766: ORRS R0, R2
4BED0754: EORS R0, R1
4BED0756: STR R0, [R6,#0x28] //保存 KeyTable[取之前相加的值] ^ [lpBuffer+ n] 循环向左移动3位
4BED0744: LDR R1, [R6,#0x1C] //lpBuffer+ n
4BED1AB4: STRB R0, [R1] //[lpBuffer+ n] = KeyTable[取之前相加的值] ^ [lpString + n] 循环向左移动3位
............
4BED0D8A: STR R0, [R6,#0x28]
//4BED0D40: push {[0x4BED21F8]} -->F04DCF0F//4BED0D4C: POP {R0}
4BED0D4E: NOP
//循环结束 这段循环相当于下面的功能
for (i=0;i<0x100;i++)
{
if (i == 0xFF)
{
j = 0;
}
else
{
j = i+1;
}
t = szKeyTable[j];
k = (k + szKeyTable[j]) & 0xFF;
szKeyTable[j] = szKeyTable[k];
szKeyTable[k] = t;
sum = (szKeyTable[j] + szKeyTable[k]) & 0xFF;
t = lpBuffer[i];
t = crol(t,3);
t = cror((t ^ szKeyTable[sum]),i&7);
lpBuffer[i] = t;
}
...........
4BED45E8: MOVS R0, #0xB
4BED45EA: NOP
4BED45EC: BL sub_400440E4 //raise(0xB) SIGSEGV 接管函数 4BEC10A4, 这里会循环和V值进行异或,代码如下:
for (i=0;i<0x100;i++)
{
lpBuffer[i] = crol((lpBuffer[i] ^ 0x29),i&7);
}
.........
//循环开始
4BED5DB0: LDR R0, [R6,#0x64]
........
4BED7BE4: CMP R2, #0
4BED7BE6: BNE loc_4BED7BF4 //R2=0跳出循环
........
4BED727C: LDR R0, [R2,#0x7C] //取 lpBuffer
4BED71E0: LDR R0, [R0]
4BED71E2: LDR R3, [R6,#0x48] //取异或值 CRC32效验值
4BED71E4: MOV R1, R3
4BED71D0: BICS R1, R0
4BED71D2: BICS R0, R3
4BED7114: ORRS R0, R1
4BED7116: LDR R1, [R2,#0x7C]
4BED71C0: STR R0, [R1] //[lpBuffer] = [lpBuffer] ^ CRC32效验值
......
4BED7958: STR R1, [R6,#0x3C]
//循环结束 代码如下:
for (i=0;i<0x100;i=i+4)
{
*(unsigned int*)(&lpBuffer[i]) = *(unsigned int*)(&lpBuffer[i]) ^ 0x39138e20;
}
..........
4BED478C: LDRB R1, [R1] //lpBuffer
//4BED4858: push {[0x4BED5A08]} -->1D68ECA3//4BED4864: POP {R2}
4BED48B8: ADDS R1, R1, R2
4BED47AC: LDR R2, [R3,#0x14]
4BED47AE: LDRB R2, [R2] //正确的表Result,这里直接扣出来
4BED47B0: SUBS R1, R1, R2
//4BED4174: push {[0x4BED5A0C]} -->E297135D//4BED4180: POP {R2}
4BED4182: ADDS R1, R1, R2
4BED4184: STR R1, [R6,#0x14] 返回值 = [[R6,#0x50],#0x18] - [[R6,#0x50],#0x14],相当于a和b是否相等
//4BED498C: push {[0x4BED59FC]} -->87898A83//4BED4998: POP {R3}//4BED3F74: CMP R1, R3//4BED3F76: BEQ unk_4BED3F84
4BED5708: MOVS R0, #1 //R0 = 1
4BED570A: STR R0, [R6,#0x38] //默认设置返回值 1
4BED570C: MOVS R0, #0 //R0 = 0
4BED56CC: LDR R1, [R6,#0x14]
4BED56CE: CMP R1, #0
4BED56D0: BNE unk_4BED56DC //判断 R1!=0 返回0 否则返回 1
4BED56DC:
4BED5694: STR R0, [R6,#0x38] //设置返回值 0
.......
4BED1820: ADDS R0, R6, #7
4BED1822: ADDS R0, #0xF1
4BED1E64: LDRB R0, [R0] //取返回值
..........
4BED1E74: SUBS R4, R7, #7
4BED1E76: SUBS R4, #5
4BED1E38: MOV SP, R4
4BED1E3A: POP {R4-R7,PC}
---------call end---------
到此整个流程基本走完;
先贴下的加解密算法:
unsigned char g_KeyTable[256] = {
0xF9, 0x6B, 0xE8, 0x1F, 0x2B, 0xBB, 0x3C, 0x60, 0x4A, 0xCC, 0x8B, 0x32, 0x22, 0x1E, 0x39, 0x73,
0x36, 0x7E, 0x4C, 0x40, 0x41, 0x38, 0x8F, 0x84, 0x37, 0x4F, 0xFA, 0x0B, 0x8D, 0x96, 0x9A, 0xA9,
0xEF, 0x30, 0x00, 0xCB, 0x99, 0x95, 0xAC, 0x34, 0x27, 0x5B, 0x54, 0x05, 0xD4, 0xC6, 0x28, 0x63,
0x18, 0xC2, 0x4B, 0x55, 0x89, 0x7A, 0x49, 0x7F, 0xAF, 0x4D, 0x87, 0x26, 0xE3, 0x47, 0x98, 0x79,
0xD3, 0x71, 0x14, 0x7D, 0xC1, 0x8C, 0x6D, 0xDA, 0xED, 0xA1, 0x0A, 0x3E, 0xF2, 0x76, 0xB2, 0x56,
0x7C, 0xEA, 0xBF, 0x90, 0x01, 0x5D, 0xBC, 0xDF, 0x0E, 0xA7, 0x6E, 0x8A, 0x67, 0x6A, 0xAE, 0xE6,
0xCF, 0xB0, 0x1A, 0xFD, 0x50, 0x66, 0xFF, 0xC5, 0x11, 0xEE, 0x97, 0x85, 0xDC, 0xA4, 0xCE, 0xEC,
0xC7, 0x15, 0x9E, 0x88, 0x0C, 0x94, 0xBE, 0xAA, 0xC0, 0xAD, 0x53, 0x2C, 0x81, 0x3F, 0x68, 0x80,
0x12, 0x48, 0x02, 0x83, 0x21, 0x1D, 0xB1, 0x16, 0xDD, 0x10, 0x25, 0xD2, 0xA0, 0x77, 0x74, 0x04,
0xD6, 0xD1, 0xAB, 0xE1, 0xD7, 0x86, 0x23, 0x51, 0x5C, 0x61, 0xE5, 0xBD, 0xE7, 0x5F, 0xF5, 0xD0,
0x58, 0x2A, 0x42, 0x62, 0xB6, 0x24, 0x92, 0x46, 0xC8, 0x4E, 0xCA, 0x64, 0xD9, 0x65, 0xBA, 0xB7,
0x9D, 0xA5, 0xE9, 0xDB, 0xB8, 0xF0, 0x7B, 0x3B, 0xB3, 0xF4, 0xDE, 0x70, 0x44, 0xA6, 0x6C, 0x78,
0x1B, 0x9F, 0xEB, 0x17, 0xE0, 0x9B, 0xFE, 0x13, 0x3D, 0x52, 0x09, 0x75, 0xCD, 0x03, 0x0D, 0xF7,
0x3A, 0xE4, 0xC9, 0xD8, 0x20, 0xF3, 0x31, 0x45, 0x82, 0xF6, 0x5A, 0xD5, 0x8E, 0xC3, 0x2E, 0x6F,
0xC4, 0xA2, 0x57, 0x08, 0xE2, 0x2F, 0x59, 0xF8, 0x72, 0x91, 0xB5, 0x43, 0x1C, 0x5E, 0x35, 0xFC,
0x06, 0x69, 0xA3, 0xB9, 0xFB, 0xA8, 0xB4, 0x93, 0x2D, 0x33, 0x9C, 0x19, 0x07, 0x0F, 0xF1, 0x29
};
unsigned char g_Result[256] = {
0x6D, 0xC4, 0xAD, 0x34, 0x7C, 0x64, 0x9C, 0x39, 0x17, 0x17, 0x26, 0xA7, 0xBA, 0x18, 0x28, 0xD2,
0xD5, 0xDD, 0x27, 0xBA, 0x86, 0x2A, 0x0E, 0x27, 0xC4, 0xEE, 0xC4, 0x31, 0xF7, 0x35, 0x62, 0x14,
0x6C, 0x41, 0x18, 0xB8, 0x77, 0xFA, 0x5C, 0xF0, 0xA4, 0x20, 0x25, 0x90, 0x16, 0x6C, 0x91, 0x43,
0x49, 0x08, 0xA4, 0x25, 0x8E, 0x84, 0xA1, 0x47, 0x02, 0x74, 0x65, 0xB6, 0xAF, 0xAE, 0x45, 0xD6,
0x6D, 0x2F, 0x53, 0x93, 0xEA, 0xF2, 0xD4, 0x88, 0xF3, 0xDB, 0x87, 0x38, 0xE0, 0xFC, 0x7E, 0x1C,
0x8E, 0x73, 0x82, 0x6D, 0x6A, 0x27, 0xE2, 0xE8, 0x64, 0x27, 0x1B, 0x97, 0x0D, 0x32, 0x05, 0xC9,
0xC6, 0x7E, 0xBF, 0x9F, 0xB8, 0xDB, 0xCC, 0x85, 0x36, 0xB9, 0xAB, 0xE5, 0xA7, 0xF9, 0x76, 0xFB,
0x0B, 0x91, 0x71, 0x16, 0x40, 0x49, 0x41, 0x81, 0x4C, 0xC9, 0x92, 0x47, 0x93, 0x3B, 0x52, 0x8C,
0xA5, 0xE1, 0x66, 0x3A, 0xB0, 0x33, 0x90, 0x86, 0xA5, 0x20, 0x9D, 0xBF, 0xF4, 0x71, 0x96, 0x2D,
0xAD, 0x52, 0x1D, 0x09, 0xCC, 0x92, 0xC1, 0xE3, 0x73, 0x06, 0xFB, 0x92, 0xA7, 0xF5, 0x38, 0xA7,
0x1A, 0xFE, 0x09, 0x37, 0x5B, 0xAB, 0xD9, 0x89, 0x96, 0xA2, 0x8A, 0x1A, 0xF6, 0xC9, 0x2B, 0xC6,
0x8E, 0x35, 0x07, 0x33, 0x87, 0xD1, 0xA7, 0xE3, 0x4C, 0x82, 0x4B, 0x89, 0x03, 0x1A, 0x8E, 0x7F,
0x7D, 0x85, 0xE4, 0xB5, 0xFB, 0xF9, 0x1E, 0xF6, 0xF7, 0xAC, 0x79, 0x62, 0x4B, 0xB4, 0x01, 0xC4,
0x99, 0x67, 0xF6, 0x7D, 0x2D, 0xF2, 0xA8, 0xA6, 0x6B, 0x1F, 0x67, 0xE4, 0x59, 0x15, 0xC9, 0xEF,
0x26, 0x18, 0xC4, 0x2C, 0xD6, 0x54, 0xFE, 0x33, 0xBE, 0xF3, 0x8C, 0xCF, 0xF8, 0x5B, 0x5F, 0x06,
0x2B, 0xC2, 0x18, 0xB6, 0x80, 0x1D, 0xBA, 0x93, 0xFF, 0x11, 0xE9, 0x2A, 0x5D, 0xA3, 0x95, 0x97
};
unsigned char crol(unsigned char c,unsigned char b)
{
unsigned char left=c<<b;
unsigned char right=c>>(sizeof(unsigned char)*8-b);
unsigned char temp=left|right;
return temp;
}
unsigned char cror(unsigned char c,unsigned char b)
{
unsigned char right =c>>b;
unsigned char left =c<<(sizeof(unsigned char)*8-b);
unsigned char temp=left|right;
return temp;
}
void Encode(unsigned char* lpString,unsigned char* lpBuffer,unsigned char* lpKeyTable)
{
unsigned char t,k = 0,sum;
int i,j;
unsigned char szKeyTable[256] = {NULL};
memcpy(szKeyTable,lpKeyTable,sizeof(szKeyTable));
for (i=0;i<0x100;i++)
{
lpBuffer[i] = cror(lpString[i%0xF],i&7);
}
for (i=0;i<0x100;i++)
{
if (i == 0xFF)
{
j = 0;
}
else
{
j = i+1;
}
t = szKeyTable[j];
k = (k + szKeyTable[j]) & 0xFF;
szKeyTable[j] = szKeyTable[k];
szKeyTable[k] = t;
sum = (szKeyTable[j] + szKeyTable[k]) & 0xFF;
t = lpBuffer[i];
t = crol(t,3);
t = cror((t ^ szKeyTable[sum]),i&7);
lpBuffer[i] = t;
}
for (i=0;i<0x100;i++)
{
lpBuffer[i] = crol((lpBuffer[i] ^ 0x29),i&7);
}
for (i=0;i<0x100;i=i+4)
{
*(unsigned int*)(&lpBuffer[i]) = *(unsigned int*)(&lpBuffer[i]) ^ 0x39138e20;
}
}
void Decode(unsigned char* lpString,unsigned char* lpBuffer,unsigned char* lpKeyTable)
{
unsigned char t,k = 0,sum;
int i,j;
unsigned char szKeyTable[256] = {NULL};
memcpy(szKeyTable,lpKeyTable,sizeof(szKeyTable));
for (i=0;i<0x100;i=i+4)
{
*(unsigned int*)(&lpBuffer[i]) = *(unsigned int*)(&lpBuffer[i]) ^ 0x39138e20;
}
for (i=0;i<0x100;i++)
{
lpBuffer[i] = cror(lpBuffer[i],i&7) ^ 0x29;
}
memcpy(g_Temp,lpBuffer,256);
for (i=0;i<0x100;i++)
{
if (i == 0xFF)
{
j = 0;
}
else
{
j = i+1;
}
t = szKeyTable[j];
k = (k + szKeyTable[j]) & 0xFF;
szKeyTable[j] = szKeyTable[k];
szKeyTable[k] = t;
sum = (szKeyTable[j] + szKeyTable[k]) & 0xFF;
t = lpBuffer[i]; //73
t = crol(t,i&7) ^ szKeyTable[sum]; //73 ^ 06
t = cror(t,3); //ae
lpBuffer[i] = t;
}
for (i=0;i<0xF;i++)
{
lpString[i%0xF] = crol(lpBuffer[i],i&7);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
unsigned char lpBuffer[256] = {NULL};
unsigned char lpString[16] = {NULL};
Decode(lpString,g_Result,g_KeyTable);
printf("座位号:%s\n",lpString);
}
座位号:L6^a9>z<81Li!*M
四,总结
1,指令恢复,编写idc脚本识别上面的三种乱序指令和一种变形BL,基本上能恢复全部的代码,关键点是对循环和循环跳出的识别,这里,循环我是人工肉眼识别,循环的跳出,我是用idc脚本,记录循环内每个相反跳,然后依次断点,然后跳出,继续idc跑下面代码;
2,CRC32效验值,也相当于反调试检测,由于效验了代码,下的断点会影响CRC32效验值,导致后面异或出的lpBuffer出问题,得到不正确的加解密结果;那问题来了,怎么取这个效验值,idc 脚本dump 出来,然后单独CRC32算出效验值 0x39138e20;
3,长度等于0xF的判断,不等于,下面的代码都得不到执行,无从分析;
4,r_debug->r_brk 反调试检测,这是个啥东西,可以百度一下,ida会修改调试的进程里的r_debug->r_brk函数的指令为非法指令,写程序注入进去会看到r_debug->r_brk:
没调试: 0x70 0x47 BX LR,
调试下: 0x10 0xDE 异常指令
所以,调试的时候,在调用这个函数,程序就中断退出了;
具体代码看恢复函数下的:复制r_debug--r_brk函数.txt、获取DT_DEBUG地址函数.txt、内存拷贝r_debug--r_brk函数.txt;
获取debug_r_brk代码如下:
void printf_r_debug_r_brk()
{
void * libc = dlopen("libdl.so",0);
LOGI("%x,%s",libc,libc);
if (libc)dlclose(libc);
unsigned int t = *(unsigned int*)((unsigned int)libc + 0x98);
LOGI("%x",t);
t = *(unsigned int*)((unsigned int)libc + 0xA4);
LOGI("%s",t);
t = *(unsigned int*)(t + 0x98);
while (1)
{
printf("%X,%X\n",*(unsigned int*)t,*(unsigned int*)(t+4));
if (*(unsigned int*)t == 0x15)
{
LOGI("-------%X",*(unsigned int*)(*(unsigned int*)(t+4) + 8));
for(int i=0;i < 16;i++)
{
LOGI("%02X ",*(unsigned char*)(*(unsigned int*)(*(unsigned int*)(t+4) + 8) + i - 1));
}
LOGI("-------");
}
if (*(unsigned int*)t == 0)break;
t = t + 8;
}
}
5,raise(0x5)和raise(0xB) 异常的作用,反调试是一部分,可以隐藏加密函数部分算法到这两个异常的接管函数里面,增加逆向难度;
具体代码看恢复函数下的: SIGTRAP异常接管函数.txt 和 SIGSEGV异常接管函数.txt ;
6,关于那个异或的V值 = 0x29 的来源,这个值,是用idc 脚本跑出来的,跑的时候需要修改CRC32效验值;调试的时候,会看到这个值会变,估计也是保护程序的以一种手段吧;具体怎么来没搞清楚,有研究出来的,请告知,谢谢!
7,附件里有idc脚本和恢复的一些函数,idc、脚本写得有点烂,勉强能用,基地址0x4BEBF000;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)