博客链接: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@1int nChkNumber; // lr@1signed int v5; // r11@1signed int v6; // r9@1int _lpUserNameBuffer; // r10@1void *hFileHandle; // r0@4void *_hFileHandle; // r4@4signed int result; // r0@5void *time; // r11@6unsigned int _nStrUserNameLenth; // r4@10int szIMEIBuffer; // r6@12int j; // r3@13int dwFirstIMEIBuffer; // r0@15MD5_CTX *lp_state; // r5@15unsigned int index; // r2@15unsigned int padlen; // r2@16int m; // r2@20int n; // r3@20char *lpRegUserBuff; // r5@22int k; // r4@22int v23; // r2@23int v24; // r3@23int _lpIMEIBuffer; // [sp+8h] [bp-4Ch]@1int _lpRegSnBuffer; // [sp+Ch] [bp-48h]@1int nStrUserNameLenth; // [sp+10h] [bp-44h]@1unsigned int nTime; // [sp+10h] [bp-44h]@12unsigned int nStrImeiLength; // [sp+14h] [bp-40h]@1int v30; // [sp+18h] [bp-3Ch]@1int v31; // [sp+1Ch] [bp-38h]@1int v32; // [sp+20h] [bp-34h]@1int v33; // [sp+24h] [bp-30h]@1int v34; // [sp+28h] [bp-2Ch]@1int v35; // [sp+2Ch] [bp-28h]@1int szFileNameBuffer; // [sp+30h] [bp-24h]@1int v37; // [sp+34h] [bp-20h]@1int v38; // [sp+38h] [bp-1Ch]@1int v39; // [sp+3Ch] [bp-18h]@1int v40; // [sp+40h] [bp-14h]@1int v41; // [sp+44h] [bp-10h]@1char lpTime[4]; // [sp+48h] [bp-Ch]@1int v43; // [sp+4Ch] [bp-8h]@1unsigned int t; // [sp+50h] [bp-4h]@6int szRegUserBuff; // [sp+54h] [bp+0h]@1MD5_CTX *state[4]; // [sp+154h] [bp+100h]@15int count[2]; // [sp+164h] [bp+110h]@15_DWORD szDecrypt_16[5]; // [sp+1ACh] [bp+158h]@15int v49; // [sp+1BCh] [bp+168h]@15int lpInt; // [sp+1C0h] [bp+16Ch]@1int v51; // [sp+1C4h] [bp+170h]@1int v52; // [sp+1C8h] [bp+174h]@1int v53; // [sp+1CCh] [bp+178h]@1int v54; // [sp+1D0h] [bp+17Ch]@1char bits; // [sp+1D4h] [bp+180h]@15int _nChkNumber; // [sp+1DCh] [bp+188h]@1i = 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/0x14szIMEIBuffer = 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获取系统时间tdwFirstIMEIBuffer = *(_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);// 格式化字符串%02XlpRegUserBuff += 2;}while ( k != 16 );                          // // ((void (__fastcall *)(_DWORD))time)(&t);    // 再次调用函数time获取系统时间tszRegUserBuff ^= 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开发工程师面试指南 功能介绍     给Android开发工程师的一份面试指南,包含面试题集与简历模板. 面试题集 面试题集里的答案大部分来源于我的博客,因此这个题集也相当于是我的博客 ...

  2. 安卓逆向工程师面试指南【干货】

    安卓逆向工程师岗位在近年来算是互联网IT行业的香饽饽之一,月薪动辄上万年薪动辄20万,让很多行业内兢兢业业拿一个五六千的外行人士很是羡慕.但是安卓逆向工程是专业技术要求很高的职业,外行人轻易抢不走你的 ...

  3. Android 开发工程师面试指南

    原文链接:https://github.com/GeniusVJR/LearningNotes <Android 开发工程师面试指南 LearningNotes >,作者是知乎Androi ...

  4. linux/android驱动工程师面试相关内容总结

    理论的东西不常用时就会慢慢的被遗忘,但是找工作就是一个如何让别人相信自己的过程,理论知识就是一个非常重要的途径. 一次次机会在错失,每次想找工作时,刷一下简历就去面试了,一次次因为理论被鄙视,也该长长 ...

  5. 《Android 开发工程师面试指南》

    <Android 开发工程师面试指南> <Android 开发工程师面试指南 LearningNotes >,作者是知乎Android开发工程师陶程,由梁观全贡献部分.大家可以 ...

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

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

  7. Android 逆向工程师要求

    职位描述: 负责移动应用安全测试和安全事件应急响应‍: 负责移动应用安全平台建设: 负责信息安全策略/流程的制定,安全培训/宣传及推广: 跟踪最新漏洞信息,进行业务产品的安全检查. 职位要求: 熟悉A ...

  8. 【Android 逆向】Android 逆向通用工具开发 ( PC 端工程分析 | 网络初始化操作 | PC 端工程核心业务逻辑 )

    文章目录 前言 一.网络初始化操作 二.PC 端工程核心业务逻辑 三.博客资源 前言 本篇博客重点分析 PC 端 hacktool 模块 ; 一.网络初始化操作 HackCommand::Prepar ...

  9. 3年Android开发工程师面试经验分享,先收藏了

    前言 程序员这个行业,日新月异,技术体系更新速度快,新技术新框架层出不穷,所有的技术都像是一个无底洞,当你学得越多就会发现不懂的越多,不懂的越多,需要学习的就更多. 因此,一旦选择了这个行业,就意味着 ...

最新文章

  1. java 反查域名_C段查询雏形之在Java中反查一个IP上的所有域名(旁站查询)
  2. Chapter 5 Blood Type——33
  3. 【Word】一些实用的小技巧
  4. js判断数据类型(如数组)及数组操作函数
  5. Nifi03 处理器
  6. c语言对称矩形的判定,江苏省扬州市仪征市2017年中考数学一模试卷(含解析).doc...
  7. 网站哪些功能可以提高用户体验度?
  8. AAAI最佳论文Informer 解读
  9. Sigar获取网卡流量信息
  10. python自动读取短信_自动化测试-自动获取手机短信验证码
  11. 【JavaWeb学习】CSS(选择器)
  12. 怎样选择步进电机和驱动器,计算方式
  13. HTTP请求头部+响应码
  14. java面试——进制转换
  15. iebook模板制作器与iebook模…
  16. matlab画极化码,极化码的matlab仿真(1)——参数设置
  17. WinForm实现通用的窗体基于屏幕中间或父窗体中间
  18. CnOpenData中国标准数据
  19. 计算机研究生就业方向之当老师(中小学)
  20. scrum回顾_[原创]如何开回顾会议(retrospective meeting)

热门文章

  1. Python爬虫六:字体反爬处理(猫眼+汽车之家)-2018.10
  2. 图片加载失败替代文字_替代艺术:为图像编写出色的描述性文字
  3. nova-compute
  4. 面向对象,数据库交互的Spring-boot(新手)
  5. 新浪微博JavaSDK开发笔记
  6. 解压技巧: iPhone自带的解压缩功能好强
  7. Manjaro 没有声音(伪输出)怎么办
  8. 升到初二后成绩“一落千丈”?你家孩子也有这个困扰么?
  9. 舌尖上的中国第一季整理笔记
  10. 软件单元测试要点的一些理解