本文為看雪論壇精華文章 看雪論壇作者ID:落葉似秋 0x0 前言 最近一直在研究Windows逆向的東西,想着快要把Android給遺忘了。是以就想利用工作之餘來研究Android相關的技術,來保持對Android熱情。 調用微信代碼來發送朋友圈動态一直是自己想實作的東西,研究了一下,果然實作了,遂寫下本文當作記錄。本文主要分析發送純文字朋友圈動态和發送圖檔朋友圈動态。 0x1 朋友圈動态類型分析
本文用到的工具如下:
- PC
- 一台可以調試微信程序的Android手機
- 微信7.0.11
- ddms(用于跟蹤調用過程)
- uiautomatorviewer(用于定位控件id)
- jadx(用于對微信apk靜态分析)
- frida(用于hook微信,獲得相關資訊,釋出朋友圈)
在分析代碼之前,首先要定位到與之相近的地方,我們首先想到的肯定是發朋友圈那個界面,如何檢視發朋友圈的界面是哪個Activity呢?很簡單,在手機上打開發送朋友圈的界面:
把手機連接配接電腦,打開USB調試,在PC的cmd視窗中執行以下指令:
adb shell dumpsys activity top
可以看到發朋友圈的那個Activity就是com.tencent.mm.plugin.sns.ui.SnsUploadUI 雖然找到了Activity,但還是不能高興太早,想要通過Activity知道哪部分是發朋友的代碼還是比較費力的。于是我們就想到從“發表”按鈕入手,找出發表朋友圈的相關代碼。 點選“發表”按鈕會發生什麼?發表是一個動态的行為,我們可以通過跟蹤點選“發表”按鈕時的調用過程,來找到有用的資訊。跟蹤調用過程,可以使用ddms工具來完成。 打開ddms,選中微信程序,在手機中打開發表朋友圈界面,然後在ddms中點選下圖圈出的圖示開始跟蹤:
将朋友圈動态發出,再點一次上圖圈出的圖示停止跟蹤。 ddms會生成跟蹤結果,對于跟蹤結果,怎麼找到按鈕事件相關的資訊呢,學過Android的朋友就會想到onClick方法,那我們就在ddms的搜尋結果中搜尋這個名稱:
成功的定位到了onClick的位置,但是比起這條onClick結果,更加令人引人注目的是它的上一條結果,因為它包含了我們剛才找到的Activity的類名:
知道這個方法被調用,我們去看看com.tencent.mm.plugin.sns.ui.SnsUploadUI類裡的OnMemuItemClick究竟是什麼。 用jadx打開微信apk,定位到com.tencent.mm.plugin.sns.ui.SnsUploadUI類,在類中搜尋onMemuItemClick,結果不多,看起來比較像的就是這個onMemuItemClick了:
在onMemuItemClick方法中看到了:
String unused2 = SnsUploadUI.this.desc = SnsUploadUI.this.tQN.getText().toString();
這行代碼有什麼特别的呢,在我看來,有兩個特别的地方:
- desc是描述(description)的英文單詞的縮寫
- this.desc被賦予this.tQN.getText().toString()
我們發朋友圈動态時候,是要寫動态的描述的,是以這個desc可能就是發朋友圈動态的描述,如果是描述,我們就可以根據這個描述,順藤摸瓜找到發朋友圈動态的地方。 而且this.desc的值又來自于this.tQN.getText().toString(),即this.tQN很有可能就是我們填寫動态描述的文本框。我們來看看this.tQN指派的地方,它在onCreate方法被指派:
可以知道它的id是d41,那麼d41是哪個控件?打開uiautomatorviewer,對發朋友圈界面截圖分析,點選截圖中的文本框,uiautomatorviewer右側跳轉到了相應的位置,果然,d41就是發動态時填寫描述文本框的id
好了,現在知道this.desc就是發表朋友圈動态時的描述,跟上他應該就可以找到發朋友圈動态的地方。 繼續往onMemuItemClick方法下部分析,可以看到this.desc被傳入了SnsUploadUI.this.tQO.a方法:
SnsUploadUI.this.tQO.a方法定義在接口com.tencent.mm.plugin.sns.ui.z 中:
知道它定義在哪個接口并不能解決問題,畢竟接口沒有實質性代碼,要找還得找接口的的實作類,在com.tencent.mm.plugin.sns.ui.SnsUploadUI類中尋找this.tQO在哪裡會被指派。 最終,我們在com.tencent.mm.plugin.sns.ui.SnsUploadUI類的ag方法中看到了許多給this.tQO指派的地方:
由此,可見this.tQO被賦予什麼值是根據this.tMY來決定的,this.tMY是一個int類型的資料,那我們hook com.tencent.mm.plugin.sns.ui.SnsUploadUI類的ag方法就可以知道this.tMY是什麼值。 在這裡,我用frida來hook,frida的javascript部分代碼如下:
var SnsUploadUI= Java.use('com.tencent.mm.plugin.sns.ui.SnsUploadUI');var ag = SnsUploadUI.ag.overload("android.os.Bundle");//get sns typeag.implementation=function(bundle){ var ret = ag.call(this,bundle); send("sns type = " + this.tMY.value); return ret;}
hook之後,每當我們在手機上打開釋出朋友圈動态的界面,ag方法被調用,控制台就會輸出相應的數字。 經過我的測試,這個數字是發表朋友圈動态的類型。朋友圈類型和其對應類如下:
- 0 帶圖檔的動态,對應:com.tencent.mm.plugin.sns.ui.ai
- 9 純文字動态,對應:com.tencent.mm.plugin.sns.ui.ae
這些類都直接或間接的實作了上面講到的com.tencent.mm.plugin.sns.ui.z接口。 這樣一來,就知道this.tQO會根據朋友圈的動态類型進行初始化,那麼,上面的SnsUploadUI.this.tQO.a方法很有可能就是發朋友圈動态的方法。 接下來,我們根據不同的朋友圈動态所對應的類來分别分析 0x2 文字動态分析 文字動态分析起來應該比圖檔動态來說簡單一些,我們就先來分析它。 上面講到,這類動态對應類是com.tencent.mm.plugin.sns.ui.ae,這個類裡我們主要看a方法,在看a方法之前,先看它傳入什麼參數,為了看清楚,這就要回看上文onMenuItemClick方法調用a方法的地方:
public final boolean onMenuItemClick(MenuItem menuItem) { String unused2 = SnsUploadUI.this.desc = SnsUploadUI.this.tQN.getText().toString(); int pasterLen = SnsUploadUI.this.tQN.getPasterLen(); int privated = SnsUploadUI.this.tKm.getPrivated(); int syncFlag2 = SnsUploadUI.this.tKm.getSyncFlag(); ...... PInt pInt = new PInt(); if (SnsUploadUI.this.tQO instanceof a) { Bundle bundle = new Bundle(); bundle.putInt("param_is_privated", privated); bundle.putString("param_description", SnsUploadUI.this.desc); bundle.putStringArrayList("param_with_list", new ArrayList(SnsUploadUI.this.uij.getAtList())); bundle.putInt("param_paste_len", pasterLen); try { bundle.putByteArray("param_localtion", SnsUploadUI.this.uik.getLocation().toByteArray()); } catch (IOException e2) { ab.printErrStackTrace("MicroMsg.SnsUploadUI", e2, "parse location error", new Object[0]); } bundle.putBoolean("param_is_black_group", SnsUploadUI.this.tQS); bundle.putStringArrayList("param_group_user", SnsUploadUI.this.tQR); bundle.putInt("param_contact_tag_count", SnsUploadUI.this.tOk); bundle.putInt("param_temp_user_count", SnsUploadUI.this.tOl); pInt.value = ((a) SnsUploadUI.this.tQO).getContentType(); z unused4 = SnsUploadUI.this.tQO; } else { SnsUploadUI.this.tQO.a(privated, syncFlag2, SnsUploadUI.this.tKm.getTwitterAccessToken(), SnsUploadUI.this.desc, SnsUploadUI.this.uij.getAtList(), SnsUploadUI.this.uik.getLocation(), (LinkedList) null, pasterLen, SnsUploadUI.this.tQS, SnsUploadUI.this.tQR, pInt, SnsUploadUI.this.tOj, SnsUploadUI.this.tOk, SnsUploadUI.this.tOl); }}
這就是a方法調用的地方,根據這段代碼和編寫hook a方法的代碼來推出它的參數。hook代碼如下:
var ae = Java.use('com.tencent.mm.plugin.sns.ui.ae');var ae_a = ae.a.overload("int","int","org.b.d.i","java.lang.String","java.util.List","com.tencent.mm.protocal.protobuf.bdi","java.util.LinkedList","int","boolean","java.util.List","com.tencent.mm.pointers.PInt","java.lang.String","int","int");ae_a.implementation = function(isPrivate,syncFlag2,twitterAccessToken,desc,atList,location,list1,pasterLen,bool1,list2,pint1,str1,num1,num2){ var ret = ae_a.call(this,isPrivate,syncFlag2,twitterAccessToken,desc,atList,location,list1,pasterLen,bool1,list2,pint1,str1,num1,num2); console.log("************Basic Info************"); console.log("isPrivate = " + isPrivate); console.log("syncFlag2 = " + syncFlag2); console.log("twitterAccessToken = " + twitterAccessToken); console.log("desc = " + "'" + desc + "'"); if(atList.size()>0){ for(var i=0;i console.log("atList[" + i + "] = " + atList.get(0)); } } if(location != null){ if(location.yRD.value != null){ console.log("location.yRD = " + location.yRD.value); } if(location.yRE.value != null){ console.log("location.yRE = " + location.yRE.value); } } console.log("list1 = " + list1); console.log("pasterLen = " + pasterLen); console.log("bool1 = " + bool1); if(list2 != null){ console.log("list2 = " + list2.size()); } else{ console.log("list2 = " + list2); } console.log("pint1 = " + pint1.value.value); console.log("str1 = " + str1); console.log("num1 = " + num1); console.log("num1 = " + num1); return ret;}//ae.a
hook成功後,發一條純文字的朋友圈動态,列印出:
是以,可得:
- privated(int):動态是否私密:0公開,1私密- desc(String):朋友圈的文本- AtList(List):艾特人的wxid- Location(com.tencent.mm.protocal.protobuf.bdi):定位資訊
好多參數我們不知道是什麼,不過問題不大,那些我們需要的參數已經能搞懂了。懂得a方法的參數,那能否嘗試直接調用它? 先來看一下com.tencent.mm.plugin.sns.ui.ae類的構造函數能否調用:
構造函數有Activity類型的參數,Activity類型的參數是很難構造的,是以放棄構造com.tencent.mm.plugin.sns.ui.ae類來調用a方法。那我們直接去看a方法,看能不能找到有用的東西。 由于是文字動态,是以我們着重關注傳入的文本,即com.tencent.mm.plugin.sns.ui.SnsUploadUI類的desc成員,在a方法中它是第4個參數:
可見this.desc在a方法中它是str,而且在a方法中,str隻有一處引用:
str傳給了ayVar.adk()方法,找一下ayVar來自哪裡,它在a方法裡初始化,而且初始化方式很簡單:
隻傳入一個數字就能初始化,我們初始化ay類的時候不用深究這個數字是什麼,和它傳入一樣的值即可。 在a方法的尾部,還看到一個引人注目的commit方法:
猜測這就是釋出朋友圈的方法,寫個簡單的frida腳本來驗證一下:
if(Java.available){ Java.perform(function(){ var ay_class = Java.use("com.tencent.mm.plugin.sns.model.ay"); var desc = "To be, or not to be, that is a question."; var ayInstance = ay_class.$new(2); ayInstance.adk(desc); ayInstance.commit(); });}
文字動态的内容是:To be, or not to be, that is a question. 果不其然,腳本運作後,文字動态發表成功了。
經過對com.tencent.mm.plugin.sns.ui.ae的a方法的分析,我們可以知道,a方法主要傳入一些發朋友圈動态所需要的通用的資料,比如動态是否私密,動态的文字描述,艾特的人,定位資訊等,這些資訊在其他類型的朋友圈動态中也會用得到。我們還知道發文字動态隻需要文字描述就能發表成功。 0x3 帶圖檔的朋友圈動态分析 帶圖檔的的動态和文字動态差不多,隻是多加了圖檔的參數,我們在分析此類動态時多關注圖檔在哪傳入即可。 帶圖檔的動态對應的類是com.tencent.mm.plugin.sns.ui.ai,有了上面的經驗,我們直接去看它的a方法(ai類有許多a方法,注意這裡說的a方法參數和com.tencent.mm.plugin.sns.ui.z接口裡的a方法參數一緻)。 在a方法的開頭,看到利用疊代器去周遊一個清單,周遊過程中組裝com.tencent.mm.plugin.sns.data.j類的資料,然後把j類放傳入連結表linkedList2中:
在組裝資料的時候,我們看到j類構造時傳入一個字元串和數字,而這個字元串對應j類的path字段,這可能就是圖檔的路徑:
那麼我們猜測j類就是存儲朋友圈的動态圖檔資訊的類,上面提到j類被放傳入連結表linkedList2中,那麼來看linkedList2被哪裡引用了
看到醒目的字元串:commit pic size,這應該是日志要列印的字元串,現在基本上可以确定j類就是存儲要發表的圖檔的資訊的類了,那麼linkedList2就是存儲所有将要發表的圖檔資訊,繼續往下尋找linkedList2還被哪裡引用了
可以看到linkedList2傳入兩個地方,一處傳入a方法:
另一處傳入com.tencent.mm.plugin.sns.ui.ai$a類構造函數:
linkedList2傳入a類後,又指派給成員變量tPF,這個tPF成員變量隻在a類的dU方法中被引用。
而dU方法在哪裡調用呢? 在a類的父類:com.tencent.mm.plugin.sns.model.h中,我們看到dU方法在u方法被調用:
而u方法在ai類的a方法中調用(可以回看前面的圖)。 分析到這,上面的linkedList2傳出去之後都終有所屬了,即最終都傳入了com.tencent.mm.plugin.sns.model.ay類ey方法。 知道圖檔往哪傳了,就寫段frida代碼調用試試吧。
if(Java.available){ Java.perform(function(){ var ay_class = Java.use("com.tencent.mm.plugin.sns.model.ay"); var j_class = Java.use("com.tencent.mm.plugin.sns.data.j") var desc = "To be, or not to be, that is a question."; var likedList_class = Java.use("java.util.LinkedList"); var linkedListInstance = likedList_class.$new(); var ayInstance = ay_class.$new(1); var jInstance1 = j_class.$new("/storage/emulated/0/test1.jpg",2); var jInstance2 = j_class.$new("/storage/emulated/0/test2.jpg",2); var jInstance3 = j_class.$new("/storage/emulated/0/test3.jpg",2); linkedListInstance.add(jInstance1); linkedListInstance.add(jInstance2); linkedListInstance.add(jInstance3); ayInstance.ey(linkedListInstance); ayInstance.adk(desc); ayInstance.commit(); });}
上面的代碼在發送文本動态代碼的基礎上初始化三個j類,分别傳入三個本地圖檔路徑,再将三個類執行個體添加到連結清單,再将連結清單傳入ay類的ey方法,最後調用ay類的commit方法将動态發送出去,代碼運作,發現帶圖檔的朋友圈動态發表成功:
- End -
看雪ID:落葉似秋
https://bbs.pediy.com/user-815293.htm
*本文由看雪論壇 落葉似秋 原創,轉載請注明來自看雪社群。
推薦文章++++
* 為了了解反彙編引擎而寫的X86/X64反彙編引擎
* 捆綁包驅動鎖首病毒分析
* **遊戲逆向分析筆記
* 對寶馬車載apps協定的逆向分析研究
* x86_64架構下的函數調用及棧幀原理
好書推薦
﹀ ﹀ ﹀
公衆号ID:ikanxue 官方微網誌:看雪安全 商務合作:[email protected]
戳