Android逆向分析(一) - 反编译看看手Q口令红包的实现原理
原文:http://blog.zhaiyifan.cn/2016/02/09/android-reverse-1/
本系列文章是《Android软件安全与逆向分析》的实践笔记(一些工具的版本和用法已经不同了),同时笔者希望系统性地记录一下逆向方面的文字和想法。
逆向工程(又称反向工程),是一种技术过程,即对一项目标产品进行逆向分析及研究,从而演绎并得出该产品的处理流程、组织结构、功能性能规格等设计要素,以制作出功能相近,但又不完全一样的产品。逆向工程源于商业及军事领域中的硬件分析。其主要目的是,在不能轻易获得必要的生产信息下,直接从成品的分析,推导出产品的设计原理。
逆向工程可能会被误认为是对知识产权的严重侵害,但是在实际应用上,反而可能会保护知识产权所有者。例如在集成电路领域,如果怀疑某公司侵犯知识产权,可以用逆向工程技术来寻找证据。 —— [ 维基百科 ]
第一篇作为开始,先讲讲简单的反编译。
因为没带kvm回来,mbpr屏幕太小,所以下文环境为windows。
反编译
让我们从实战开始,先实践一下怎么去反编译一个apk,看看某些功能的实现,甚至打一个包吧。毕竟没有实践的原理都是耍流氓。
准备
工具
- Apktool
- jadx
安装包
- 手机QQ 6.2.3 (目标就设定为看看口令红包是怎么做的吧)
Apktool的使用
首先确保你安装了java 7或以上,并能直接在命令行调用java
。
- 下载 windows用wrapper脚本 (mac使用这个)。
- 下载最新的apktool。
- 重命名上面下载的apktool jar文件为
apktool.jar
。 - 把apktool.bat和apktool.jar放在同一个目录下,并加入PATH环境变量。
- 现在你可以直接通过命令行调用
apktool
并查看使用方式了。
Apktool v2.0.3 - a tool for reengineering Android apk files
with smali v2.1.0 and baksmali v2.1.0usage: apktool-advance,--advanced prints advance information.-version,--version prints the version then exitsusage: apktool if|install-framework [options] <framework.apk>-p,--frame-path <dir> Stores framework files into <dir>.-t,--tag <tag> Tag frameworks using <tag>.usage: apktool d[ecode] [options] <file_apk>-f,--force Force delete destination directory.-o,--output <dir> The name of folder that gets written. Default is apk.out-p,--frame-path <dir> Uses framework files located in <dir>.-r,--no-res Do not decode resources.-s,--no-src Do not decode sources.-t,--frame-tag <tag> Uses framework files tagged by <tag>.usage: apktool b[uild] [options] <app_path>-f,--force-all Skip changes detection and build all files.-o,--output <dir> The name of apk that gets written. Default is dist/name.apk-p,--frame-path <dir> Uses framework files located in <dir>.
jadx的使用
- 下载jadx。
- 运行gradlew dist编译。
jadx\jadx-gui\build\install\jadx-gui\bin
下有可运行的guijadx\jadx-cli\build\install\jadx\bin
是命令行程序- 可以都加入PATH环境变量,以便直接命令行调用。
分析APK文件
First Try
虽然我们可以用jadx直接打开apk傻瓜式地去查看源代码,但是为了更理解反编译的过程和工作原理,以便以后在碰到一些问题(比如加壳)的时候可以自己解决,这里我们先装逼一下,使用Apktool去进行分析。
D:\dev\reverse>apktool d -o qq mobileqq_android_6.2.3.apk
I: Using Apktool 2.0.3 on mobileqq_android_6.2.3.apk
I: Loading resource table...
Exception in thread "main" brut.androlib.AndrolibException: Multiple res specs: attr/nameat brut.androlib.res.data.ResTypeSpec.addResSpec(ResTypeSpec.java:78)at brut.androlib.res.decoder.ARSCDecoder.readEntry(ARSCDecoder.java:248)at brut.androlib.res.decoder.ARSCDecoder.readTableType(ARSCDecoder.java:212)at brut.androlib.res.decoder.ARSCDecoder.readTableTypeSpec(ARSCDecoder.java:154)at brut.androlib.res.decoder.ARSCDecoder.readTablePackage(ARSCDecoder.java:116)at brut.androlib.res.decoder.ARSCDecoder.readTableHeader(ARSCDecoder.java:78)at brut.androlib.res.decoder.ARSCDecoder.decode(ARSCDecoder.java:47)at brut.androlib.res.AndrolibResources.getResPackagesFromApk(AndrolibResources.java:544)at brut.androlib.res.AndrolibResources.loadMainPkg(AndrolibResources.java:63)at brut.androlib.res.AndrolibResources.getResTable(AndrolibResources.java:55)at brut.androlib.Androlib.getResTable(Androlib.java:66)at brut.androlib.ApkDecoder.setTargetSdkVersion(ApkDecoder.java:198)at brut.androlib.ApkDecoder.decode(ApkDecoder.java:96)at brut.apktool.Main.cmdDecode(Main.java:165)at brut.apktool.Main.main(Main.java:81)
竟然报错了,Multiple res specs: attr/name,在网上找了找资料,应该是腾讯利用Apktool的bug去进行了加壳,除了添加同名id外还做了若干加固,好,你狠,我们下篇文章针对腾讯的壳来分析并修改Apktool,这次先用jadx来试试。
Second Try
如果直接用jadx-gui打开QQ的apk,你会发现,卡死了。不错,就是卡死了,因为太大了…
我们打开jadx-gui文件(其实就是个启动的script),加上:
set JAVA_OPTS=-server -Xms1024m -Xmx8192m -XX:PermSize=256m -XX:MaxPermSize=1024m
就跟我们加速as/idea差不多,这样就能顺利地打开了(可能会需要比较久的时间)。
字符串大法
为了找到我们的目标,红包,我们首先尝试用字符串搜索大法:在Resources -> resources.arsc -> res -> values -> strings.xml找到口令红包对应的
<string name="qb_hbdetail_command_word">口令红包</string>
然后Crtl+Shift+F进行Text Search,结果…没找到。
我们再使用资源id大法,直接在resources.arsc找到
0x7f0a0e5a (2131365466) = string.qb_hbdetail_command_word: 口令红包
再搜,好,你狠。。。还是没有。是在下输了。
类/函数名大法
我们再祭出第二大杀器,类/函数/变量名大法搜索大法。
通常类名符合的范围更小,所以先只使用Class。
试试看红包的英语:RedPacket(类名命名所以R和P大写)
OK,我们找到了十几条,开始逐一排查,第一条RedPacketInfo
点进去一看就是个包含了各种field的ui用的vo类,跳过,再看下一个,从包名com.tencent.mobileqq.data
看上去,似乎有戏,QQWalletRedPacketMsg
:
package com.tencent.mobileqq.data;import android.text.TextUtils;
import com.tencent.mobileqq.hotpatch.NotVerifyClass;
import cooperation.qzone.util.WiFiDash;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import tencent.im.msg.im_msg_body.QQWalletAioBody;/* compiled from: ProGuard */
public class QQWalletRedPacketMsg {public String authkey;private int channelId;public int conftype;public QQWalletTransferMsgElem elem;public String envelopeName;public int envelopeid;public boolean isOpened;public int msgFrom;public String redPacketId;public int redtype;private int resend;public int templateId;...串行化、读写、构建方法等,可以无视。
从field名来看,这里还是比较可疑的,猜测redtype
是不是描述红包类型的。
我们再次使用关键词redtype
进行搜索,这次选择Code,只进行代码内搜索,结果却发现貌似不对,找到相关的字符串是”查看详情”,貌似是描述红包领取状态的。
不放弃,继续抓住QQWalletRedPacketMsg
这个类进行搜索,看看是不是有外面包着这个类的Class,搜索QQWalletRedPacketMsg,范围使用Field,排除掉类本身外,只有唯一的结果:MessageForQQWalletMsg
:
public class MessageForQQWalletMsg extends ChatMessage {// 哦哦?COMMAND_REDPACKET?口令红包public static final int MSG_TYPE_COMMAND_REDPACKET = 6;public static final int MSG_TYPE_COMMON_REDPACKET = 2;public static final int MSG_TYPE_COMMON_THEME_REDPACKET = 4;public static final int MSG_TYPE_INDIVIDUAL_REDPACKET = 2001;public static final int MSG_TYPE_LUCY_REDPACKET = 3;public static final int MSG_TYPE_LUCY_THEME_REDPACKET = 5;public static final int MSG_TYPE_PUBLIC_ACCOUNT_REDPACKET = 2002;public static final int MSG_TYPE_TRANSFER = 1;...
我们找到了一个常量字段,目测就是这个描述了是否是口令红包了。在该类搜索此字段还找到
public static boolean isCommandRedPacketMsg(MessageRecord messageRecord) {if (messageRecord != null && (messageRecord instanceof MessageForQQWalletMsg) && ((MessageForQQWalletMsg) messageRecord).messageType == MSG_TYPE_COMMAND_REDPACKET) {return true;}return false;
}
果然,我们再接着分别查找MSG_TYPE_COMMAND_REDPACKET
和isCommandRedPacketMsg
,结果只在TroopMessageManager
里面找到了一段没成功反编译的代码中对方法isCommandRedPacketMsg
的引用:
L_0x0100:r2 = com.tencent.mobileqq.data.MessageForQQWalletMsg.isCommandRedPacketMsg(r25);if (r2 == 0) goto L_0x011e;
这里如果是口令红包会继续走下去,而如果不是则会跳到L_0x011e。
而从类的名字来看,TroopMessageManager
应该是指群消息管理者
,应该没错,毕竟红包也是群消息的一种。
于是我们只能耐心地看下去这段神奇的充满goto的代码。晕着看完后大概看到就是各种逻辑判断和调用MsgProxyUtils.java
去处理消息处理逻辑和缓存。然后就没了…好,你屌,是在下输了。我再试试别的。
常量大法
常量大法其实也可以算是字符串搜索的一种,只是不去搜索xml里的,而是使用中文转化为unicode后的字符串去进行查找。自行搜索Unicode编码转化可以找到online convertor。
口令红包对应的是”\u53e3\u4ee4\u7ea2\u5305”:
找到2个类共3处代码引用。
最后那个类的起名有点耐人寻味,PasswdRedBagManager
,密码红包管理器,有点意思:
public void b(String str) {((TroopTipsMsgMgr) this.f2203a.getManager(80)).a(str, "\u533f\u540d\u4e0d\u80fd\u62a2\u53e3\u4ee4\u7ea2\u5305\u54e6", NetConnInfoCenter.getServerTime(), BaseConstants.DEFAULT_QUICK_HEARTBEAT_TIMEOUT, f);
}
这里”\u533f\u540d\u4e0d\u80fd\u62a2\u53e3\u4ee4\u7ea2\u5305\u54e6”转换成中文后是”匿名不能抢口令红包哦”,原来还有这种逻辑,产品经理你真是够了。
这里我们重新从该类的上面看下来,大致扫一扫,发现onDestroy下面有一个方法打的log很神奇:
public long[] m883a(SessionInfo sessionInfo, String str) {if (QLog.isColorLevel()) {QLog.d(f2197a, (int) h, "openPasswdRedBagByPassword, passwd = " + str);}long[] jArr = new long[]{0, 0};if (sessionInfo == null) {return jArr;}if (TextUtils.isEmpty(str)) {return jArr;}c();List<String> list = (List) this.f2206a.get(str);if (list == null || list.isEmpty()) {return jArr;}PasswdRedBagInfo passwdRedBagInfo;String str2 = a(sessionInfo.a) + "_" + sessionInfo.f1757a;for (String str3 : list) {HashMap hashMap = (HashMap) this.f2209b.get(str3);if (hashMap != null) {passwdRedBagInfo = (PasswdRedBagInfo) hashMap.get(str2);if (!(passwdRedBagInfo == null || a(str3))) {jArr[g] = passwdRedBagInfo.a.uint64_creator_uin.get();if (!b(str3)) {if (!c(str3)) {hashMap.put(str2, passwdRedBagInfo);jArr[f] = 1;break;}jArr[f] = 3;} else {jArr[f] = 2;}}}}passwdRedBagInfo = null;if (passwdRedBagInfo == null) {return jArr;}b(sessionInfo.a, sessionInfo.f1757a, passwdRedBagInfo.a.string_redbag_id.get().toStringUtf8());a(sessionInfo, passwdRedBagInfo);return jArr;
}
isColorLevel目测是某种debug用的tag,可能某些环境下部分用户会打开,而从log结合我们平时打log习惯来看,这个方法应该就叫openPasswdRedBagByPassword
了,第二个参数就是password
。终于找到了。看一下逻辑大致是从外面load进来所有红包信息到本类的各种hashmap和list(有一个tag,只会加载第一次,本类多个方法都会调用这个方法),然后根据password从里面找到对应passwdRedBagInfo
,设置result tag,然后调用了
b(sessionInfo.a, sessionInfo.f1757a, passwdRedBagInfo.a.string_redbag_id.get().toStringUtf8());
a(sessionInfo, passwdRedBagInfo);
我们先不急看这两个方法是做什么的。再往下看下一个方法,直接就有:
public long[] b(SessionInfo sessionInfo, String str) {if (QLog.isColorLevel()) {QLog.d(f2197a, (int) h, "openPasswdRedBagById, id = " + str);}
openPasswdRedBagById
用id打开红包,猜测该id就是我们最早看到的结构里的redPacketId
字段。
而该方法同样调用了
b(sessionInfo.a, sessionInfo.f1757a, str);
a(sessionInfo, passwdRedBagInfo);
看看这两个方法:
public void a(SessionInfo sessionInfo, PasswdRedBagInfo passwdRedBagInfo) {if (sessionInfo != null && passwdRedBagInfo != null) {Object obj = (sessionInfo.a == 0 || sessionInfo.a == h || sessionInfo.a == Action.ACTION_REGISTNEWACCOUNT_COMMITSMS || sessionInfo.a == Action.ACTION_LOGIN) ? g : null;String str = sessionInfo.f1757a;String valueOf = String.valueOf(passwdRedBagInfo.a.uint64_creator_uin.get());if (obj != null) {str = valueOf.equals(this.f2213d) ? sessionInfo.f1757a : this.f2213d;}JSONObject a = QQWalletMsgItemBuilder.a(this.f2203a, sessionInfo, passwdRedBagInfo.a.string_redbag_id.get().toStringUtf8(), passwdRedBagInfo.a.string_authkey.get().toStringUtf8(), str, "appid#1344242394|bargainor_id#1000030201|channel#msg", "graphb", null);Bundle bundle = new Bundle();bundle.putString("json", a.toString());bundle.putString("callbackSn", jbi.a);Intent intent = new Intent(this.f2200a, PayBridgeActivity.class);intent.putExtras(bundle);intent.addFlags(268435456);intent.putExtra("pay_requestcode", 5);this.f2200a.startActivity(intent);}
}public void b(int i, String str, String str2) {if (!TextUtils.isEmpty(str2)) {HashMap hashMap = (HashMap) this.f2209b.get(str2);if (hashMap != null) {PasswdRedBagInfo passwdRedBagInfo = (PasswdRedBagInfo) hashMap.get(a(i) + "_" + str);if (passwdRedBagInfo != null && !passwdRedBagInfo.f4810a) {passwdRedBagInfo.f4810a = true;ThreadManager.a(new kmr(this, str2), h, null, true);}}}
}
发现第一个方法似乎就直接发请求了,看来只要调用到这里,就是可以领红包了。那最初又是如何来这里的呢?我们搜索对PasswdRedBagManager
内这两个方法的引用找到BaseChatPie.java
:
...
public PasswdRedBagManager f25190a;
...
public class EnterForSend implements OnKeyListener, OnEditorActionListener {...// 这里从方法名判断是每次输入后public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {if (i != BaseChatPie.dr) {return false;}String obj = this.a.f25220a.getText().toString();if (obj.length() > 0) {// 调用了下面的方法!long[] a = this.a.a(obj);SendMsgParams sendMsgParams = new SendMsgParams();sendMsgParams.b = this.a.dL;sendMsgParams.a = this.a.dJ;sendMsgParams.c = this.a.dN;sendMsgParams.f26863c = this.a.dL;...}return true;}
}// 这里调用了那2个openPasswdRedBagxxx方法
public long[] m5613a(String str) {long[] jArr = null;if (!AnonymousChatHelper.a().a(this.f25174a.a)) {if (TextUtils.isEmpty(this.f25269d) || !str.equals(this.f25278e)) {jArr = this.f25190a.a(this.f25174a, str);} else {jArr = this.f25190a.b(this.f25174a, this.f25269d);}if (jArr != null && jArr[s] == 1) {this.f25269d = QunUppUploadTask.QunUppAppId;this.f25278e = QunUppUploadTask.QunUppAppId;this.f25228a.sendEmptyMessage(dz);if (QLog.isColorLevel()) {QLog.d(PasswdRedBagManager.a, u, "passwdredbags result[0]=" + jArr[s] + ",result[1]=" + jArr[t] + ",send str=" + str);}}} else if (QLog.isColorLevel()) {QLog.d(PasswdRedBagManager.a, u, "current is in Anonymous, dont search passwdredbags");}return jArr;
}
可见每次我们输入消息发送时,都发生了判断,会去查询是不是红包口令,如果是则直接发请求拿红包然后继续,否则直接当做普通消息继续发送。
总结和下期预告
经过长久的跟踪,我们成功反编译了手机QQ,并追溯到手机QQ红包的数据结构和判断流程。期间经历过数次无用功,但逆向工程正是这么一回事,尤其是静态分析,如果不及时找其他的路,而一路钻牛角尖从一个线索一路去看,很可能会越陷越深,本文的跟踪流程正是不断在坑还小的时候钻出来,然后去找其他的路径,最后才快速地找到了想看的东西。
至于下一期,可能是smali,可能是怎么修改Apktool,也可能是jadx的源码分析,看心情吧,哈哈。
参考资料:
http://www.mak-blog.com/tencent-shell-crack.html
http://www.kanxue.com/bbs/showthread.php?p=1390763&langid=3
Android逆向分析(一) - 反编译看看手Q口令红包的实现原理相关推荐
- Android逆向分析(1) 反编译看看手Q口令红包的实现原理
前言 本篇文章是作者MarkZhai的逆向分析系列的第一篇,已授权发布,并计划之后该系列的更新会第一时间发布在本公号上,敬请关注! 原文 本系列文章是<Android软件安全与逆向分析>的 ...
- jadx重新打包_Android反编译看看手Q口令红包的实现原理
首篇作为开始,先讲讲简单的反编译.反编译通常有几种目的:互相学习.借来用用.嘿嘿(干你,又分为小干干类似微信红包,和大干干改别人的apk帮他上架). 因为没带kvm回来,mbpr屏幕太小,所以下文环境 ...
- Android逆向之旅—反编译利器Apktool使用教程(Apktool的安装使用)建议新手浏览
文章目录 一.下载软件 第一步(apktool.bat) 第二步(apktool.jar) 二.安装软件 三.使用软件 解包 修改文件 重打包 签名 生成keystore文件 进行签名 四.使用 一. ...
- android逆向分析之从smali到java
通过上一篇 android逆向分析之反编译,在dex2jar的前提下,我们获取到了源码,在apktool的前提下,我们可以获取到资源和smali文件,本篇主要讲述smali几个最基本的知识和smali ...
- linux反编译unity手游,Unity3D安卓手游逆向
本文就是记录自己的一次瞎胡闹!! 随便在网上找了一款使用Unity3D 开发的Android 手游,其运行效果是这样的 Unity3D逆向工具 使用ApkDb 反编译apk 文件(在apk 上右键–& ...
- Android逆向去掉某功能,逆向教程之-反编译apk完全精简删除菜单功能(二)
本帖最后由 liuxiaoxin 于 2020-12-3 19:00 编辑 授人以鱼,不如授人以渔! 本教程图文并茂,步骤非常详细,偏小白向,大佬请自觉屏蔽. 使用工具:MT管理器免费版 被修改的软件 ...
- Android 逆向分析大全
转载:Android 逆向分析大全:https://www.jianshu.com/p/a12d04fc748f 1. 概述 1.1 分析步骤 通用逆向分析步骤 1. 了解该模块正向编程相关方法 2. ...
- android 常用编译工具,Android 抖音常用反编译工具
Android 抖音常用反编译工具 常用反编译工具 apktool :反编译apk,重构. dex2jar :反编译apk,解压 classes.dex 文件 JD-GUI :将class文件反编译成 ...
- android逆向分析概述_Android存储概述
android逆向分析概述 Storage is this thing we are all aware of, but always take for granted. Not long ago, ...
最新文章
- python重难点之装饰器详解
- 基于图像的三维模型重建——相机模型与对极几何
- 怎么能方便的进行数据库存储过程的版本管理?
- python读取excle表格数据,将数据编辑到图像上工程
- 学习逆向知识之用于游戏外挂的实现.第二讲,快速寻找植物大战僵尸阳光基址.以及动态基址跟静态基址的区别...
- Vue常用经典开源项目
- Flutter 常用的按钮组件
- JAVA CLASS混淆工具:Allatori 简单试用
- Visual Studio Code 取色器插件 取色选取 插件安装和使用
- 06-maven的profile和Spring boot 的profile整合
- pdf阅读器与迅捷pdf编辑器的使用方法
- 去除【CSDN论坛】【博客】所有广告的方法(非会员or非专家/版主 也可使用哦)
- Uncaught ReferenceError: is not defined
- 项目总结Word基本格式
- 关于激活函数的思考(zig-zagging dynamics)
- 计算机发展简史 计算机的发展历史介绍
- Java 数组元素倒序的三种方式
- 【论文精读3】MVSNet系列论文详解-P-MVSNet
- Ubuntu搜狗输入法乱码问题
- 计算机网络信息安全总结报告,信息安全自查工作总结报告
热门文章
- 手机上的电子词典:PPC版本金山词霸
- C++ Primer Plus Chapter 4 --复合类型(笔记)
- 安卓·设置navigation bar的颜色(setColor方法)
- WebContainers简介:在浏览器中原生运行Node.js(译文)
- java三维滑雪_知到电工电子学山东联盟第二单元章节测试答案
- 完整的从虚拟机安装到kali的安装(详细,必能上网)
- 本科毕业论文不会写怎么办?
- 联想E431笔记本wifi驱动安装
- 网站赞助打shang单页HTML源码
- leetcode738 单调递增的数字