博客链接:http://blog.csdn.net/qq1084283172/article/details/52133172
一、简介
这个题目是别人面试UC优视集团Android逆向工程师一职位的面试题,相比较前面的面试题1,增加了一些难度。
二、题目分析
1.使用JEB程序对UC-crackme-2.apk进行反编译分析,函数clacSnFuntion就是对用户输入的注册码进行校验的。
2.校验用户输入的用户名和注册码的函数clacSnFuntion是在Native层实现的,具体的实现在libclacSn.so库中。
3.在Native层,对注册码校验函数clacSnFuntion的实现进行分析,在IDA静态分析中发现Java_com_ucweb_crackme140522_MainActivity_clacSnFuntion函数中校验用户的注册码的具体函数CheckRegisterCode的代码被加密了是程序动态运行时在JNI_OnLoad函数中对该函数的代码进行解密的。
4.在JNI_OnLoad函数中,对加密的校验函数CheckRegisterCode的代码进行解密的过程,首先获取动态加载库"libclacSn.so"的内存映射地址,修改模块 "libclacSn.so"的内存保护属性,获取system/lib/libc.so库文件中的导出函数ptrace的调用地址对程序进行反调试(需要Nop调用),然后在"libclacSn.so"库文件的内存映射区中进行函数CheckRegisterCode的特征码的匹配寻找进行函数加密代码的定位,对用户注册码校验函数CheckRegisterCode的代码进行解密。
5.过掉获取system/lib/libc.so库文件中的导出函数ptrace的调用地址对程序进行反调试的方法是将函数ptrace的调用Nop调用即可。
6.校验函数CheckRegisterCode的代码解密函数DecryptDataOfAddressOffset_20140522的具体解密的实现。
7. 动态调试分析代码解压后的校验函数CheckRegisterCode的代码逻辑,需要代码解密的函数CheckRegisterCode的实际地址为A9B891B4。
8.来分析一下CheckRegisterCode函数的实现,函数CheckRegisterCode将获取到的用户手机DeviceId进行MD5加密算法处理然后和用户输入的用户名称进行计算得到最终用户需要输入的注册码,在函数memcmp处下断点即函数memcmp的第1个参数就是需要输入的注册码。
7.MD5算法的在ARM汇编下的典型特征:
在没有算法特征函数以及符号的情况下MD5函数的还是有着典型的特征的。
8. CheckRegisterCode函数的代码的还原。
signed int __fastcall CheckRegisterCode_A9B891B4(int lpIMEIBuffer, int lpUserNameBuffer, int lpRegSnBuffer)
{
int i; // r4@1
int nChkNumber; // lr@1
signed int v5; // r11@1
signed int v6; // r9@1
int _lpUserNameBuffer; // r10@1
void *hFileHandle; // r0@4
void *_hFileHandle; // r4@4
signed int result; // r0@5
void *time; // r11@6
unsigned int _nStrUserNameLenth; // r4@10
int szIMEIBuffer; // r6@12
int j; // r3@13
int dwFirstIMEIBuffer; // r0@15
MD5_CTX *lp_state; // r5@15
unsigned int index; // r2@15
unsigned int padlen; // r2@16
int m; // r2@20
int n; // r3@20
char *lpRegUserBuff; // r5@22
int k; // r4@22
int v23; // r2@23
int v24; // r3@23
int _lpIMEIBuffer; // [sp+8h] [bp-4Ch]@1
int _lpRegSnBuffer; // [sp+Ch] [bp-48h]@1
int nStrUserNameLenth; // [sp+10h] [bp-44h]@1
unsigned int nTime; // [sp+10h] [bp-44h]@12
unsigned int nStrImeiLength; // [sp+14h] [bp-40h]@1
int v30; // [sp+18h] [bp-3Ch]@1
int v31; // [sp+1Ch] [bp-38h]@1
int v32; // [sp+20h] [bp-34h]@1
int v33; // [sp+24h] [bp-30h]@1
int v34; // [sp+28h] [bp-2Ch]@1
int v35; // [sp+2Ch] [bp-28h]@1
int szFileNameBuffer; // [sp+30h] [bp-24h]@1
int v37; // [sp+34h] [bp-20h]@1
int v38; // [sp+38h] [bp-1Ch]@1
int v39; // [sp+3Ch] [bp-18h]@1
int v40; // [sp+40h] [bp-14h]@1
int v41; // [sp+44h] [bp-10h]@1
char lpTime[4]; // [sp+48h] [bp-Ch]@1
int v43; // [sp+4Ch] [bp-8h]@1
unsigned int t; // [sp+50h] [bp-4h]@6
int szRegUserBuff; // [sp+54h] [bp+0h]@1
MD5_CTX *state[4]; // [sp+154h] [bp+100h]@15
int count[2]; // [sp+164h] [bp+110h]@15
_DWORD szDecrypt_16[5]; // [sp+1ACh] [bp+158h]@15
int v49; // [sp+1BCh] [bp+168h]@15
int lpInt; // [sp+1C0h] [bp+16Ch]@1
int v51; // [sp+1C4h] [bp+170h]@1
int v52; // [sp+1C8h] [bp+174h]@1
int v53; // [sp+1CCh] [bp+178h]@1
int v54; // [sp+1D0h] [bp+17Ch]@1
char bits; // [sp+1D4h] [bp+180h]@15
int _nChkNumber; // [sp+1DCh] [bp+188h]@1
i = 0;
nChkNumber = (*_stack_chk_guard)[0];
v35 = 0;
v41 = 0;
v31 = 0x1F314341;
v37 = 0x103C2233;
v32 = 0x20014131;
v38 = 0xF61283B;
v33 = 0x50423331;
v39 = 0x1320363B;
v34 = 0x11520E;
v5 = 0x40081311;
v6 = 0x3371601E;
_lpIMEIBuffer = lpIMEIBuffer;
_lpUserNameBuffer = lpUserNameBuffer;
_lpRegSnBuffer = lpRegSnBuffer;
_nChkNumber = nChkNumber;
v40 = 0x5E2120;
*(_DWORD *)lpTime = 0x503C0052;
szFileNameBuffer = 0x40081311;
v30 = 0x3371601E;
v43 = 0;
memset_0(&szRegUserBuff, 0, 0x100); // 保存用户输入的注册码
v51 = 0;
v52 = 0;
v53 = 0;
v54 = 0;
lpInt = 0;
nStrImeiLength = Strlen(_lpIMEIBuffer); // 获取设备机器码的字符串长度
nStrUserNameLenth = Strlen(_lpUserNameBuffer);// 获取用户输入用户名的长度
//
while ( 1 ) // 解密需要加载的库文件/system/lib/libc.so
{
*(int *)((char *)&szFileNameBuffer + i) = v6 + v5;
i += 4;
if ( i == 24 )
break;
v5 = *(int *)((char *)&szFileNameBuffer + i);
v6 = *(int *)((char *)&v30 + i);
} // =========================================
//
*(_DWORD *)lpTime = 'emit';
hFileHandle = (void *)dlopen(&szFileNameBuffer, 0);// 加载系统库文件/system/lib/libc.so
_hFileHandle = hFileHandle;
if ( hFileHandle )
{
time = dlsym(hFileHandle, lpTime); // 获取库函数time的调用地址
dlclose(_hFileHandle);
((void (__fastcall *)(unsigned int *))time)(&t);// 调用函数time获取系统时间
//
if ( Strlen(_lpRegSnBuffer) != 0x10 ) // 注册码必须是16位
goto Jmp_FalseLenth; //
//
_nStrUserNameLenth = nStrUserNameLenth;
if ( nStrUserNameLenth >= (signed int)nStrImeiLength )// 用户输入的用户名的长度大于设备机器码的长度的情况
_nStrUserNameLenth = nStrImeiLength; // 用户名的长度取设备机器码的长度
//
nTime = t / 0x14; // 系统时间t/0x14
szIMEIBuffer = j_dlmalloc(nStrImeiLength + 1);// 为保存设备机器码分配内存
memset_0(szIMEIBuffer, 0, nStrImeiLength + 1);// 缓冲区清零
((void (__fastcall *)(unsigned int *))time)(&t);// 再次调用函数time获取系统时间t
*(_DWORD *)szIMEIBuffer ^= (signed int)t / 0x14 ^ nTime;
strcpy_0(szIMEIBuffer, _lpIMEIBuffer);
if ( (signed int)_nStrUserNameLenth > 0 )
{
j = 0;
do
{
*(_BYTE *)(szIMEIBuffer + j) ^= *(_BYTE *)(_lpUserNameBuffer + j);
++j;
}
while ( j != _nStrUserNameLenth );
}
((void (__fastcall *)(_DWORD))time)(&t); // 再次调用函数time获取系统时间t
dwFirstIMEIBuffer = *(_DWORD *)szIMEIBuffer;// 取设备机器码的首DWORD数据
szDecrypt_16[1] = 0;
szDecrypt_16[2] = 0;
szDecrypt_16[3] = 0;
*(_DWORD *)szIMEIBuffer = dwFirstIMEIBuffer ^ (signed int)t / 0x14 ^ nTime;
v49 = 0; //
//
lp_state = (MD5_CTX *)state; // ============================================
state[0] = (MD5_CTX *)0x67452301;
state[1] = (MD5_CTX *)0xEFCDAB89;
state[2] = (MD5_CTX *)0x98BADCFE;
count[0] = 0;
state[3] = (MD5_CTX *)0x10325476;
szDecrypt_16[0] = 0; // 保存szIMEIBuffer经过MD5加密后的结果
count[1] = 0; // MD5算法的初始化,调用MD5Init函数
// ============================================
MD5Update_A9B88B78((MD5_CTX *)state, szIMEIBuffer, nStrImeiLength);// 调用MD5的MD5算法的Update函数,szIMEIBuffer保存原结果
// ============================================
MD5Encode_A9B88C64((int)&bits, (int)count, 8u);
index = ((unsigned int)count[0] >> 3) & 0x3F;
padlen = index > 0x37 ? 120 - index : 56 - index;
MD5Update_A9B88B78((MD5_CTX *)state, 0xA9B8CF44, padlen);
MD5Update_A9B88B78((MD5_CTX *)state, (int)&bits, 8u);
MD5Encode_A9B88C64((int)szDecrypt_16, (int)state, 16u);// 调用MD5算法的MD5Final函数,szDecrypt保存MD5加密后的结果
// ============================================
do
{
LOBYTE(lp_state->count[0]) = 0;
lp_state = (MD5_CTX *)((char *)lp_state + 1);
}
while ( (_DWORD *)lp_state != szDecrypt_16 );//
//
m = 16;
n = 0;
do
{
*((_BYTE *)&lpInt + n) = m ^ *((_BYTE *)szDecrypt_16 + n);
++n;
m = (m + 1) & 0xFF;
}
while ( n != 16 ); //
//
lpRegUserBuff = (char *)&szRegUserBuff; // 用户输入的用户名
k = 0;
do
{
v23 = *((_BYTE *)&lpInt + k);
v24 = k++ + 16;
j_sprintf(lpRegUserBuff, (const char *)dword_A9B8AC58, v23 ^ v24);// 格式化字符串%02X
lpRegUserBuff += 2;
}
while ( k != 16 ); //
//
((void (__fastcall *)(_DWORD))time)(&t); // 再次调用函数time获取系统时间t
szRegUserBuff ^= nTime ^ (signed int)t / 0x14;// 生成用户输入的注册码**********************
free(szIMEIBuffer); //
//
if ( !memcmp_0((int)&szRegUserBuff, _lpRegSnBuffer, 16) )// 对16位用户输入的注册码进行校验
result = 1; // 返回值为1,此种情况说明,用户输入的注册码正确
else
Jmp_FalseLenth:
result = 0;
}
else
{
result = -1;
}
if ( _nChkNumber != (*_stack_chk_guard)[0] )
j___stack_chk_fail_0(result);
return result;
}
//说明 用户需要输入的注册码是有用户的手机的设备ID和用户输入的用户名称经过算法生成的。
9.MD5算法的C语言实现代码。
http://blog.chinaunix.net/uid-24118190-id-4372129.html
由于电脑分辨率的问题导致截图不清楚,具体的详细清楚的分析过程报告我已经放在附件里,下载地址为:http://download.csdn.net/detail/qq1084283172/9596495