博客链接: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. windows上传ipa文件到苹果开发者中心的教程

    转: windows上传ipa文件到苹果开发者中心的教程 我们在苹果开发者中心上架ios app的时候,需要使用xcode或transporter先上传ipa文件到开发者中心. 但是假如我们只是H5开 ...

  2. [Java Tutorial学习分享]接口与继承

    目录 接口 概述 Java 中的接口 使用接口作为API 定义一个接口 The Interface Body 实现接口 使用接口作为类型 进化的接口 默认方法 扩展包含默认方法的接口 静态方法 接口总 ...

  3. PAT-1133(Splitting A Linked List)vector的应用+链表+思维

    Splitting A Linked List PAT-1133 本题一开始我是完全按照构建链表的数据结构来模拟的,后来发现可以完全使用两个vector来解决 一个重要的性质就是位置是相对不变的. # ...

  4. Azure Front Door(一)为基于.net core 开发的Azure App Service 提供流量转发

    一,引言 之前我们讲解到使用 Azure Traffic Manager.Azure LoadBalancer.Azure Application Gateway,作为项目的负载均衡器来分发流量,转发 ...

  5. 200-Java语言基础-Java编程入门-004 | Java分支与循环

    一.流程控制语句 可以控制程序的执行流程 在程序开发的过程之中一共会存在有三种程序逻辑:顺序结构.条件分支(选择)结构.循环结构. 顺序结构的定义,即:所有的程序将按照定义的代码从上往下.顺序依次执行 ...

  6. 【工具】 memtester内存压力测试工具

    作者:李春港 出处:https://www.cnblogs.com/lcgbk/p/14497838.html 目录 一.简介 二.Memtester安装 三.使用说明 四.测试示例 一.简介 mem ...

  7. Go ORM框架 - GORM 踩坑指南

    今天聊聊目前业界使用比较多的 ORM 框架:GORM.GORM 相关的文档原作者已经写得非常的详细,具体可以看这里,这一篇主要做一些 GORM 使用过程中关键功能的介绍,GORM 约定的一些配置信息说 ...

  8. 记一次 mysql主从复制安装配置 过程

    mysql主从复制安装配置 1.centos安装及准备 去centos官网下载相应source版本的镜像文件并在vmware中安装,安装中会遇到填写installation source,输入以下即可 ...

  9. Python内置函数作用及解析

    Python内置的函数及其用法.为了方便记忆,已经有很多开发者将这些内置函数进行了如下分类: 数学运算(7个)    类型转换(24个)    序列操作(8个)    对象操作(7个)    反射操作 ...

  10. python类属性

    类属性 类属性分为共有属性和私有属性. 私有属性的定义方法eg:__age(若无次定义则默认为公有属性) 类属性举例: class people: name = "china" _ ...