第36講:App 逆向的常見技巧

SpiderLiH發表於2020-10-03

現在我們可以看到很多 App 在請求 API 的時候都有加密引數,前面我們也介紹了一種利用 mitmdump 來實時抓取資料的方法,但是這總歸還有些不方便的地方。

如果要想拿到 App 傳送的請求中包含哪些加密引數,就得剖析本源,深入到 App 內部去找到這些加密引數的構造邏輯,理清這些邏輯之後,我們就能自己用演算法實現出來了。這其中就需要一定的逆向操作,我們可能需要對 App 進行反編譯,然後通過分析原始碼的邏輯找到對應的加密位置。

所以,本課時我們來用一個示例介紹App 逆向相關操作。

1.案例介紹

這裡我們首先以一個 App 為例介紹這個 App 的抓包結果和加密情況,然後我們對這個 App 進行逆向分析,最後模擬實現其中的加密邏輯。

App 的下載地址為:https://app5.scrape.center/

我們先執行一下這個 App,上拉滑動,一些電影資料就會呈現出來了,介面如下:在這裡插入圖片描述
這時候我們用 Charles 抓包來試一下,可以看到類似 API 的請求 URL 類似如下:https://app5.scrape.center/api/movie/?offset=0&limit=10&token=NDVjMTdjNjk5YWM2NWZkOGU5ZjFjNWEyN2MzNjhiYjIwMzRlZDU3ZiwxNTkxMjgyMzcz%0A,這裡我們可以發現有三個引數,分別為 offset、limit 還有 token,其中 token 是一個非常長的加密字串,我們也不好直觀地推測其生成邏輯。

本課時我們就來介紹一下逆向相關的操作,通過逆向操作獲得 apk 反編譯後的程式碼,然後追蹤這個 token 的生成邏輯是怎樣的,最後我們再用程式碼把這個邏輯實現出來。

App 逆向其實多數情況下就是反編譯得到 App 的原始碼,然後從原始碼裡面找尋特定的邏輯,本課時就來演示一下 App 的反編譯和入口點查詢操作。

2.環境準備

在這裡我們使用的逆向工具叫作 JEB。

JEB 是一款專業的安卓應用程式的反編譯工具,適用於逆向和審計工程,功能非常強大,可以幫助逆向人員節省很多逆向分析時間。利用這個工具我們能方便地獲取到 apk 的原始碼資訊,逆向一個 apk 不在話下。

JEB 支援 Windows、Linux、Mac 三大平臺,其官網地址為 https://www.pnfsoftware.com/,你可以在官網瞭解下其基本介紹,然後通過搜尋找到一些完整版安裝包下載。下載之後我們會看到一個 zip 壓縮包,解壓壓縮包之後會得到如下的內容:

在這裡插入圖片描述
在這裡我們直接執行不同平臺下的指令碼檔案即可啟動 JEB。比如我使用的是 Mac,那我就可以在此目錄下執行如下命令:

sh jeb_macos.sh

這樣我們就可以開啟 JEB 了。開啟 JEB 之後,我們把下載的 apk 檔案直接拖拽到 JEB 裡面,經過一段時間處理後,會發現 JEB 就已經將程式碼反編譯完成了,如圖所示:在這裡插入圖片描述
這時候我們可以看到在左側 Bytecode 部分就是反編譯後的程式碼,在右側顯示的則是 Smali 程式碼,通過 Smali 程式碼我們大體能夠看出一些執行邏輯和資料操作等過程。

現在我們得到了這些反編譯的內容,該從哪個地方入手去找入口呢?

由於這裡我們需要找的是請求加密引數的位置,那麼最簡單的當然是通過 API 的一些標誌字串來查詢入口了。API 的 URL 裡面包含了關鍵字 /api/movie,那麼我們自然就可以通過這個來查詢了。

我們可以在 JEB 裡面開啟查詢視窗,查詢 /api/movie,如圖所示:
在這裡插入圖片描述
這時候我們發現就找到了一個對應的宣告如下:

.field public static final indexPath:String = "/api/movie"

這裡其實就是宣告瞭一個靜態不可變的字串,叫作 indexPath。但這裡是 Smali 程式碼呀?我們怎麼去找到它的原始碼位置呢?

