├── MetadataCrawler
├── MiniAppsSearch.py
├── db.py
├── defaultWords.txt
├── httpGetData.txt
├── main.py
└── wordsplits.py
├── README.md
└── XposedPlugin
├── .gitignore
├── .idea
├── caches
│ ├── build_file_checksums.ser
│ └── gradle_models.ser
├── codeStyles
│ └── Project.xml
├── encodings.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
└── runConfigurations.xml
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── vsa
│ │ └── xposedutility
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── xposed_init
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── vsa
│ │ │ └── xposedutility
│ │ │ ├── ICases.java
│ │ │ ├── MLog.java
│ │ │ ├── Utilities.java
│ │ │ ├── XEntry.java
│ │ │ ├── tests
│ │ │ ├── MyXposedHelper.java
│ │ │ ├── Wechat7020.java
│ │ │ ├── WechatMiniAppsDownloader.java
│ │ │ └── WechatSearchToken.java
│ │ │ └── xposedUtility.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_xposed_utility.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── vsa
│ └── xposedutility
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/MetadataCrawler/MiniAppsSearch.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import time
3 | import json
4 | import urllib
5 |
6 | import string
7 |
8 | import random
9 |
10 | with open("httpGetData.txt") as f:
11 | data0 = f.read()
12 |
13 | headers ={
14 | "content-type":"application/x-www-form-urlencoded",
15 | "User-Agent":"Mozilla/5.0 (Linux; Android 7.1; Nexus 6p ) AppleWebKit/537.36 (KHTML, like Gecko) Version/8.0 Chrome/37.0.0.0 Mobile Safari/537.36 MicroMessenger/6.7.3.1341(0x26) NetType/WIFI Language/en Process/appbrand0",
16 | "charset":"utf-8",
17 | "Accept-Encoding":"gzip, deflate",
18 | "Connection":"close"
19 | }
20 |
21 | url = 'https://mp.weixin.qq.com/wxa-cgi/innersearch/subsearch'
22 |
23 | def _request(data):
24 | global url, headers
25 | retry = 3
26 | while retry > 0:
27 | retry -= 1
28 | try:
29 | time.sleep(random.random()*2 + 2)
30 | resp = requests.post(url,data=data, headers=headers)
31 | break
32 | except Exception, e:
33 | print '[-]', e
34 | with open('errors.txt','w') as f:
35 | f.write('%s -- %s\n' % (e, data))
36 |
37 | with open('lastreq.txt','w') as f:
38 | f.write(resp.text.encode('utf8'))
39 | return resp
40 |
41 |
42 | def _request_by_response(respjs, olddata, onitems=None):
43 |
44 | while respjs['respBody']['continueFlag']:
45 |
46 | o_pre, o_suc = olddata.split('offset_buf=')
47 | o_suc = o_suc.split('&',1)[1]
48 | mid = 'offset_buf=%s&' % urllib.quote_plus(respjs['respBody']['offsetBuf'])
49 |
50 | olddata = o_pre + mid + o_suc
51 |
52 | resp = _request(olddata)
53 | respjs = json.loads(resp.text)
54 |
55 | if onitems != None: onitems(respjs['respBody']['items'])
56 |
57 | if 'offsetBuf' in respjs['respBody']: print respjs['respBody']['offsetBuf']
58 |
59 | def _request_by_word(word, onitems=None):
60 |
61 | word = urllib.quote_plus(word.encode('utf8'))
62 |
63 | tdata = data0
64 | tdata = tdata.replace('%%QUERY%%', word)
65 |
66 | resp = _request(tdata)
67 | respjs = json.loads(resp.text)
68 |
69 | if 'respBody' not in respjs:
70 | with open('norespBody.txt','w') as f:
71 | f.write('%s -- %s\n' % (word, tdata))
72 | return
73 |
74 | if onitems != None: onitems(respjs['respBody']['items'])
75 |
76 |
77 | _request_by_response(respjs, tdata, onitems)
78 |
--------------------------------------------------------------------------------
/MetadataCrawler/db.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | import os
3 | import thread
4 |
5 | DBFILE = os.path.dirname(os.path.abspath(__file__)) +'/data.db'
6 | DBLOCK = thread.allocate_lock()
7 |
8 | if not os.path.exists(DBFILE):
9 | conn = sqlite3.connect(DBFILE, check_same_thread=False)
10 | cur = conn.cursor()
11 | cur.execute('CREATE TABLE miniapps(docID, nickName, iconUrl, userName, description, path, scoreTfIdf, \
12 | scoreQuailty, appid PRIMARY KEY, extra_json, appuin, keyword, insertTime);')
13 | cur.execute('CREATE TABLE searched(wid INTEGER, words PRIMARY KEY);')
14 | conn.commit()
15 | else:
16 | conn = sqlite3.connect(DBFILE, check_same_thread=False)
17 | cur = conn.cursor()
18 |
19 | conn.row_factory = sqlite3.Row
20 |
21 | def add_new_apps(item, keyword):
22 | with DBLOCK:
23 | try:
24 | SQLins = "INSERT INTO miniapps (docID, nickName, iconUrl, userName, description, path, scoreTfIdf, \
25 | scoreQuailty, appid, extra_json, appuin, keyword, insertTime) VALUES (?,?,?,?,?,?,?,?,?,?,?,?, CURRENT_TIMESTAMP)"
26 | cur.execute(SQLins, (item['docID'], item['nickName'], item['iconUrl'], item['userName'], item['description'], item['path'], \
27 | item['scoreTfIdf'], item['scoreQuailty'], item['appid'], item['extra_json'], item['appuin'], keyword))
28 | conn.commit()
29 | except Exception, e:
30 | if "UNIQUE constraint failed" not in str(e):
31 | print '[-]', e
32 |
33 | def get_apps():
34 | cursor = conn.execute("SELECT docID, nickName, iconUrl, userName, description, path, scoreTfIdf, \
35 | scoreQuailty, appid, extra_json, appuin, keyword FROM miniapps")
36 | return cursor
37 |
38 | def get_apps_count():
39 | cursor = conn.execute("SELECT count(appid) FROM miniapps")
40 | return cursor.fetchall()[0][0]
41 |
42 | def add_searched_word(word):
43 | with DBLOCK:
44 | try:
45 | SQLins = "INSERT INTO searched (wid, words) VALUES ((SELECT IFNULL(MAX(wid), 0) + 1 FROM searched), ?)"
46 | cur.execute(SQLins, (word,))
47 | conn.commit()
48 | except Exception, e:
49 | print '[-]', e
50 |
51 | def get_searched_words():
52 | cursor = conn.execute("SELECT wid, words FROM searched")
53 | return cursor
--------------------------------------------------------------------------------
/MetadataCrawler/defaultWords.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OSUSecLab/MiniCrawler/2415b58b25189c62074893a196ecbd42c0d5ed6a/MetadataCrawler/defaultWords.txt
--------------------------------------------------------------------------------
/MetadataCrawler/httpGetData.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OSUSecLab/MiniCrawler/2415b58b25189c62074893a196ecbd42c0d5ed6a/MetadataCrawler/httpGetData.txt
--------------------------------------------------------------------------------
/MetadataCrawler/main.py:
--------------------------------------------------------------------------------
1 |
2 | import db
3 | import MiniAppsSearch
4 | import wordsplits
5 |
6 |
7 | words = {}
8 |
9 | with open("defaultWords.txt") as f:
10 | for i in f:
11 | i = i.strip()
12 | words[i] = 1
13 |
14 |
15 | searched = set()
16 | print db.get_apps_count()
17 | for searched_words in db.get_searched_words():
18 | searched.add(searched_words['words'])
19 | print '[*] searched', len(searched)
20 |
21 |
22 | for appmeta in db.get_apps():
23 | nickName = appmeta['nickName'].lower()
24 |
25 | for i in wordsplits.words_from_miniapp(appmeta):
26 | if i not in searched:
27 | if i not in words:
28 | words[i] = 0
29 | words[i] += 1
30 |
31 | words = sorted(words.items(), key=lambda kv: kv[1], reverse=True)
32 | print '[*] words', len(words)
33 |
34 | lastfound = 0
35 | lastcount = 0
36 | def onitems(items):
37 | global lastfound, lastcount
38 | print len(items)
39 | for item in items:
40 | print lastcount, lastfound, item['appid'], item['nickName'], item['description']
41 | db.add_new_apps(item)
42 |
43 | for word, count in words:
44 | print 'XXXXXXXXXXXXXXXXX'
45 | print 'XXXXXXXXXXXXXXXXX'
46 | print 'XXXXXXXXXXXXXXXXX'
47 | print 'XXXXXXXXXXXXXXXXX'
48 | print word, count
49 | print 'XXXXXXXXXXXXXXXXX'
50 | print 'XXXXXXXXXXXXXXXXX'
51 | print 'XXXXXXXXXXXXXXXXX'
52 | print 'XXXXXXXXXXXXXXXXX'
53 | oldn = db.get_apps_count()
54 |
55 | MiniAppsSearch._request_by_word(word, onitems)
56 | db.add_searched_word(word)
57 |
58 | lastcount = db.get_apps_count()
59 | lastfound = lastcount - oldn
60 | print 'OOOOOOOOOOOOOOOOO'
61 | print 'OOOOOOOOOOOOOOOOO'
62 | print 'OOOOOOOOOOOOOOOOO'
63 | print 'OOOOOOOOOOOOOOOOO'
64 | print word, count
65 | print lastfound
66 | print 'OOOOOOOOOOOOOOOOO'
67 | print 'OOOOOOOOOOOOOOOOO'
68 | print 'OOOOOOOOOOOOOOOOO'
69 | print 'OOOOOOOOOOOOOOOOO'
--------------------------------------------------------------------------------
/MetadataCrawler/wordsplits.py:
--------------------------------------------------------------------------------
1 | # encoding=utf-8
2 | import jieba
3 |
4 | jieba.enable_paddle()
5 |
6 | def words_from_miniapp(miniapp):
7 | nickName = miniapp['nickName'].lower()
8 | description = miniapp['description'].lower()
9 |
10 | seg_listA = jieba.cut(nickName, cut_all=True)
11 | seg_listB = jieba.cut(description, cut_all=True)
12 |
13 | words = set(list(nickName) + list(description) + list(seg_listA) + list(seg_listB))
14 |
15 | return words
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## How to run it
2 | MiniCrawler is a scalable and open source WeChat mini-app crawler, it contains two parts:
3 |
4 | **Xposed Plugin:** Extracting data (cookie) from WeChat to initialize the mini-app metadata crawler, and injecting downloading commands to WeChat.
5 |
6 | **Metadata Crawler:** Using keywords and cookie to send mini-app query messages to WeChat server and retrieve mini-app metadata.
7 |
8 | ## How to run it
9 |
10 | **Crawl Mini-app Metadata**
11 |
12 | 1. Install Xposed and WeChat 7.0.19 on your phone.
13 | 2. Compile and install XposedPlugin, and enable it on Xposed.
14 | 3. Run `adb logcat | grep WechatSearchToken` in terminal.
15 | 4. Use Wechat to search for mini-apps with any keyword.
16 | 5. In the terminal, you will find string `HTTP_GET_DATA` in format `begid=0&longitude=-...&latitude=...&client_version=...&query=...&...`. Replace the content of `query` with `%%QUERY%%` and put the string in file `httpGetData.txt`
17 | 6. Prepare the initial keywords that you want to search for mini-apps to file `defaultWords.txt`(one keyword per line).
18 | 7. Run script `main.py`, it will search for mini-app metadata and save them to a created database file `data.db`
19 |
20 | **Download Mini-apps**
21 |
22 | 1. Install Xposed and WeChat 7.0.20 on your phone.
23 | 2. Compile and install XposedPlugin, and enable it on Xposed.
24 | 3. Open any mini-app in WeChat.
25 | 4. For each mini-app you want download, prepare its `appid` (such as `wx5054764a3fdfb3b5`) which can be found in `data.db`.
26 | 5. Run command `adb shell am broadcast -a android.intent.myper --es appid "wx5054764a3fdfb3b5"`, and the WeChat will download this mini-app and save its version information and downloading URL to `/sdcard/apps.txt`
27 |
28 | ## Citation
29 |
30 | If you create a research work that uses our work, please cite our paper:
31 |
32 | ```
33 | @article{zhang2021measurement,
34 | title={A Measurement Study of Wechat Mini-Apps},
35 | author={Zhang, Yue and Turkistani, Bayan and Yang, Allen Yuqing and Zuo, Chaoshun and Lin, Zhiqiang},
36 | journal={Proceedings of the ACM on Measurement and Analysis of Computing Systems},
37 | volume={5},
38 | number={2},
39 | pages={1--25},
40 | year={2021},
41 | publisher={ACM New York, NY, USA}
42 | }
43 | ```
44 |
--------------------------------------------------------------------------------
/XposedPlugin/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 |
--------------------------------------------------------------------------------
/XposedPlugin/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OSUSecLab/MiniCrawler/2415b58b25189c62074893a196ecbd42c0d5ed6a/XposedPlugin/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/XposedPlugin/.idea/caches/gradle_models.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OSUSecLab/MiniCrawler/2415b58b25189c62074893a196ecbd42c0d5ed6a/XposedPlugin/.idea/caches/gradle_models.ser
--------------------------------------------------------------------------------
/XposedPlugin/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/XposedPlugin/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/XposedPlugin/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/XposedPlugin/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/XposedPlugin/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/XposedPlugin/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/XposedPlugin/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/XposedPlugin/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.example.vsa.xposedutility"
7 | minSdkVersion 19
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 | repositories {
21 | jcenter()
22 | }
23 | dependencies {
24 | implementation 'com.google.code.gson:gson:2.8.2'
25 | implementation fileTree(include: ['*.jar'], dir: 'libs')
26 | implementation 'com.android.support:appcompat-v7:28.0.0'
27 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
28 | testImplementation 'junit:junit:4.12'
29 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
30 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
31 | compileOnly 'de.robv.android.xposed:api:82'
32 | //compileOnly 'de.robv.android.xposed:api:82:sources'
33 | }
34 |
--------------------------------------------------------------------------------
/XposedPlugin/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/androidTest/java/com/example/vsa/xposedutility/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.example.vsa.xposedutility;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.example.vsa.xposedutility", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
29 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/assets/xposed_init:
--------------------------------------------------------------------------------
1 | com.example.vsa.xposedutility.XEntry
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/java/com/example/vsa/xposedutility/ICases.java:
--------------------------------------------------------------------------------
1 | package com.example.vsa.xposedutility;
2 |
3 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
4 |
5 | public interface ICases {
6 | public void hook(XC_LoadPackage.LoadPackageParam pparam);
7 | }
8 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/java/com/example/vsa/xposedutility/MLog.java:
--------------------------------------------------------------------------------
1 | package com.example.vsa.xposedutility;
2 |
3 | import android.util.Log;
4 |
5 | public class MLog {
6 | public static void d(String tag, String msg) {
7 |
8 | if (msg.length() > 2000) {
9 | Log.v(tag, "sb.length = " + msg.length());
10 | int chunkCount = msg.length() / 2000; // integer division
11 | for (int i = 0; i <= chunkCount; i++) {
12 | int max = 2000 * (i + 1);
13 | if (max >= msg.length()) {
14 | Log.v(tag, msg.substring(2000 * i));
15 | } else {
16 | Log.v(tag, msg.substring(2000 * i, max));
17 | }
18 | }
19 | } else {
20 | Log.v(tag, msg.toString());
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/java/com/example/vsa/xposedutility/Utilities.java:
--------------------------------------------------------------------------------
1 | package com.example.vsa.xposedutility;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.File;
7 | import java.io.FileReader;
8 | import java.io.FileWriter;
9 | import java.io.IOException;
10 | import java.io.OutputStreamWriter;
11 |
12 | import de.robv.android.xposed.XC_MethodHook;
13 |
14 | public class Utilities {
15 |
16 | public static void printParameter(String TAG, XC_MethodHook.MethodHookParam param) {
17 | int i = 0;
18 | if (param.args != null && param.args.length > 0)
19 | for (Object arg : param.args) {
20 | MLog.d(TAG, "arg:" + i++ + ":" + arg + ":-:" + (arg==null?"":arg.getClass()));
21 |
22 | if(arg instanceof Object[]){
23 | for (Object aa :(Object[]) arg){
24 | MLog.d(TAG, " subarg:" + aa);
25 | }
26 | }
27 |
28 | }
29 |
30 | if(param.getResult()!=null)
31 | Log.d(TAG, "ret:" + i++ + ":" + param.getResult() + ":-:" + param.getResult().getClass());
32 |
33 | }
34 |
35 | public static void printStackTrace(String TAG) {
36 | Log.d(TAG, "printStackTrace:");
37 | for (StackTraceElement tr : new Exception().getStackTrace()) {
38 | Log.d(TAG, " " + tr);
39 | }
40 | }
41 |
42 | public static void writeToFile(String fpath, String data) {
43 | FileWriter writer = null;
44 | try {
45 | writer = new FileWriter(fpath, true);
46 | writer.write(data);
47 | writer.flush();
48 | writer.close();
49 | } catch (IOException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 | public static String getpidname() {
55 | final File cmdline = new File("/proc/" + android.os.Process.myPid() + "/cmdline");
56 | try (BufferedReader reader = new BufferedReader(new FileReader(cmdline))) {
57 | return cleanTextContent(reader.readLine());
58 | } catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 | return "";
62 | }
63 |
64 | private static String cleanTextContent(String text)
65 | {
66 | // strips off all non-ASCII characters
67 | text = text.replaceAll("[^\\x00-\\x7F]", "");
68 |
69 | // erases all the ASCII control characters
70 | text = text.replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", "");
71 |
72 | // removes non-printable characters from Unicode
73 | text = text.replaceAll("\\p{C}", "");
74 |
75 | return text.trim();
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/java/com/example/vsa/xposedutility/XEntry.java:
--------------------------------------------------------------------------------
1 | package com.example.vsa.xposedutility;
2 |
3 | import com.example.vsa.xposedutility.tests.Wechat7020;
4 | import com.example.vsa.xposedutility.tests.WechatMiniAppsDownloader;
5 | import com.example.vsa.xposedutility.tests.WechatSearchToken;
6 |
7 | import de.robv.android.xposed.IXposedHookLoadPackage;
8 | import de.robv.android.xposed.XposedBridge;
9 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
10 |
11 | public class XEntry implements IXposedHookLoadPackage {
12 | @Override
13 | public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
14 | XposedBridge.log("xloading.. " + lpparam.packageName +" -- " + Utilities.getpidname());
15 |
16 | new WechatSearchToken().hook(lpparam);
17 | new Wechat7020().hook(lpparam);
18 | new WechatMiniAppsDownloader().hook(lpparam);
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/java/com/example/vsa/xposedutility/tests/MyXposedHelper.java:
--------------------------------------------------------------------------------
1 | package com.example.vsa.xposedutility.tests;
2 |
3 |
4 | import android.util.Log;
5 |
6 | import com.example.vsa.xposedutility.Utilities;
7 |
8 | import java.lang.reflect.Constructor;
9 | import java.lang.reflect.Member;
10 | import java.lang.reflect.Method;
11 | import java.util.HashSet;
12 |
13 | import de.robv.android.xposed.XC_MethodHook;
14 | import de.robv.android.xposed.XposedBridge;
15 |
16 | public class MyXposedHelper {
17 |
18 | public static HashSet IGNORES = new HashSet();
19 |
20 | static{
21 | IGNORES.add("notifyAll");
22 | IGNORES.add("equals");
23 | IGNORES.add("notify");
24 | IGNORES.add("hashCode");
25 | IGNORES.add("wait");
26 | IGNORES.add("getClass");
27 | IGNORES.add("toString");
28 | }
29 |
30 | public static HashSet hookAllbut(ClassLoader classLoader, String classp, HashSet ignores, XC_MethodHook callback){
31 | HashSet hooked = new HashSet();
32 | try {
33 | Class clazz = classLoader.loadClass(classp);
34 | for(Method md : getAllMethods(clazz)) {
35 | if (ignores == null || (ignores != null && !ignores.contains(md.getName()))) {
36 | XposedBridge.hookMethod(md, callback);
37 | hooked.add(md);
38 | }
39 | }
40 | } catch (ClassNotFoundException e) {
41 | //e.printStackTrace();
42 | }
43 | return hooked;
44 | }
45 |
46 | public static HashSet hookAll(ClassLoader classLoader, String classp, HashSet shoulds, XC_MethodHook callback){
47 | HashSet hooked = new HashSet();
48 | try {
49 | Class clazz = classLoader.loadClass(classp);
50 | for(Method md : getAllMethods(clazz)) {
51 | if (shoulds == null || (shoulds != null && shoulds.contains(md.getName()))) {
52 | XposedBridge.hookMethod(md, callback);
53 | hooked.add(md);
54 | }
55 | }
56 | } catch (ClassNotFoundException e) {
57 | //e.printStackTrace();
58 | }
59 | return hooked;
60 | }
61 |
62 | public static HashSet hookAllDeclaredMethods(ClassLoader classLoader, String classp, HashSet shoulds, XC_MethodHook callback){
63 | HashSet hooked = new HashSet();
64 | try {
65 | Class clazz = classLoader.loadClass(classp);
66 | for(Member md : getDeclaredMethods(clazz)) {
67 | if (shoulds == null || (shoulds != null && shoulds.contains(md.getName()))) {
68 | XposedBridge.hookMethod(md, callback);
69 | hooked.add(md);
70 | }
71 | }
72 | } catch (ClassNotFoundException e) {
73 | //e.printStackTrace();
74 | }
75 | return hooked;
76 | }
77 |
78 | public static HashSet hookAllConstructor(ClassLoader classLoader, String classp, HashSet shoulds, XC_MethodHook callback){
79 | HashSet hooked = new HashSet();
80 | try {
81 | Class clazz = classLoader.loadClass(classp);
82 | for (Member md : clazz.getDeclaredConstructors()) {
83 | if (shoulds == null || (shoulds != null && shoulds.contains(md.getName()))) {
84 | XposedBridge.hookMethod(md, callback);
85 | hooked.add(md);
86 | }
87 | }
88 | } catch (ClassNotFoundException e) {
89 | //e.printStackTrace();
90 | }
91 | return hooked;
92 | }
93 |
94 | public static HashSet hookbyname(ClassLoader classLoader, String classp, String mname, int argscount, XC_MethodHook callback){
95 | HashSet hooked = new HashSet();
96 | try {
97 | Class clazz = classLoader.loadClass(classp);
98 | for(Method md : getAllMethods(clazz)) {
99 |
100 | if (md.getName().equals(mname) &&(argscount == -1 || md.getParameterTypes().length == argscount)) {
101 | XposedBridge.hookMethod(md, callback);
102 | hooked.add(md);
103 | }
104 | }
105 | } catch (ClassNotFoundException e) {
106 | //e.printStackTrace();
107 | }
108 | return hooked;
109 | }
110 |
111 |
112 | public static HashSet getAllMethods(Class clazz){
113 | HashSet ms = new HashSet();
114 | for (Method md : clazz.getDeclaredMethods()) {
115 | ms.add(md);
116 | }
117 | for (Method md : clazz.getMethods()) {
118 | ms.add(md);
119 | }
120 | return ms;
121 | }
122 |
123 | public static HashSet getDeclaredMethods(Class clazz){
124 | HashSet ms = new HashSet();
125 | for (Method md : clazz.getDeclaredMethods()) {
126 | ms.add(md);
127 | }
128 | for (Constructor md : clazz.getDeclaredConstructors()) {
129 | ms.add(md);
130 | }
131 | return ms;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/java/com/example/vsa/xposedutility/tests/Wechat7020.java:
--------------------------------------------------------------------------------
1 | package com.example.vsa.xposedutility.tests;
2 |
3 | import android.util.Log;
4 |
5 | import com.example.vsa.xposedutility.ICases;
6 | import com.example.vsa.xposedutility.Utilities;
7 |
8 | import java.lang.reflect.Constructor;
9 | import java.lang.reflect.Member;
10 | import java.lang.reflect.Method;
11 | import java.security.SecureClassLoader;
12 | import java.util.HashSet;
13 |
14 | import dalvik.system.BaseDexClassLoader;
15 | import de.robv.android.xposed.XC_MethodHook;
16 | import de.robv.android.xposed.XposedBridge;
17 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
18 |
19 | public class Wechat7020 implements ICases {
20 | static String TAG = Wechat7020.class.getSimpleName();
21 |
22 | @Override
23 | public void hook(final XC_LoadPackage.LoadPackageParam pparam) {
24 | if (!pparam.packageName.equals("com.tencent.mm")) return;
25 |
26 | hookAll(pparam, pparam.classLoader);
27 | final String packageName = pparam.packageName;
28 | Class[] loaderClassList = {
29 | BaseDexClassLoader.class,
30 | SecureClassLoader.class,
31 | };
32 |
33 | for (final Class loaderClass : loaderClassList) {
34 | XposedBridge.hookAllConstructors(loaderClass, new XC_MethodHook() {
35 | @Override
36 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
37 | ClassLoader classLoader = (ClassLoader) param.thisObject;
38 |
39 | hookAll(pparam, classLoader);
40 |
41 | }
42 | });
43 | }
44 | }
45 | static boolean did = false;
46 | public void hookAll(XC_LoadPackage.LoadPackageParam pparam, ClassLoader classLoader) {
47 |
48 |
49 | XC_MethodHook cb =new XC_MethodHook() {
50 | protected void beforeHookedMethod(final MethodHookParam param) throws Throwable {
51 | Log.d(TAG, "---->>>:" + param.method.getName());
52 | Utilities.printParameter(TAG, param);
53 |
54 | if(param.method.getName().equals("invokeHandler") && !did){
55 | did = true;
56 |
57 | final Object thiobj = param.thisObject;
58 | final Method invokeHandler = thiobj.getClass().getMethod("invokeHandler",String.class, String.class, int.class);
59 |
60 |
61 | }
62 |
63 | if(param.method.getDeclaringClass().getName().equals("com.tencent.mm.plugin.appbrand.appcache.ba")) {
64 | if (param.method instanceof Constructor && ((Constructor) param.method).getParameterTypes().length == 4) {
65 |
66 | String appid = param.args[0] + "";
67 | String version = param.args[2] + "";
68 | String url = param.args[3] + "";
69 |
70 | Utilities.writeToFile("/sdcard/apps.txt", appid + " " + version + " " + url + "\n");
71 | }
72 | }
73 |
74 | if(param.method.getDeclaringClass().getName().equals("com.tencent.mm.plugin.appbrand.jsapi.l")) {
75 | if (!WechatMiniAppsDownloader.wvs.contains(param.thisObject))
76 | WechatMiniAppsDownloader.wvs.add(param.thisObject);
77 | }
78 | }
79 | };
80 |
81 |
82 |
83 | HashSet clss = new HashSet<>();
84 |
85 | clss.add("com.tencent.mm.plugin.appbrand.jsapi.l");
86 |
87 | clss.add("com.tencent.mm.plugin.appbrand.appcache.ba");
88 |
89 |
90 |
91 | for(String cls : clss){
92 | for(Member md:MyXposedHelper.hookAllDeclaredMethods(classLoader, cls, null, cb)) {
93 | Log.d(TAG, "hooked:" + md);
94 | }
95 | }
96 |
97 |
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/XposedPlugin/app/src/main/java/com/example/vsa/xposedutility/tests/WechatMiniAppsDownloader.java:
--------------------------------------------------------------------------------
1 | package com.example.vsa.xposedutility.tests;
2 |
3 | import android.app.Activity;
4 | import android.content.BroadcastReceiver;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.IntentFilter;
9 | import android.os.Bundle;
10 | import android.util.Log;
11 |
12 | import com.example.vsa.xposedutility.ICases;
13 |
14 | import java.lang.reflect.InvocationTargetException;
15 | import java.lang.reflect.Method;
16 | import java.security.SecureClassLoader;
17 | import java.util.HashSet;
18 |
19 | import dalvik.system.BaseDexClassLoader;
20 | import de.robv.android.xposed.XC_MethodHook;
21 | import de.robv.android.xposed.XposedBridge;
22 | import de.robv.android.xposed.XposedHelpers;
23 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
24 |
25 | public class WechatMiniAppsDownloader implements ICases {
26 | static String TAG = WechatMiniAppsDownloader.class.getSimpleName();
27 |
28 | public static HashSet