微信逆向之朋友圈

前提


這篇文章我主要會講我所掌握逆向的一些小技巧,及如何一步步的爬取到微信朋友圈的數據的過程。關於微信逆向的工做,可能不少小夥伴呢都幹過這事,最讓人頭疼的就是如何快速定位一個Hook點。還有就是如何理清被混淆以後的代碼。這兩個點弄清楚,可能Hook你所要的東西,那就是很輕鬆了。我這裏只是針對Android的微信7.0.3版本最新版本7.0.4java

生活在這樣的大數據年代,固然數據是最重要的,若是做爲一個商戶,你有N多個客戶,那如何對這些商戶進行分類,其次就是作到精準營銷,而後那就是Money了。 那位對於朋友圈數據的分析會起到很是重要的做用。給每一個商戶打上一個Tag。我這只是一個簡單的舉例,不妨礙你們閱讀下面的文章,那麼廢話很少說了。開始編碼android

若是你掌握瞭如下內容,你將能夠獲取好友的朋友圈圖片,視頻,評論,點贊及你以前所看到的內容,即便他已經關閉朋友圈,只要你看過,那就是存在數據庫中,咱們就能夠拿獲得git

準備工做


以上除了手機root外,其餘的工具我都附上了連接,你們直接去下載便可。對於如何root手機,做爲一個Android 開發者,自行Google吧 推薦經過TWRP+SuperSu 的方式,不少機型均可以燒成功。網上不少案例。xpose 的使用這裏我就不過多的贅述了,一些Api的調用而已。Jadx這個工具呢必不可少,固然你也可使用其餘的反編譯工具(dex2jar,jeb,jd-gui....)用着順手就行,我這裏使用的是Jadx,啊真的很好用。github

加載微信源碼

如下的操做會讓你加快Hook點定位sql

  1. 用Jadx 打開下載好的微信Apk,編譯成功應該是以下頁面

jadx.png

  1. 把編譯好的項目另存爲Gradle項目shell

  2. 用AS 打開剛剛另存的Gradle項目,AS 能夠幫們創建快速索引,方便咱們調試找Hook點。可能還有更好的操做,有同窗知道的能夠留言區交流如下數據庫

以上操做完成以後,準備工做已經大部分都完成了。緩存

朋友圈數據庫

這裏我就簡單介紹一下,網上也有這方面的資料,SnsMicroMsg.db這個就是保存咱們朋友圈數據的數據庫,SnsInfo這個表是具體內容,咱們能夠在/data/data/com.tencent.mm/MicroMsg/md5({mm+uin})帳號目錄下找到這個文件,並且也沒有對數據庫加密,可一直直接Navicat打開的,可是微信聊天記錄數據庫就是加密的,須要破解密碼才能打開,密碼一直沒變過仍是經過{IMEI+UIN}md5便可,回到主題,可是雖然你能夠拿到朋友圈數據庫,可是你發現並無什麼卵用,裏面的數據所有加密了,徹底摸不着頭腦。這裏你們能夠看看這個數據庫文件。除了能看到userName 和createTime 有點用,其餘的字段真是看不到頭緒bash

咱們接下來注意觀察第二張圖片,content,attrBuf,這二個字段裏面全是BLOB格式的,能夠發現,小老弟把數據藏起來了啊。那難道咱們就沒有辦法了嗎!固然仍是能夠拿的到的,那就是經過微信源碼查看了,咱們接下來的工做就是,看微信是如何拿到這個BLOB,如何進行解析的。咱們也經過他的方式進行解析不就得了嗎。接下來進入主題。微信

Hook朋友圈Activity

我這裏Hook點是我的朋友圈入口,經過點擊通信錄,進入我的的朋友圈頁面,而後找到這個Activity是哪個Activity,而後查看這個頁面的源碼。目前到如今爲止,尚未用到Xpose 這個工具,那麼咱們如何知道這個Activity是哪一個呢,其實Android系統給咱們提供了一個dumpsys的工具,根據包名能夠獲取到當前是哪一個Activity,咱們只須要adb shell 登進去

點擊進入某我的的這個頁面,而後調用一下代碼就能夠獲取當前是哪一個activity了

dumpsys activity |grep -i com.tencent.mm
複製代碼

打開微信我的朋友圈頁面執行上述代碼以後便可獲取當前頁面,這時候能夠在terminal裏面看到以下

com.tencent.mm/.plugin.sns.ui.SnsUserUI

複製代碼

