少し前に、宝物を集めて収集するタスクを実行する Alipay の小さなプログラムを思いつきました...
とても良いです。すべてのタスクを取得して、タスクを自動的に実行できるツールを作りたいです。パッケージをキャプチャしたときに署名を検証するためにこの投稿を思いついたのですが、パッケージを
キャプチャした後、アプレット全体が https を使用してデータを送信していることがわかりました。
クラッキングの代替方法の目的
Xiaobai には Xiaobai のメソッドが必要です。アプレットを降参させ、サインで使用されるすべてのパラメータを素直に教えてもらいます。
0x0 携帯電話環境
- アンドロイド11
- K40安定版/miui12/BLロック解除
- マスクのブラシ化 (移動証明書のブラシ化)
- LSPが提供するフレームワーク
0x1 が使用するツール
- 携帯端末
- Drony 1.3.154 (機能: リクエストを指定された IP: ポートに転送するようにラダーを構築する必要があります。ダウンロード アドレス: フォーラムに投稿: https://www.52pojie.cn/thread-1340770-1-1.html)
- アリペイ
- 黄色い小鳥のバッグ
- MTマネージャー(ファイル検索などのジョブ)
- コンピューター
- fiddler (以降の部分を fd と呼びます)
- Notepad++ (JSTool プラグイン、ワンクリックの美化/ズーム JS コード) [オプション]
- 解凍ソフト
0x2は復号化処理を実現します
1. Alipayミニプログラムキャプチャパッケージ
黄色い小鳥はバッグを取るのが面倒なので、フィドラー+ドロニーに変えてバッグを取りたい
当初はLittle Yellow Birdアプリを使ってパケットをキャプチャしていました 全てキャプチャできましたが、インジェクターを使うのが面倒だったので、パソコンのFDを使ってパケットをキャプチャしようと考えました 以前はDronyを使っていましたが、Androidに乗り換えてから11, 古いバージョンでは Wi-Fi を取得できませんでした
。リストを更新しました。Drony の使用方法は以前に説明しました。必要な場合は、自分でジャンプできます: fiddler+drony キャプチャ パッケージ
フィドラー+ドロニー キャプチャ パケット
ドロニーを設定した後、転送をオンにし、コンピューターで fd を開き、携帯電話で Alipay アプリを開いてアプレットに入ります。fd はパッケージの記号 6e2edd36a36df5609e71e22a6fb41987 をキャッチしました
。
タスクリスト取得とは、ヘッダーにsign/token/などの情報があることを意味します。
Task/list? を直接開くと、プロンプトが表示されます{"code":-2001,"msg":"签名错误"}
。これにより、sign が署名検証であるという考えがさらに検証されるため、次に、sign 値のアルゴリズムを取得する方法を見つける必要があります。
2. ソースコードから符号を解析する
フォーラムで次の「Alipay アプレット」を検索したところ、ソース コードの取得に関する 19 年前の投稿を見つけました:
Alipay アプレットのキャプチャとソース コードの取得 (https://www.52pojie.cn/forum.php?mod=viewthread&tid) = 1050690)
21年経っても効果があるとは思いませんでした。。。
ソースコードを入手する
そこで、MT マネージャーを使用して/data/user/0/com.eg.android.AlipayGphone/files/nebulaInstallApps/
ディレクトリ内のアプレットの名前を直接検索すると、確かに 2 つのファイルが見つかりました。
上記の投稿の記述によると.tar
、ファイルにはアプレットのソースコードが含まれていますが、忌まわしいことにMTが開けない(ZIP拡張子を変更すると開けなくなる)ため、パソコンに解凍し、解凍ソフトで開きます。
案の定、これはアプレットのソース コードであり、html ファイルと 2 つの js ファイルが含まれています。
減圧分析
これらをすべてフォルダーに解凍し、検索してTask/list
両方の JS ファイルが含まれていることを確認します。
しかし、検索を 1 つずつ開いたところ、index.js は Task ではなく、task であることがわかりました。。大文字と小文字を区別しない文字は区別されないため、除外されます。
したがって、index.worker.js が目標となり、ファイルを開いて再度検索して見つけます。
upTaskInfo: function () {
s.default.get({
url: "Task/list",
data: {
is_filter: 0,
filter: getApp().globalData.filter
},
login: !1,
loading: !0
}).then((function (e) {
var t
//此处省略N多代码
})).catch((function (e) {}))
}
s.default.get
URLからメソッドが呼び出されており、2つのパラメータの値がすべて0であることがわかりますので、getApp().globalData.filter=0
検索してくださいget:
s.default.get メソッド
検索するget:
とネットワークリクエストと思われるコードがあった
HOST: s,
API_ROOT: s + "/",
API_VERSION: u,
DEVICE_TYPE: r,
APP_ID: c,
get: function (e) {
return e.method = "GET",this.request(e)
},
post: function (e) {
return e.method = "POST",this.request(e)
}
これを見てください。もう一度リクエストしてください
検索してrequest:
リクエスト関数を見つけます(実際、最初にsignを検索してリクエスト関数を見つけましたが、ほとんど同じです)、
request: function (e) {
var n,u,r,c,l,d,m,p,h,g,
v = this;
if (1 == a.default.state.foo ? (this.HOST = "https://a.b.com", this.API_ROOT = "https://a.b.com/") :
(this.HOST = s, this.API_ROOT = s + "/"), e = Object.assign({
data: {}
},e), n = Date.parse(new Date) / 1e3 + "", u = Math.ceil(1e4 * Math.random()) + "", r = f, -1 !== e.url.indexOf("Ad/record") && (e.data.filter = getApp().globalData.filter), c = {}, "string" == typeof(l = e.url.split("?"))[1]) {
for (d in l = l[1].split("&"))
c[(m = l[d].split("="))[0]] = m[1];
e.url = l[0]
}
return Object.assign(e.data, c),
Object.keys(e.data).forEach((function (t) {
void 0 === e.data[t] && (e.data[t] = "")
})),
p = {},
Object.assign(p, e.data),
p.appid = this.APP_ID,
p.nonce = u,
p.timestamp = n,
p.os = this.DEVICE_TYPE,
p.v = this.API_VERSION,
p.token = r,
p._url = this.API_ROOT + e.url.split("?")[0],
h = "",
Object.keys(p).sort().forEach((function (e) {
h += e + p[e]
})),
g = (0, o.hexMD5)(i.Base64.encode(h)),
new Promise((function (a, o) {
t.request({
url: v.API_ROOT + e.url,
data: e.data,
method: e.method ? e.method : "POST",
header: {
"Cache-Control": "no-cache",
"Content-Type": "application/x-www-form-urlencoded",
os: v.DEVICE_TYPE,
appid: v.APP_ID,
nonce: u,
v: v.API_VERSION,
timestamp: n,
token: r,
sign: g,
"Adzone-Id": getApp().globalData.adzoneId
},
success: function (n) {
-2e3 == n.data.code ? (v.login((function () {
a(v.request(e))
})), t.showToast({
content: "需要授权登录"
})) : a(n)
},
fail: function (e) {
o(e)
}
})
}))
}
少し分析すると、次のように結論付けることができます。
JSを修正したら、携帯電話の元のディレクトリに戻します
PS: 実は、私も最初はそれほど期待していなかったので、sign は文字列を単純に連結して、キー (md5) を追加するものだと思っていました。。。上記は、小さなプログラムを放棄した後の解析です。
最初は JS を解析せず、index.worker.js
JS のコードを修正しました。リクエスト関数が戻る前に、すべてのパラメータを再度要求しました。上記のコード:
Object.assign(e.data, c), Object.keys(e.data).forEach((function (t) {
void 0 === e.data[t] && (e.data[t] = "")
})), p = {}, Object.assign(p, e.data), p.appid = this.APP_ID, p.nonce = u, p.timestamp = n, p.os = this.DEVICE_TYPE, p.v = this.API_VERSION, p.token = r, p._url = this.API_ROOT + e.url.split("?")[0], h = "", Object.keys(p).sort().forEach((function (e) {
h += e + p[e]
}));
g = (0, o.hexMD5)(i.Base64.encode(h));//sign的值g
t.showModal({
title: "参数信息提示",
content: 'request参数e:' + JSON.stringify(data) + '\n\n参数p:' + JSON.stringify(p) + '\n\np._url:' + p._url + '\n\nh:' + h + '\n\nbase64后sign值:' + g,
showCancel: !1,//变量data已经在request函数一开始就用e赋值了
confirmText: "已阅,退下吧"
})
アプレット ファイルの検証に失敗しました。再リクエストしてくださいindex.worker.js
ただし、それを保存して圧縮パッケージindex.worker.js
に戻し、アプレット ディレクトリに転送した後、アプレットを再度開きます。.tar
アプレットは実際にリロードされました。(後でわかったことですが、ディレクトリ内の合計は検証ファイルmd5のようなファイルであるcert.json
可能性があり、変更されていることが判明した後、再要求されます)sign.json
でもfdに出てきたリクエストindex.worker.js
…同じ理由ではないので、FDの自動応答(AutoResponder)を使って修正したJSを返します。
では、これは武装解除ではないでしょうか? 記号は 6e2edd36a36df5609e71e22a6fb41987 です
0x3 ケースクローズアプレットがキーを引き渡しました
PS: 各インターフェイスの h 値によって連結された文字列は異なりますが、js メソッドを直接使用します。
署名暗号化の手順は大まかに次のとおりです。各インターフェイスのパラメータとパラメータ p の合計が ASCII コード値の順になり、次に値 + キーが結合され、最後に Base64 エンコード後、md5 が符号値になります。