Apktool v2.0.3 - a tool for reengineering Android apk fileswith smali v2.1.0 and baksmali v2.1.0usage: apktool -advance,--advancedprints advance information. -version,--versionprints 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>.3、jadx的使用
D:dev everse>apktool d -o qq mobileqq_android_6.2.3.apkI: Using Apktool 2.0.3 on mobileqq_android_6.2.3.apkI: 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来试试。
set JAVA_OPTS=-server -Xms1024m -Xmx8192m -XX:PermSize=256m -XX:MaxPermSize=1024m就跟我们加速as/idea的原理差不多,多给点内存,这样就能顺利地打开了(可能会需要比较久的时间)。

5、字符串大法
为了找到我们的目标,红包,我们首先尝试用字符串搜索大法:在Resources -> resources.arsc -> res -> values -> strings.xml找到口令红包对应的
<string name="qb_hbdetail_command_word">口令红包</string>然后Crtl+Shift+F进行Text Search,结果…没找到。
0x7f0a0e5a (2131365466) = string.qb_hbdetail_command_word: 口令红包再搜,好,你狠。。。还是没有。是在下输了。

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; ...串行化、读写、构建方法等,可以无视。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。

找到2个类共3处代码引用。
最后那个类的起名有点耐人寻味,PasswdRedBagManager,密码红包管理器,有点意思:
public void b(String str) {((TroopTipsMsgMgr) this.f2203a.getManager(80)).a(str, "u533fu540du4e0du80fdu62a2u53e3u4ee4u7ea2u5305u54e6", NetConnInfoCenter.getServerTime(), BaseConstants.DEFAULT_QUICK_HEARTBEAT_TIMEOUT, f);}这串Unicode转换成中文后是”匿名不能抢口令红包哦”,原来还有这种逻辑,产品经理你真是够了。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 {// 使用redPacketId直接打开jArr = this.f25190a.b(this.f25174a, this.f25269d);}// 无意义的打log打点啥的,华丽丽地无视吧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;}可见每次我们输入消息发送时,都发生了判断,会去查询是不是红包口令,如果是则直接发请求拿红包然后继续,否则直接当做普通消息继续发送。所以如果想要做自动抢红包的话,其实只要直接在收到消息时,调用PasswdRedBagManager的open方法即可,连模拟UI、生成请求、发送消息都不用了,我们再也不用昧着良心说口令了。顺便我们还看到了手机QQ确实喜欢用Activity,这里的红包弹框也是一个单独的Activity,而且请求是发送到手Q红包那边去的,看来还分业务线。