博客链接: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

UC-Android逆向工程师面试第2题分析的更多相关文章

  1. Android逆向工程师的黑科技

    你们发现了吗?Android逆向.安全方面的工程师真的越来越"稀有"了. 以腾讯.美团.百度为代表的大厂们,在某招聘网站上居然薪酬高达30-60k. 现在移动端市场越来越火热,AP ...

  2. UC-Android逆向工程师 面试题1的分析

    1.简介 这个题目是一位吾爱破解的坛友在面试UC的Android逆向工程事时,遇到的题目.此题不难,与阿里移动去年移动安全比赛的题目差不多,题目的验证方式也是查表对比,并且这个表的数据是放在文件中的. ...

  3. Android面试一天一题(1Day)

    写在前面 该博客思路源于在简书看到goeasyway博主写的Android面试一天一题系列,无copy之意,仅为让自己总结知识点,成长一点点.先感谢各位大神的无私分享~! 关于题目,大部分则出自And ...

  4. 大厂0距离:网易 Linux 运维工程师面试真题,内含答案

    作为 Linux 运维工程师,进入大公司是开启职业新起点的关键,今天马哥 linux 运维及云计算智囊团的小伙伴特别分享了其在网易面试 Linux 运维及云计算工程师的题目和经历,希望对广大 Linu ...

  5. 2021大厂Android面试高频100题最新汇总(附答案详解)

    前言 现在越来越多的人应聘工作时都得先刷个几十百来道题,不刷题感觉都过不了面试. 无论是前后端.移动开发,好像都得刷题,这么多人通过刷题过了面试,说明刷题对于找工作还是有帮助的. 不过这其中有一个问题 ...

  6. .NET工程师面试宝典

    .Net工程师面试笔试宝典 传智播客.Net培训班内部资料 这套面试笔试宝典是传智播客在多年的教学和学生就业指导过程中积累下来的宝贵资料,大部分来自于学员从面试现场带过来的真实笔试面试题,覆盖了主流的 ...

  7. .Net工程师面试笔试宝典

    .Net工程师面试笔试宝典 传智播客.Net培训班内部资料 http://net.itcast.cn 这套面试笔试宝典是传智播客在多年的教学和学生就业指导过程中积累下来的宝贵资料,大部分来自于学员从面 ...

  8. web前端工程师面试技巧 常见问题解答

    web前端工程师面试技巧 常见问题解答 每年的春招是各企业需求人才的黄金时期,不少的前端大牛或者前端新手在面试时候不知道怎么来回答面试官的问题,下面来看下我转载的这篇文章吧,希望对从事前端工作的你有所 ...

  9. [转]Android逆向之动态调试总结

    一.在SO中关键函数上下断点 刚学逆向调试时.大多都满足于在SO中某关键函数上下断点.然后通过操作应用程序,去触发这个断点,然后进行调试 详细的步骤可以参见非虫大大的<Android软件安全与逆 ...

随机推荐

  1. WIFI6 基本知识(一)

    什么是WI-FI6(802.11ax) Wi-Fi 6 是下一代 802.11ax 标准的简称.随着 Wi-Fi 标准的演进,WFA 为了便于 Wi-Fi 用户和设备厂商轻松了解其设备连接或支持的 W ...

  2. dubbo-zookeeper quick start

    目录 dubbo快速开始 服务提供者(Service provider) 定义服务接口(Defining service interfaces) 在服务提供方实现接口(Implement interf ...

  3. 解决appium点击软键盘上的搜索按钮

    在执行appium自动化测试的时候,需要点击软件盘上的搜索按钮. 具体操作步骤如下: 前提:需要事先安装搜狗输入法 1.唤醒软件盘,可以封装到一个类里,用到的时候随时调用. import os#调起s ...

  4. 剑指 Offer 67. 把字符串转换成整数 + 字符串

    剑指 Offer 67. 把字符串转换成整数 Offer_67 题目描述 题解分析 java代码 package com.walegarrett.offer; /** * @Author WaleGa ...

  5. $nextTick解决Vue组件卸载在加载合并的问题

    情况是这样的,父子组件都是复选框,点击父组件的复选框,子组件的复选框要显示并全选,取消复选框,子组件隐藏.子组件显隐我用的 v-if ,使用created钩子函数来使子组件处于全选状态. 但是出现的问 ...

  6. sitemesh简单介绍

    SiteMesh 是一个网页布局和修饰的框架,利用它可以将网页的内容和页面结构分离,以达到页面结构共享的目的. Sitemesh是由一个基于Web页面布局.装饰以及与现存Web应用整合的框架. 它能帮 ...

  7. python多线程参考文章

    1. https://www.jianshu.com/p/c93e630d8089 2.https://www.runoob.com/python/python-multithreading.html ...

  8. 常用开发库 - MapStruct工具库详解

    常用开发库 - MapStruct工具库详解 MapStruct是一款非常实用Java工具,主要用于解决对象之间的拷贝问题,比如PO/DTO/VO/QueryParam之间的转换问题.区别于BeanU ...

  9. 《C++反汇编与逆向分析技术揭秘》--算术运算和赋值

    一.加法 1.Debug下: 14: int nVarOne0 = 1 + 5 - 3 * 6;//编译时计算得到结果 00C0550E C7 45 F8 F4 FF FF FF mov dword ...

  10. Android 之 TableLayout 布局详解

    TableLayout简介 •简介 Tablelayout 类以行和列的形式对控件进行管理,每一行为一个 TableRow 对象,或一个 View 控件. 当为 TableRow 对象时,可在 Tab ...