The premise


In this article, I will mainly talk about some small skills I have mastered in reverse, and how to climb the process of obtaining the data of wechat moments step by step. About the reverse work of wechat, many friends may have done this, the most annoying thing is how to quickly locate a Hook point. Then there’s how to untangle the confused code. If you can Hook what you want, it will be very easy. I’m just here for Android wechat 7.0.3 version of the latest version 7.0.4

Living in such a big data era, of course, data is the most important, if as a merchant, you have more than N customers, then how to classify these merchants, the second is to achieve precision marketing, and then that is Money. The analysis of the circle of friends data will play a very important role. Put a Tag on each merchant. I am just a simple example, do not prevent you from reading the following article, so the nonsense is not said. Start coding

If you master the following content, you will be able to get friends circle pictures, videos, comments, likes and content you have seen before, even if he has closed circle of friends, as long as you have seen, that is in the database, we can get

The preparatory work


  • A root phone
  • A wechat Apk
  • Xpose tools
  • Jadx

I have attached links to all the tools above except mobile root, so you can directly download them. As for how to root the phone, as an Android developer, Google recommended using TWRP+SuperSu, many models can burn successfully. Lots of cases online. The use of Xpose I won’t go into too much detail here, some Api calls. Jadx is an essential tool, but you can also use other decompile tools (dex2jar,jeb, jD-gui….) I’m using Jadx, it’s really good to use.

Load the source code of wechat

The following steps will help you speed up the Hook point location

  1. Open the downloaded wechat Apk with Jadx, and the following page should be compiled successfully

  1. Save the compiled project as a Gradle project

  2. Use AS to open the saved Gradle project. AS can help you create a quick index, which is convenient for us to debug and find Hook points. There may be a better operation, there are students know the message area can exchange the following

With that done, much of the preparation is done.

Circle of Friends database

I’m just going to go over this briefly, but there’s also information on the Internet, SnsMicroMsg. Db, which is the database that holds our circle of friends data, SnsInfo, which is the table, We can be in/data/data/com. Tencent. The mm/MicroMsg/md5 (+ uin} {mm) account directory to find the file, and there is no the database encryption, can be directly Navicat has been opened, but WeChat chat record database is encrypted, You need to break the password to open it, the password has not changed or you can use {IMEI+UIN} MD5, back to the theme, but although you can get the circle of friends database, but you find that there is no use, all the data inside the encrypted, completely confused. Here you can look at the database file. Except you can see that userName and createTime are useful, the other fields are really confusing

Note that the second image, content and attrBuf, are all blobs. You can see that the data is hidden. Is there nothing we can do?! Of course, you can still get to, that is through the wechat source code to view, our next work is to see how wechat is to get this BLOB, how to parse. Let’s just do it his way. Let’s move on to the topic.

Hook up your circle of friends

Hook point here is the entry of personal circle of friends. Click the address book to enter the page of personal circle of friends, and then find which Activity this Activity is, and then check the source code of this page. So far, the Xpose tool has not been used, so how can we know which Activity this Activity is? In fact, the Android system provides us with a dumpsys tool, which can obtain the current Activity according to the package name, we only need ADB shell to log in

Click to go to the page for a person, and then call the code to get the current activity

dumpsys activity |grep -i com.tencent.mm
Copy the code

Open the wechat personal Moments page and execute the above code to get the current page. At this time, you can see the following in terminal

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

Copy the code

At this point, we already know where the personal moments page is. Next, we went to the wechat project just opened to find the SnsUserUI Activity. When we opened the page, we could find that a lot of code has been confused and difficult to understand, such AS A.A.A, B.C. A package name and method name. But let’s not panic this time. Next open the phone, we can see through the UI of the wechat page, this is a list, it is certainly not listView is recyclerView. This is when I open AS and find the class SnsUserUI, and then open his Structure directory backwards

And you can see that there’s a method called initView if you’re familiar with it, and we write code like this all the time, so let’s just click on it, and I’m not going to paste a lot of code here. You can decompile it if you are interested


 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, newa() {
            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(newOnItemClickListener() { public final void onItemClick(AdapterView<? > adapterView, View view, int i, long j) { } });Copy the code

When I open the observation, there’s a setAdapter in there, right! So let’s make sure that the data is in this Adapter, because when we’re writing the list, we’re rendering the data to the list through this Adapter, so let’s go ahead and click on this Adapter and see how it’s written.

Circle of friends Adapter in-depth

We have found the Adapter through the above method. We click on the field (this.qxz) and find that its class name is 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(); }... Omit a lot of code........Copy the code

Then how shall we find it next? Adapter has been obtained, is it still a little bit? Of course not, think about our daily write listView Adapter, is not in the getView to assign to the view operation, this time wechat is the same, we also open the as class structure directory

a


   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); }... Omit a lot of code........Copy the code

If we click on it, we can see that, as we thought, the first line is exposed, n nVar = (n) getItem(I); You can see that this class n is the data source for each item, and if you look down one line, this class name is also likely to be TimeLineObject, and it is returned by calling the CMI method with n, so it is not the BLOB field in the database. Let’s go in and have a look

Map<String, TimeLineObject> qAb = new ConcurrentHashMap(); . Omit a lot of code........ public final TimeLineObjectcmi() {
        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!");
            returne.ahM(); }}Copy the code

