Android反編譯看看手Q口令紅包的實(shí)現(xiàn)原理
首篇作為開(kāi)始,先講講簡(jiǎn)單的反編譯。反編譯通常有幾種目的:互相學(xué)習(xí)、借來(lái)用用、嘿嘿(干你,又分為小干干類似微信紅包,和大干干改別人的apk幫他上架)。
因?yàn)闆](méi)帶kvm回來(lái),mbpr屏幕太小,所以下文環(huán)境為windows。
一、反編譯
讓我們從實(shí)戰(zhàn)開(kāi)始,先實(shí)踐一下怎么去反編譯一個(gè)apk,看看某些功能的實(shí)現(xiàn)。畢竟沒(méi)有實(shí)踐的原理都是耍流氓。
這里我們保留互相學(xué)習(xí)的心態(tài),所以是友善的第一種目的,嘻嘻。
1、準(zhǔn)備
工具
- Apktool
- jadx(新一代反編譯大殺器)
安裝包
手機(jī)QQ 6.2.3 (目標(biāo)就設(shè)定為看看口令紅包是怎么做的吧)
2、Apktool的使用
首先確保你安裝了java 7或以上,并能直接在命令行調(diào)用java。
- 下載 windows用wrapper腳本 (mac使用這個(gè))。
- 下載最新的apktool。
- 重命名上面下載的apktool jar文件為apktool.jar。
- 把a(bǔ)pktool.bat和apktool.jar放在同一個(gè)目錄下,并加入PATH環(huán)境變量。
- 現(xiàn)在你可以直接通過(guò)命令行調(diào)用apktool并查看使用方式了。
Apktool v2.0.3 - a tool for reengineering Android apk files with smali v2.1.0 and baksmali v2.1.0 usage: apktool -advance,--advanced prints advance information. -version,--version prints the version then exits usage: 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的使用
- 下載jadx。
- 運(yùn)行g(shù)radlew dist編譯。
- jadx\jadx-gui\build\install\jadx-gui\bin下有可運(yùn)行的gui
- jadx\jadx-cli\build\install\jadx\bin是命令行程序
- 可以都加入PATH環(huán)境變量,以便直接命令行調(diào)用。
4、分析APK文件
First Try
雖然我們可以用jadx直接打開(kāi)apk傻瓜式地去查看源代碼,但是為了更理解反編譯的過(guò)程和工作原理,以便以后在碰到一些問(wèn)題(比如加殼)的時(shí)候可以自己解決,這里我們先裝逼一下,使用Apktool去進(jìn)行分析。
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/name at 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)
竟然報(bào)錯(cuò)了,Multiple res specs: attr/name,在網(wǎng)上找了找資料,應(yīng)該是騰訊利用Apktool的bug去進(jìn)行了加殼,除了添加同名id外還做了若干加固,好,你狠,我們下篇文章針對(duì)騰訊的殼來(lái)分析并修改Apktool,這次先用jadx來(lái)試試。
Second Try
如果直接用jadx-gui打開(kāi)QQ的apk,你會(huì)發(fā)現(xiàn),卡死了。不錯(cuò),就是卡死了,因?yàn)樘罅恕?/p>
我們打開(kāi)jadx-gui文件(其實(shí)就是個(gè)啟動(dòng)的script),加上:
set JAVA_OPTS=-server -Xms1024m -Xmx8192m -XX:PermSize=256m -XX:MaxPermSize=1024m
就跟我們加速as/idea的原理差不多,多給點(diǎn)內(nèi)存,這樣就能順利地打開(kāi)了(可能會(huì)需要比較久的時(shí)間)。
5、字符串大法
為了找到我們的目標(biāo),紅包,我們首先嘗試用字符串搜索大法:在Resources -> resources.arsc -> res -> values -> strings.xml找到口令紅包對(duì)應(yīng)的
<string name="qb_hbdetail_command_word">口令紅包</string>
然后Crtl+Shift+F進(jìn)行Text Search,結(jié)果…沒(méi)找到。
我們?cè)偈褂觅Y源id大法,直接在resources.arsc找到
0x7f0a0e5a (2131365466) = string.qb_hbdetail_command_word: 口令紅包
再搜,好,你狠。。。還是沒(méi)有。是在下輸了。
6、類/函數(shù)名大法
我們?cè)偌莱龅诙髿⑵?,?函數(shù)/變量名大法搜索大法。
通常類名符合的范圍更小,所以先只使用Class。
試試看紅包的英語(yǔ):RedPacket(類名命名所以R和P大寫)
OK,我們找到了十幾條,開(kāi)始逐一排查,第一條RedPacketInfo點(diǎn)進(jìn)去一看就是個(gè)包含了各種field的ui用的vo類,跳過(guò),再看下一個(gè),從包名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;
...串行化、讀寫、構(gòu)建方法等,可以無(wú)視。
從field名來(lái)看,這里還是比較可疑的,猜測(cè)redtype是不是描述紅包類型的。
我們?cè)俅问褂藐P(guān)鍵詞redtype進(jìn)行搜索,這次選擇Code,只進(jìn)行代碼內(nèi)搜索,結(jié)果卻發(fā)現(xiàn)貌似不對(duì),找到相關(guān)的字符串是”查看詳情”,貌似是描述紅包領(lǐng)取狀態(tài)的。
不放棄,繼續(xù)抓住QQWalletRedPacketMsg這個(gè)類進(jìn)行搜索,看看是不是有外面包著這個(gè)類的Class,搜索QQWalletRedPacketMsg,范圍使用Field,排除掉類本身外,只有唯一的結(jié)果: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; ...
我們找到了一個(gè)常量字段,目測(cè)就是這個(gè)描述了是否是口令紅包了。在該類搜索此字段還找到
public static boolean isCommandRedPacketMsg(MessageRecord messageRecord) { if (messageRecord != null && (messageRecord instanceof MessageForQQWalletMsg) && ((MessageForQQWalletMsg) messageRecord).messageType == MSG_TYPE_COMMAND_REDPACKET) { return true; } return false; }
果然,我們?cè)俳又謩e查找MSG_TYPE_COMMAND_REDPACKET和isCommandRedPacketMsg,結(jié)果只在TroopMessageManager里面找到了一段沒(méi)成功反編譯的代碼中對(duì)方法isCommandRedPacketMsg的引用:
L_0x0100: r2 = com.tencent.mobileqq.data.MessageForQQWalletMsg.isCommandRedPacketMsg(r25); if (r2 == 0) goto L_0x011e;
這里如果是口令紅包會(huì)繼續(xù)走下去,而如果不是則會(huì)跳到L_0x011e。
而從類的名字來(lái)看,TroopMessageManager應(yīng)該是指群消息管理者,應(yīng)該沒(méi)錯(cuò),畢竟紅包也是群消息的一種。
于是我們只能耐心地看下去這段神奇的充滿goto的代碼。暈著看完后大概看到就是各種邏輯判斷和調(diào)用MsgProxyUtils.java去處理消息處理邏輯和緩存。然后就沒(méi)了…好,你屌,是在下輸了。我再試試別的。
7、常量大法
常量大法其實(shí)也可以算是字符串搜索的一種,只是不去搜索xml里的,而是使用中文轉(zhuǎn)化為unicode后的字符串去進(jìn)行查找。自行搜索Unicode編碼轉(zhuǎn)化可以找到online convertor。
口令紅包對(duì)應(yīng)的是”\u53e3\u4ee4\u7ea2\u5305”:
找到2個(gè)類共3處代碼引用。
最后那個(gè)類的起名有點(diǎn)耐人尋味,PasswdRedBagManager,密碼紅包管理器,有點(diǎn)意思:
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); }
這串Unicode轉(zhuǎn)換成中文后是”匿名不能搶口令紅包哦”,原來(lái)還有這種邏輯,產(chǎn)品經(jīng)理你真是夠了。
這里我們重新從該類的上面看下來(lái),大致掃一掃,發(fā)現(xiàn)onDestroy下面有一個(gè)方法打的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目測(cè)是某種debug用的tag,可能某些環(huán)境下部分用戶會(huì)打開(kāi),而從log結(jié)合我們平時(shí)打log習(xí)慣來(lái)看,這個(gè)方法應(yīng)該就叫openPasswdRedBagByPassword了,第二個(gè)參數(shù)就是password。終于找到了??匆幌逻壿嫶笾率菑耐饷鎙oad進(jìn)來(lái)所有紅包信息到本類的各種hashmap和list(有一個(gè)tag,只會(huì)加載第一次,本類多個(gè)方法都會(huì)調(diào)用這個(gè)方法),然后根據(jù)password從里面找到對(duì)應(yīng)passwdRedBagInfo,設(shè)置result tag,然后調(diào)用了
b(sessionInfo.a, sessionInfo.f1757a, passwdRedBagInfo.a.string_redbag_id.get().toStringUtf8()); a(sessionInfo, passwdRedBagInfo);
我們先不急看這兩個(gè)方法是做什么的。再往下看下一個(gè)方法,直接就有:
public long[] b(SessionInfo sessionInfo, String str) { if (QLog.isColorLevel()) { QLog.d(f2197a, (int) h, "openPasswdRedBagById, id = " + str); }
openPasswdRedBagById用id打開(kāi)紅包,猜測(cè)該id就是我們最早看到的結(jié)構(gòu)里的redPacketId字段。
而該方法同樣調(diào)用了
b(sessionInfo.a, sessionInfo.f1757a, str); a(sessionInfo, passwdRedBagInfo);
看看這兩個(gè)方法:
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); } } } }
發(fā)現(xiàn)第一個(gè)方法似乎就直接發(fā)請(qǐng)求了,看來(lái)只要調(diào)用到這里,就是可以領(lǐng)紅包了。那最初又是如何來(lái)這里的呢?我們搜索對(duì)PasswdRedBagManager內(nèi)這兩個(gè)方法的引用找到BaseChatPie.java:
public PasswdRedBagManager f25190a; ... public class EnterForSend implements OnKeyListener, OnEditorActionListener { ... // 這里從方法名判斷是每次輸入點(diǎn)擊發(fā)送后調(diào)用 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) { // 調(diào)用了外部類的下述方法 long[] a = this.a.a(obj); // 再進(jìn)行消息發(fā)送 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; } } // 這里調(diào)用了那2個(gè)openPasswdRedBagxxx方法 public long[] m5613a(String str) { long[] jArr = null; // 非匿名模式才會(huì)繼續(xù)嘗試匹配口令紅包,原來(lái)里里外外都做了判斷 if (!AnonymousChatHelper.a().a(this.f25174a.a)) { if (TextUtils.isEmpty(this.f25269d) || !str.equals(this.f25278e)) { // 使用密碼打開(kāi) jArr = this.f25190a.a(this.f25174a, str); } else { // 使用redPacketId直接打開(kāi) jArr = this.f25190a.b(this.f25174a, this.f25269d); } // 無(wú)意義的打log打點(diǎn)啥的,華麗麗地?zé)o視吧 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; }
可見(jiàn)每次我們輸入消息發(fā)送時(shí),都發(fā)生了判斷,會(huì)去查詢是不是紅包口令,如果是則直接發(fā)請(qǐng)求拿紅包然后繼續(xù),否則直接當(dāng)做普通消息繼續(xù)發(fā)送。所以如果想要做自動(dòng)搶紅包的話,其實(shí)只要直接在收到消息時(shí),調(diào)用PasswdRedBagManager的open方法即可,連模擬UI、生成請(qǐng)求、發(fā)送消息都不用了,我們?cè)僖膊挥妹林夹恼f(shuō)口令了。順便我們還看到了手機(jī)QQ確實(shí)喜歡用Activity,這里的紅包彈框也是一個(gè)單獨(dú)的Activity,而且請(qǐng)求是發(fā)送到手Q紅包那邊去的,看來(lái)還分業(yè)務(wù)線。
到此為止我們的目的告一段落,其實(shí)繼續(xù)下去,還可以嘗試dump當(dāng)前Activity,用Activity名字去查找,或者用hierarchy view看看view id試試。
經(jīng)過(guò)上文的折騰,我們成功反編譯了手機(jī)QQ,并追溯到手機(jī)QQ紅包的數(shù)據(jù)結(jié)構(gòu)和判斷流程。期間經(jīng)歷過(guò)數(shù)次無(wú)用功,但逆向工程正是這么一回事,尤其是靜態(tài)分析,如果不及時(shí)找其他的路,而一路鉆牛角尖從一個(gè)線索一路去看,很可能會(huì)越陷越深,本文的跟蹤流程正是不斷在坑還小的時(shí)候鉆出來(lái),然后去找其他的路徑,最后才快速地找到了想看的東西。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)填寫
這篇文章主要為大家詳細(xì)介紹了Android短信驗(yàn)證碼自動(dòng)填寫功能的實(shí)現(xiàn)過(guò)程,感興趣的小伙伴們可以參考一下2016-08-08Android給scrollView截圖超過(guò)屏幕大小形成長(zhǎng)圖
這篇文章主要為大家詳細(xì)介紹了Android給scrollView截圖超過(guò)屏幕大小形成長(zhǎng)圖,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Android基于廣播事件機(jī)制實(shí)現(xiàn)簡(jiǎn)單定時(shí)提醒功能代碼
這篇文章主要介紹了Android基于廣播事件機(jī)制實(shí)現(xiàn)簡(jiǎn)單定時(shí)提醒功能代碼,較為詳細(xì)的分析了Android廣播事件機(jī)制及提醒功能的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Android進(jìn)階——安卓調(diào)用ESC/POS打印機(jī)打印實(shí)例
本篇文章主要介紹了Android進(jìn)階——安卓調(diào)用ESC/POS打印機(jī)打印實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04Assert.assertEquals()方法參數(shù)詳解
本文詳細(xì)講解了Assert.assertEquals()方法參數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12Android中使用TagFlowLayout制作動(dòng)態(tài)添加刪除標(biāo)簽
這篇文章主要介紹了Android中使用TagFlowLayout制作動(dòng)態(tài)添加刪除標(biāo)簽的步驟詳解,需要的朋友參考下吧2017-07-07Android編程判斷網(wǎng)絡(luò)是否可用及調(diào)用系統(tǒng)設(shè)置項(xiàng)的方法
這篇文章主要介紹了Android編程判斷網(wǎng)絡(luò)是否可用及調(diào)用系統(tǒng)設(shè)置項(xiàng)的方法,涉及Android針對(duì)網(wǎng)絡(luò)連接的判定及屬性設(shè)置的調(diào)用,需要的朋友可以參考下2016-03-03Android React Native原生模塊與JS模塊通信的方法總結(jié)
這篇文章主要介紹了Android React Native原生模塊與JS模塊通信的方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-02-02