週末小實驗 反組譯漫畫人 apk
原本只是在寫爬蟲啊 反組譯看的眼睛好痛QQ
故事是這樣的
我之前都是用漫畫人在手機上看漫畫
最近發現一個很好用的漫畫 foss android app — Tachiyomi
他是一個漫畫爬蟲大集合
上有各種神奇的 source ,也可以直接看日文生肉
目前中文的來源他只有爬動漫之家
但是最近在重看醫龍 動漫之家沒有
所以想說來開個 pr 把漫畫人加進去
這樣就可以不用看一堆廣告惹(?
沒想到寫一寫還跑去反組譯人家的 apk
來記錄一下這個豆頁痛的過程還有用到的工具
寫一個 Tachiyomi extension
這個 app 模組化的很不錯
所以要自己在新增漫畫的 source 不算太難
使用的是 kotlin ,寫起來比 java 有趣多了
他的 extensions 都在這個 repo 裡
複製現有的 extension 改個名字就可以開始惹
以漫畫人為例,語系是 zh
,名字叫 manhuaren
先修改 src/zh/manhuaren/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: Manhuaren'
pkgNameSuffix = 'zh.manhuaren'
extClass = '.Manhuaren'
extVersionCode = 1
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"
然後就可以開始在 src/zh/manhuaren/src/eu/kanade/tachiyomi/extension/zh/manhuaren/Manhuaren.kt
寫主要的爬蟲邏輯了
主要有兩種爬法
第一種是直接 parse 漫畫網站的 html,第二種是看他有沒有 api 可以用
分別要繼承 en.kanade.tachiyomi.source.online
裡面的 ParsedHttpSource
以及 HttpSource
然後分別實做
{popularManga,latestUpdates,searchManga}{Request,Parse}
{mangaDetails,chapterListParse,pageList,imageUrl}Parse
總共 10 個 method
命名還算是清楚
參考一下別的 extension 很快就能理解他的用途
找出漫畫人的 api
他的 web 只有用到一個 api,其他頁面只能直接 parse html
所以 J 邊我想用漫畫人的 app 去找出其他 api,感覺比較有趣
先用 mitmproxy
開一個 http proxy 讓手機去連
就可以找出他的 api endpoint
以熱門漫畫為例,url 跟 GET 的參數長這樣
url = 'http://mangaapi.manhuaren.com/v2/manga/getCategoryMangas'params = {
'start': '0',
'limit': '20',
'sort': '0',
'subCategoryType': '0',
'subCategoryId': '0',
'gsm': 'md5',
'gft': 'json',
'gts': '2018-10-22+20:46:22',
'gak': 'android_manhuaren2',
'gat': '',
'gaui': '191909801',
'gui': '191909801',
'gut': '0',
'gsn': '3ea9c09bab84b1d25cfdc4e328a1a7bd'
}
試改了一下 start
跟 limit
結果直接 error
仔細觀察就發現gsn
這個參數是隨著其他參數變動的 MD5 hash
於是就需要反組譯一下他的 apk 看看這個 hash 是怎麼產生的
反組譯漫畫人的 apk
這邊使用的是 jadx
這個工具
通常反組譯出來的變數都很醜
像是很多地方變數都叫做 a
又會因為 scope 不同而代表不同的意思
所以 trace 的有點痛苦 QQ
以下是精簡過的流程
一開始先搜尋 gsn
可以找到這個
public class a {
public static final String k = "gsn";
public static final String l = "gsm";
public static final String m = "gft";
public static final String n = "gts";
public static final String o = "gak";
public static final String p = "gat";
public static final String q = "gaui";
public static final String r = "gui";
public static final String s = "gut";
然後用 a.k
下去搜尋
找到他在建構 http get 的參數長這個樣子
bVar.b("gsm", a.w);
bVar.b(a.m, a.x);
bVar.b(a.n, com.ilike.cartoon.module.sync.a.e());
bVar.b(a.o, a.y);
bVar.b(a.p, "");
bVar.b(a.q, com.ilike.cartoon.module.save.z.f() + "");
bVar.b(a.r, com.ilike.cartoon.module.save.z.b() + "");
bVar.b(a.s, com.ilike.cartoon.module.save.z.a() + "");
bVar.b(a.k, a.a(bVar.c()));
最後一行的 a.k
就是 "gsn"
後面的 a.a()
就是我們要找的 hash 產生的方法
也就是下面這一段
public static String a(Map<String, String> map) {
Object[] toArray = map.keySet().toArray();
Arrays.sort(toArray);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(C);
stringBuilder.append("GET");
for (Object obj : toArray) {
stringBuilder.append(obj);
try {
a(map, stringBuilder, obj);
} catch (UnsupportedEncodingException e) {
}
}
stringBuilder.append(C);
return aa.d(stringBuilder.toString());
}
最後把他改成 kotlin 就完成惹
結語
原本以為只是一個簡單的爬蟲
結果花了一整個晚上在研究 a.a
跟 b.a
是什麼😢