這時候我們可以右鍵該字串,選擇解析選項,這時 JEB 就可以成功幫我們定位到 Java 程式碼的宣告處了。
在這裡插入圖片描述
這時候我們便可以看到其跳轉到了如下頁面:
在這裡插入圖片描述
這裡我們就能看到 indexPath 的原始宣告,同時還看到了一個 index 方法的宣告,包含三個引數 offset、limit 還有 token,由此可以發現,這引數和宣告其實恰好和 API 的請求 URL 格式是相同的。

但這裡還觀察到這個是一個介面宣告,一定有某個類實現了這個介面。

我們這時候可以順著 index 方法來查詢是什麼類實現了這個 index 方法,在 index 方法上面右鍵選擇“交叉引用”,如圖所示:
在這裡插入圖片描述
這時候我們可以發現這裡彈出了一個視窗,找到了對應的位置,如圖所示:
在這裡插入圖片描述
我們選中它,點選確定,這時候就跳轉到了對應的 index 實現的位置了,如圖所示:
在這裡插入圖片描述
這裡 index 方法的實現如下:

public Observable index(int arg6, int arg7) {
    ArrayList v2 = new ArrayList();
    ((List)v2).add("/api/movie");
    return this.apiService.index((arg6 - 1) * arg7, arg7, Encrypt.encrypt(((List)v2)));
}

就能很輕易地發現一個類似 encrypt 的方法,代表加密的意思,其引數就是 v2,而 v2 就是一個 ArrayList,包含一個元素,就是 /api/movie 這個字串。
這時候我們再通過交叉引用找到 Encrypt 的定義,跳轉到如圖所示的位置:
在這裡插入圖片描述
這裡可以發現 encrypt 的方法實現如下:

public static String encrypt(List arg7) {
    String v1 = String.valueOf(new Timestamp(System.currentTimeMillis()).getTime() / 1000);
    arg7.add(v1);
    String v2 = Encrypt.shaEncrypt(TextUtils.join(",", ((Iterable)arg7)));
    ArrayList v3 = new ArrayList();
    ((List)v3).add(v2);
    ((List)v3).add(v1);
    return Base64.encodeToString(TextUtils.join(",", ((Iterable)v3)).getBytes(), 0);
}

這裡我們分析一下,傳入的引數就是 arg7,剛才經過分析可知 arg7 其實就是一個長度為 1 的列表,其內容就是一個字串,即 ["/api/movie"]。
緊接著看邏輯,這裡又定義了一個 v1 的字串,其實就是獲取了時間戳資訊,然後把結果加入 arg7,現在 arg7 就有兩個內容了,一個是 /api/movie,另一個是時間戳。

接著又宣告瞭 v2,這裡經過分析可知是將 arg7 使用逗號拼接起來,然後呼叫了 shaEncrypt 操作,而 shaEncrypt 經過觀察其實就是 SHA1 演算法。

緊接著又宣告瞭一個 ArrayList,把 v2 和 v1 的結果加進去。最後把 v3 的內容使用逗號拼接起來,然後 Base64 編碼即可。

好,現在整體的 token 加密的邏輯就理清楚了。

3.模擬

瞭解了基本的演算法流程之後,我們可以用 Python 把這個流程實現出來,程式碼實現如下:

import hashlib
import time
import base64
from typing import List, Any
import requests
​
INDEX_URL = 'https://app5.scrape.cuiqingcai.com/api/movie?limit={limit}&offset={offset}&token={token}'
LIMIT = 10
OFFSET = 0def get_token(args: List[Any]):
    timestamp = str(int(time.time()))
    args.append(timestamp)
    sign = hashlib.sha1(','.join(args).encode('utf-8')).hexdigest()
    return base64.b64encode(','.join([sign, timestamp]).encode('utf-8')).decode('utf-8')
  
args = ['/api/movie']
token = get_token(args=args)
index_url = INDEX_URL.format(limit=LIMIT, offset=OFFSET, token=token)
response = requests.get(index_url)
print('response', response.json())