到此爲止,咱們已經知道了我的朋友圈頁面在哪裏了。接下來咱們去剛剛AS打開的微信項目裏面找到這個SnsUserUI Activity了,打開頁面能夠發現不少代碼已經混淆的很難讀懂了,什麼a.a.a,b.c.da這樣的包名已經方法名。可是咱們這時候不要慌張。接下來打開手機,咱們能夠經過微信這個頁面的UI看到,這是一個list,那確定不是listview就是recyclerview了。這是後打開AS找到SnsUserUI這個類,而後倒開他的structure目錄

snsuser.png

能夠發現裏面有個方法initView 是否是很熟悉,咱們常常寫代碼也會這樣寫,那麼咱們這就點進去看一下, 這裏就不粘貼不少代碼。有興趣能夠自行反編譯看看

public final void initView() {
        this.qXD = (RelativeLayout) findViewById(f.sns_user_year_tip_layout);
        this.qXE = (TextView) findViewById(f.sns_user_year_tip);
        this.qXD.post(new Runnable() {
            public final void run() {
                LayoutParams layoutParams = new LayoutParams(-1, -2);
                layoutParams.topMargin = x.aj(SnsUserUI.this) + SnsUserUI.this.getResources().getDimensionPixelSize(i.d.ActionBarHeight);
                SnsUserUI.this.qXD.setLayoutParams(layoutParams);
            }
        });
        this.qXz = new as(this, new a() {
            public final void fc(int i, int i2) {
                super.fc(i, i2);
            }
        }, this.hMy, new as.c() {
        });
        this.qXA.naj.setAdapter(this.qXz);
        this.qXA.naj.setOnItemClickListener(new OnItemClickListener() {
            public final void onItemClick(AdapterView<?> adapterView, View view, int i, long j) {
            }
        });

複製代碼

打開觀察以後,發現這裏面有個setAdapter的地方,對的!確定數據就在這個Adapter裏面,由於咱們平時寫list 渲染的時候數據都是經過adapter 渲染到list 上的,咱們繼續點進去看看這個adapter 是如何寫的。

朋友圈Adapter深刻

咱們經過上述的方法已經找到了Adapter了,咱們點擊(this.qXz)這個字段發現他的類名是as,點擊進去發現,果真沒錯。

public final class as extends BaseAdapter {
    private Activity coM;
    boolean cog = false;
    private String country;
    List<n> list = new ArrayList();
    String lnO = "";
    private String ngt = "";
    Map<Integer, Integer> qBc = new HashMap();
    Map<Integer, Integer> qBd = new HashMap();
    int qBe = 0;
    int qBf = 0;
    String qHI = "";
    private bd qKW = null;
    private az qQo;
    Map<Integer, Integer> qQp = new HashMap();
    private f qQq;
    boolean qQr = false;
    at qQs;
    private c qQt;
    int qQu = BaseClientBuilder.API_PRIORITY_OTHER;
    int qQv = 0;
    private long qQw = 0;
    private long qQx = 0;
    int qQy = 0;
    protected OnClickListener qQz = new OnClickListener() {
        public final void onClick(View view) {
            if (view.getTag() instanceof TimeLineObject) {
                TimeLineObject timeLineObject = (TimeLineObject) view.getTag();
                if (as.Yp(timeLineObject.Id)) {
                    h.ptS.X(10231, "1");
                    com.tencent.mm.av.a.agc();
                } else {
                    h.ptS.X(10090, "1,0");
                    if (!(com.tencent.mm.q.a.bN(as.this.coM) || com.tencent.mm.q.a.bL(as.this.coM))) {
                        com.tencent.mm.av.e a = g.a(af.getAccPath(), timeLineObject, 8);
                        a.fuR = as.this.userName;
                        com.tencent.mm.av.a.b(a);
                    }
                }
                as.this.notifyDataSetChanged();
            }
            ........省略不少代碼........
            
複製代碼

那咱們接下來怎麼找呢,Adapter已經拿到了,難道仍是一點點翻嗎?固然不是了,想一想咱們平常寫listview的Adapter時候,是否是在getView的時候進行給view賦值的操做,這時候微信也同樣,咱們一樣打開as這個類的structure目錄

as.png
從圖中能夠發 a這個方法嫌疑很大,裏面有各類view

private void a(int i, QFadeImageView qFadeImageView, TextView textView, TextView textView2, TextView textView3, TextView textView4, int i2, d dVar, int i3) {
        n nVar = (n) getItem(i);
        TimeLineObject cmi = nVar.cmi();
        bys q = aj.q(nVar);
        Object obj = null;
        if (q != null && (((q.vUS & 2) == 2 && q.wnx != null) || ((q.vUS & 4) == 4 && q.vTG != null))) {
            obj = 1;
        }
        if (!(!this.cog || q == null || obj == null || this.userName == null || !this.userName.equals(nVar.field_userName))) {
            textView3.setBackgroundResource(com.tencent.mm.plugin.sns.i.e.personactivity_sharephoto_icon);
            textView3.setVisibility(0);
        }
        
 ........省略不少代碼........

複製代碼

咱們點進去能夠看到,果真如同咱們所想同樣,第一行就暴露了, n nVar = (n) getItem(i); 能夠發現這個n類就是每一個item的數據源,並且往下看一行,這個類名也頗有嫌疑TimeLineObject,並且是經過n 調用cmi方法返回的,那會不會是數據庫裏面的那個BLOB字段呢。容咱們點進去看看

Map<String, TimeLineObject> qAb = new ConcurrentHashMap();
     ........省略不少代碼........

 public final TimeLineObject cmi() {
        if (this.field_content == null) {
            return e.ahM();
        }
        TimeLineObject timeLineObject;
        if (this.qzT == null) {
            this.qzT = g.u(this.field_content) + g.u(this.field_attrBuf);
        }
        if (qAb.containsKey(this.qzT)) {
            timeLineObject = (TimeLineObject) qAb.get(this.qzT);
            if (timeLineObject != null) {
                return timeLineObject;
            }
        }
        try {
            timeLineObject = (TimeLineObject) new TimeLineObject().parseFrom(this.field_content);
            qAb.put(this.qzT, timeLineObject);
            return timeLineObject;
        } catch (Exception e) {
            ab.e("MicroMsg.SnsInfo", "error get snsinfo timeline!");
            return e.ahM();
        }
    }

複製代碼

這時候發現了難以想象的東西 field_contentfield_attrBuf 這兩個字段,這個不就是數據庫裏面定義的content和attrbuf字段嗎。原來是經過這樣的方式轉化的。繼續往下看,能夠發現若是 ' qAb.get(this.qzT) ' 拿不到TimeLineObject的話,就會本身new 一個而後存儲到這個Map集合中,作緩存做用,那咱們是否是能夠經過這種方式呢拿到這個TimeLineObject,固然是能夠的。

解碼朋友圈BLOB字段

經過上面咱們已經知道了如何解析朋友圈數據庫content 字段了,經過TimeLineObject的parseFrom方法進行轉化。可是當咱們點到TimeLineObject這個類裏面你會發現,臥槽,怎麼這樣。

public class TimeLineObject extends a {
    public String Id;
    public int dhE;
    public int eRm;
    public String hPC;
    public String jfn;
    public int ozl;
    public String qEy;
    public String qXr;
    public av qiN;
    public csw qiP;
    public String uzJ;
    public int vTa;
    public int wsA;
    public String wsB;
    public ccr wsC;
    public cqv wsD;
    public int wsE;
    public String wsu;
    public axc wsv;
    public du wsw;
    public ta wsx;
    public String wsy;
    public int wsz;

複製代碼

能夠發現裏面嵌套了不少的類。這是我時候咱們怎麼作呢。我也沒有很好的辦法,而後就經過一個類一個類的點開,而後打印它裏面是String字段,這裏應該來個表情捂臉,有好方法的同窗們能夠留言區交流,可是當我打印到了wsu這個字段的時候發現,能夠拿獲得咱們發朋友圈時的標題了。瞬間感受到了輕鬆。可是這只是一個很小的進步,咱們要拿的但是朋友圈圖片視頻評論點贊全部內容啊。

獲取朋友圈信息源具體內容

那麼如何獲取到圖片視頻等信息呢。那麼咱們仍是要回到adapter 這個類裏面。在他的a方法中有個構造參數QFadeImageView看到ImageView 感受離獲取圖片地址不遠了。那麼咱們繼續觀察這個方法。

private void a(int i, QFadeImageView qFadeImageView, TextView textView, TextView textView2, TextView textView3, TextView textView4, int i2, d dVar, int i3) {
        n nVar = (n) getItem(i);
        TimeLineObject cmi = nVar.cmi();
        bys q = aj.q(nVar);
        Object obj = null;
        if (q != null && (((q.vUS & 2) == 2 && q.wnx != null) || ((q.vUS & 4) == 4 && q.vTG != null))) {
            obj = 1;
        }
        ........省略不少代碼........
        if (cmi.wsx.vqt == 1) {
            qFadeImageView.setVisibility(0);
            af.cjr().a(cmi.wsx.vqu, (View) qFadeImageView, this.coM.hashCode(), com.tencent.mm.plugin.sns.model.g.a.IMG_SCENE_SNSSUSER, azVar);
        } else if (cmi.wsx.vqt == 2) {
            textView4.setText(bp.bb(cmi.wsx.Desc, ""));
            textView4.setVisibility(0);
        } else if (cmi.wsx.vqt == 21) {
            nVar.cmA();
            boolean z = true;
            if (this.cog) {
                z = true;
            } else if (m.a(nVar, q)) {
                z = false;
            }
            qFadeImageView.setVisibility(0);
            af.cjr().a(cmi.wsx.vqu, (View) qFadeImageView, this.coM.hashCode(), com.tencent.mm.plugin.sns.model.g.a.IMG_SCENE_SNSSUSER, azVar, z);
        }
        ........省略不少代碼........

複製代碼

從上面的代碼中咱們能夠看獲得,微信這邊調用了這個方法把 af.cjr().a(xxxxx)把Imageview 傳入了進入,點擊查看了其餘的參數只有cmi.wsx.vqu這個參數有用。能夠發現他調用了TimeLineObject裏面ta這個類裏面的集合vqu字段。

public final class ta extends a {
    public String Desc;
    public String Title;
    public String Url;
    public int vqt;
    public LinkedList<azc> vqu = new LinkedList();
    public int vqv;
    public String vqw;
    public ayd vqx;
複製代碼

先無論調用了imageview 這個方法幹了什麼,咱們先把這個集合裏面的東西給打印出來。

azc.toString{id:13055580620160049319 Desc:黃昏 Title: Url:http://mmsns.qpic.cn/mmsns/kEgG3ynkRxponsiaCyzhl9Gniaz7GdWLujZdSMV4kmlME6fia07m577MQ3OU9kMKibjAIiaMc7MWHKF0/0 ckO:null qDg: vSY:http://mmsns.qpic.cn/mmsns/kEgG3ynkRxponsiaCyzhl9Gniaz7GdWLujZdSMV4kmlME6fia07m577MQ3OU9kMKibjAIiaMc7MWHKF0/150 vTc: vTf: vTh: vTi: vTj: vTm:f814a6351db8cd3ef118e14e6ff70b80 vTn:WSEN6qDsKwV8A02w3onOGQYfxnkibdqSOkmHhZGNB4DFJ9qdBeATTF8UiaDA1go3GLryav2ukPJK06SOFjchiaqJA vTp:14604729124651202068 vTq:WSEN6qDsKwV8A02w3onOGQYfxnkibdqSOkmHhZGNB4DFJ9qdBeATTF8UiaDA1go3GLwHGCkbxWxDWW5dsMhLsBUg vTs:14604729124651202068 vTt:}

複製代碼

不出咱們所料,裏面果真有咱們須要的東西,並且把Desc,url都給了咱們,經過個人測試發現圖片的url並不能打開。可是視頻連接的url是能夠打開的。是否是很happy。這時候咱們已經拿到了視頻源和分享的連接源,可是如何拿到圖片源呢。那咱們繼續查看源碼,發現點擊剛剛給上述調用了這個集合的方法裏面並無什麼東西,都是一些賦值的操做。沒有獲取具體圖片源信息的位置。

這時候我是這樣操做的,由於這個獲取到的Url雖然咱們解析不了,可是咱們能夠點擊查看Url的引用,看哪裏有着對這個Url字段的引用,微信內部是如何解析的 當你點擊開的時候會發現並無什麼用處,見下圖

url.png

引用的地方太多了,根本不知道如何下手。這時候又會一頭霧水不知道如何進行下一步。那麼咱們只能回到起點,再次尋找下一個hook點。

進入朋友圈詳情二次Hook

既然上一個頁面咱們拿不到圖片的數據,那咱們就深刻進入朋友圈具體內容頁面,而後進行二次Hook,獲取到朋友圈具體圖片內容。咱們仍是以一樣的方法進行,hook,打開此頁面而後經過 dumpsys 獲取當前的activity,進入這個activity查看源碼下手找到圖片的具體位置。

經過代碼查看到這是一個com.tencent.mm.plugin.sns.ui.SnsGalleryUI,顧名思義,朋友圈相冊頁面。那麼咱們點進去看看具體寫了些什麼東西

public class SnsGalleryUI extends SnsBaseGalleryUI implements a {
    private int qKn = 0;
    private String userName = "";

    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        getWindow().addFlags(128);
        wS(this.mController.xyi.getResources().getColor(c.dark_actionbar_color));
        LV(this.mController.xyi.getResources().getColor(c.dark_actionbar_color));
        initView();
    }
........省略不少代碼........


複製代碼

並無什麼東西可看的。沒有具體的內容,咱們進入父類SnsBaseGalleryUI看看作了些什麼進入看了一下,發現方法體也沒有什麼東西,可是一個字段引發了個人注意

public abstract class SnsBaseGalleryUI extends MMActivity implements a {
    private boolean jop = true;
    private LinearLayout qKf;
    r qKg;
    private LinearLayout qKh;
    s qKi;
    private boolean qKj = true;
    private TextView qKk = null;
    protected SnsInfoFlip qKl;
    protected Button qKm;
........省略不少代碼........
複製代碼

咱們從上面的字段能夠猜到哪個是引發了個人注意,很明顯那就是SnsInfoFlip這個類。繼續點擊去看看裏面寫了些什麼內容。據我猜想這應該也是個adapter,viewpager的adapter,經過structure目錄能夠發現裏面有個getView的方法,可是反編譯並無徹底把smail 內容轉換成java 類型,看着比較費勁,那咱們繼續往下尋找,這時候你會有驚喜

sns.png

能夠看到這個函數裏面參數是azc記憶力好的小夥伴應該還記得,前面所說的,azc這個類裏面包含着url,這時候找到了用的地方了。把代碼貼上你們能夠看一下

private void a(azc azc, int i, String str) {
        String str2;
        long j = 0;
        if (this.qNm != null && (this.qNm instanceof MMGestureGallery)) {
            float f;
            float f2;
            float f3;
            if (azc.vTb != null) {
                f = azc.vTb.vTP;
                f2 = azc.vTb.vTO;
            } else {
                f = 0.0f;
                f2 = 0.0f;
            }
            if (f <= 0.0f || r5 <= 0.0f) {
                if (azc.Id.startsWith("Locall_path")) {
                    str2 = an.fQ(af.getAccSnsPath(), azc.Id) + com.tencent.mm.plugin.sns.data.i.m(azc);
                } else {
                    str2 = an.fQ(af.getAccSnsPath(), azc.Id) + com.tencent.mm.plugin.sns.data.i.d(azc);
                }
                Options akG = com.tencent.mm.sdk.platformtools.d.akG(str2);
                ........省略不少代碼........

複製代碼

上面的字段local_path,博主感受找到了那個點,開始hook這個method an.fQ(af.getAccSnsPath(), azc.Id) + com.tencent.mm.plugin.sns.data.i.m(azc);,surprise這個方法返回的值就是朋友圈圖片的本地路徑。打印下來是這樣的路徑

/storage/emulated/0/tencent/MicroMsg/cd1c67e86728a83c6079e2b48ia/sns/1/4/snst_13069507190820253764
//這個路徑經過adb pull 出來,就是一張圖片
複製代碼

總結

經過以上的分析,咱們已經定位到了哪些類和哪些方法能夠幫助咱們獲取到朋友圈的數據。

接下來咱們要作的事爬取朋友圈數據,這裏兩種方式爬取數據

  1. xpose

這種方式是最簡單粗暴的,hook微信的內部方法

首先經過sqlite拿到SnsMicroMsg.db
而後query SnsInfo這個表裏面的數據content
經過XposedHelpers 實例化TimeLineObject
調用TimeLineObject的parseFrom方法(可獲取朋友圈標題)
而後獲取TimeLineObject裏面的ta類,獲取到集合azc(視頻連接可直接經過這個類獲取)
而後循環這個集合,經過SnsInfoFlip裏面的a方法獲取到thumb的地址。
複製代碼
  1. classloader

這種方式呢,也是可行的,只須要手機root便可不須要xpose框架

首先把當前微信目錄下的SnsMicroMsg這個數據庫導出到Sdcard中
而後同樣經過sqlite打開數據庫,query SnsInfo table
接着DexClassLoader把微信apkloader起來,同時把咱們上訴介紹到的類都給load進去。
而後經過反射的方式執行第一種xpose 方式也行。
複製代碼

經過以上兩種方式皆可完成爬朋友圈數據功能,下面拋個爬取數據成功的頁面。

detail.png
相關文章
相關標籤/搜索