The two fields field_content and field_attrBuf are defined in the database. So that’s the way it was transformed. If ‘qab.get (this.qzt)’ does not get the TimeLineObject, it will create a new one and store it in the Map for caching. Of course you can.

Decodes circle BLOB field

We already know how to parse the Content field of the Moments database, using the parseFrom method of TimeLineObject. But when we click on this TimeLineObject class you’re like, oh, my god, what’s going on here.

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;

Copy the code

You can see that there are a number of nested classes. This is what we do when I’m here. I didn’t have a good way, so I opened it by class and printed the String field inside it. There should be an emoji to cover the face. Students with good methods can communicate in the message area, but when I printed the wSU field, I found that we could get the title of our moments. I felt relief in an instant. But this is only a small step forward. We are taking moments, pictures, videos, comments, likes, everything.

Get the specific content of the circle of friends information source

So how to get pictures and videos and other information. So we’re going to go back to the Adapter class. And in his a method he has a construct parameter QFadeImageView and he sees that ImageView is not far away from getting the address of the image. So let’s keep looking at this method.

 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; }... Omit a lot of code........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); }... Omit a lot of code........Copy the code

From the above code, we can see that wechat here calls this method to af.cjr().a (XXXXX) to the Imageview into, click to view the other parameters only cmea. Wsx.vqu parameter is useful. You can see that he calls the collection vqu field in the class TA in TimeLineObject.

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;
Copy the code

So regardless of what this imageView method does, let’s just print out what’s in this collection.

Azc. toString{id:13055580620160049319 Desc: dusk 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:}

Copy the code

As we expected, there was something we needed in it, and the Desc and URL were given to us. Through my test, I found that the URL of the image could not be opened. But the url of the video link is open. Is not very happy. At this time we have got the video source and share the link source, but how to get the picture source. So let’s go ahead and look at the source code, and we see that clicking on the method that just called this collection doesn’t have anything in it, it’s just some assignment. No location to get specific image source information.

At this time, I operate like this, because although we can not parse the Url obtained, we can click to view the Url reference, see where there is a reference to this Url field, how to parse wechat internal when you click open, you will find that it is not useful, see the following picture

There are so many references, I don’t know where to start. And then you don’t know what to do next. So we can only go back to the starting point and look for the next hook point again.

Enter the circle of friends details two Hook

Since we could not get the data of pictures on the last page, we will go deep into the page of specific content of friends circle, and then Hook twice to get the specific content of pictures in friends circle. Let’s do it in the same way, hook, open the page and get the current activity through dumpsys, enter the activity and check the source code to find the specific location of the image.

Through code view to this is a com. Tencent. Mm. Plugin. SNS. UI. SnsGalleryUI, as the name implies, circle of friends photo gallery page. So let’s go in and see what it says. Okay

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(); }... Omit a lot of code........Copy the code

There wasn’t much to see. There’s no specific content, so we go into the parent SnsBaseGalleryUI and see what we’ve done and we go in and there’s nothing in the method body either, but there’s one field that catches my eye, right

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; . Omit a lot of code........Copy the code

As we can guess from the fields above which one caught my eye, it was clearly the class SnsInfoFlip. Click on it to see what it says. I guess this is also an adapter, a Viewpager adapter, and you can see from the Structure directory that there is a getView method, but the decompile doesn’t completely convert the smail contents to Java types, so it’s hard to see, so let’s go ahead and look for it. This is where you get a surprise

And you can see that the parameter in this function is azc and if you have a good memory you’ll remember that the class azc contains the URL, so now you’ve found a place to use it. I’ll post the code so you can look at it


  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.0 f; F2 = 0.0 f; }if(f < = 0.0 f | | r5 < = 0.0 f) {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); . Omit a lot of code........Copy the code

The above fields local_path, bloggers feel found that point, began to hook this method an. FQ (af) getAccSnsPath (), azc. Id) + com. Tencent. Mm. Plugin. SNS. Data. I.m (azc); The value returned by the surprise method is the local path to the circle of friends image. It prints out like this

/storage/emulated/0/tencent/MicroMsg/cd1 c67e86728a83c6079e2b48ia/SNS/a quarter/snst_13069507190820253764 / / the path through the adb pull out, is a pictureCopy the code

conclusion

Through the above analysis, we have identified which classes and which methods can help us to obtain the data of friends circle.

So what we’re going to do is we’re going to crawl our circle of friends data, and there are two ways to crawl our circle of friends data

  1. xpose

This method is the most simple and crude internal method of hook wechat

The first step is to get SnsMicroMsg. Db from sqLite and then to query the content in the SnsInfo table which is instantiated with XposedHelpers TimeLineObject Call the parseFrom method on the TimeLineObject and get the ta class inside of the TimeLineObject, get the collection AZC (which is the class you can get the video link directly from) and loop through the collection, SnsInfoFlip is used to retrieve the thumb's address.Copy the code
  1. classloader

This approach, too, is feasible and requires only the root phone without the Xpose frame

First, export the SnsMicroMsg database in the current wechat directory to Sdcard, then open the database through sqLite, query SnsInfo table, and then DexClassLoader to the wechat APkloader. And we're going to add all the classes that we introduced to load. Then perform the first Xpose by reflection.Copy the code

Through the above two ways can be completed to climb circle of friends data function, the following throw a climb data successful page.