這裡最關鍵的就是 token 的生成過程,我們定義了一個 get_token 方法來實現,整體上思路就是上面梳理的內容:

  • 列表中加入當前時間戳;
  • 將列表內容用逗號拼接;
  • 將拼接的結果進行 SHA1 編碼;
  • 將編碼的結果和時間戳再次拼接;
  • 將拼接後的結果進行 Base64 編碼。

最後執行結果如下:

response {'count': 100, 'results': [{'id': 1, 'name': '霸王別姬', 'alias': 'Farewell My Concubine', 'cover': 'https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c', 'categories': ['劇情', '愛情'], 'published_at': '1993-07-26', 'minute': 171, 'score': 9.5, 'regions': ['中國大陸', '中國香港']}, {'id': 2, 'name': '這個殺手不太冷', 'alias': 'Léon', 'cover': 'https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c', 'categories': ['劇情', '動作', '犯罪'], 'published_at': '1994-09-14', 'minute': 110, 'score': 9.5, 'regions': ['法國']}, {'id': 3, 'name': '肖申克的救贖', 'alias': 'The Shawshank Redemption', 'cover': 'https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c', 'categories': ['劇情', '犯罪'], 'published_at': '1994-09-10', 'minute': 142, 'score': 9.5, 'regions': ['美國']}, {'id': 4, 'name': '泰坦尼克號', 'alias': 'Titanic', 'cover': 'https://p1.meituan.net/movie/b607fba7513e7f15eab170aac1e1400d878112.jpg@464w_644h_1e_1c', 'categories': ['劇情', '愛情', '災難'], 'published_at': '1998-04-03', 'minute': 194, 'score': 9.5, 'regions': ['美國']}, {'id': 5, 'name': '羅馬假日', 'alias': 'Roman Holiday', 'cover': 'https://p0.meituan.net/movie/289f98ceaa8a0ae737d3dc01cd05ab052213631.jpg@464w_644h_1e_1c', 'categories': ['劇情', '喜劇', '愛情'], 'published_at': '1953-08-20', 'minute': 118, 'score': 9.5, 'regions': ['美國']}, {'id': 6, 'name': '唐伯虎點秋香', 'alias': 'Flirting Scholar', 'cover': 'https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@464w_644h_1e_1c', 'categories': ['喜劇', '愛情', '古裝'], 'published_at': '1993-07-01', 'minute': 102, 'score': 9.5, 'regions': ['中國香港']}, {'id': 7, 'name': '亂世佳人', 'alias': 'Gone with the Wind', 'cover': 'https://p0.meituan.net/movie/223c3e186db3ab4ea3bb14508c709400427933.jpg@464w_644h_1e_1c', 'categories': ['劇情', '愛情', '歷史', '戰爭'], 'published_at': '1939-12-15', 'minute': 238, 'score': 9.5, 'regions': ['美國']}, {'id': 8, 'name': '喜劇之王', 'alias': 'The King of Comedy', 'cover': 'https://p0.meituan.net/movie/1f0d671f6a37f9d7b015e4682b8b113e174332.jpg@464w_644h_1e_1c', 'categories': ['劇情', '喜劇', '愛情'], 'published_at': '1999-02-13', 'minute': 85, 'score': 9.5, 'regions': ['中國香港']}, {'id': 9, 'name': '楚門的世界', 'alias': 'The Truman Show', 'cover': 'https://p0.meituan.net/movie/8959888ee0c399b0fe53a714bc8a5a17460048.jpg@464w_644h_1e_1c', 'categories': ['劇情', '科幻'], 'published_at': None, 'minute': 103, 'score': 9.0, 'regions': ['美國']}, {'id': 10, 'name': '獅子王', 'alias': 'The Lion King', 'cover': 'https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e1438406.jpg@464w_644h_1e_1c', 'categories': ['動畫', '歌舞', '冒險'], 'published_at': '1995-07-15', 'minute': 89, 'score': 9.0, 'regions': ['美國']}]}

這裡就以第一頁的資料為示例來演示了,其他的頁面我們通過修改 page 的值就可以拿到了。

4.總結

以上我們便通過一個樣例講解了一個比較基本的 App 的逆向過程,包括 JEB 的使用、追蹤程式碼的操作等等,最後通過分析程式碼理清了基本邏輯,最後模擬實現了 API 的引數構造和請求傳送,得到最終的資料。

相關文章