1.簡介
Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構建高質量的原生使用者介面。Flutter應用程式是用Dart編寫的,這是一種由Google在7年多前建立的語言。Flutter是Google使用Dart語言開發的移動應用開發框架,使用一套Dart程式碼就能快速構建高效能、高保真的iOS和Android應用程式。
HTTP應用層的抓包已經成為日常工作測試與偵錯中的重要一環,最近接觸新專案突然之間發現之前的抓包手段都不好使了,頓時模組與模組之間的前端與服務之間的互動都變成了不可見,整個人都好像被矇住了眼睛。
2.驗證是否走代理
Flutter 應用的網路請求是不走手機的系統代理的,也就是說你在系統設定中設定了代理地址和埠號後 Flutter 也不會走你的代理,而抓包是必須要設定代理的,然後走代理我們才可以成功的抓到包,現在人家都不從你這裡走,累死你都抓不到。
方法一:首先我們使用正常的抓包流程:通過fiddler進行抓包,可以看到,只抓到一些圖片和一些沒有用處的亂七八糟的檔案,那麼很有可能他不走代理。
還有一種方法可以判斷APP是否為無代理請求模式:以fiddler為例,當我們設定好fiddler證書、模擬器wifi設定好ip和埠後,使用者端關閉fiddler抓包工具,如果該APP還可以正常執行說明請求為無代理模式。
宏哥查了一下現在使用Flutter的應用程式,發現好多程式都用它,宏哥就選擇了某魚這一款APP。
按照之前的宏哥設定,模擬器設定了代理而且這個代理是走Fiddler的,如果宏哥沒有啟動Fiddler如果是走代理的應用程式,就會出現網路問題,如果是不走代理的應用程式,就可以正常存取網路。具體操作步驟如下:
1.宏哥沒有啟動Fiddler,然後用瀏覽器存取百度,出現網路問題,因為代理的網路走到Fiddler這裡,Fiddler不通,出現網路問題。如下圖所示:
2.宏哥沒有啟動Fiddler,然後啟動應用某魚APP,正常存取網路,因為不走代理的網路,Fiddler啟動不啟動對其沒有影響,不會出現網路問題。如下圖所示:
通過以上對比,我們確認了這款某魚APP不走我們手機設定的代理,因此我們就不可能抓到它的包了。
3.為什麼http請求沒有通過wifi走代理?
為什麼http請求沒有通過wifi走代理呢,因為之前安卓原生使用的一些http框架都是正常走代理的啊,那是不是有可能程式碼中有api方法可以設定請求不走代理,於是乎就研讀了一下Flutter中http相關的原始碼,最終找到了答案。
3.1http請求原始碼跟蹤
http.dart中的HttpClient是一個抽象類,成員方法的具體實現在http_impl.dart中,http的get請求實現如下:
Future<HttpClientRequest> getUrl(Uri url) => _openUrl("get", url);
Future<_HttpClientRequest> _openUrl(String method, Uri uri) {
.
.
.
// Check to see if a proxy server should be used for this connection.
var proxyConf = const _ProxyConfiguration.direct();
if (_findProxy != null) {
// TODO(sgjesse): Keep a map of these as normally only a few
// configuration strings will be used.
try {
proxyConf = new _ProxyConfiguration(_findProxy(uri));
} catch (error, stackTrace) {
return new Future.error(error, stackTrace);
}
}
return _getConnection(uri.host, port, proxyConf, isSecure)
.then((_ConnectionInfo info) {
.
.
.
});
}
首先,我們可以發現方法中有一行註釋// Check to see if a proxy server should be used for this connection.,意思是「檢查是否應該使用代理伺服器進行此連線」;
然後,有一個proxyConf物件初始化和根據_findProxy來建立新的proxyConf物件的語句,然後通過_getConnection(uri.host, port, proxyConf, isSecure)來建立連線,_getConnection的原始碼如下:
Future<_ConnectionInfo> _getConnection(String uriHost, int uriPort,
_ProxyConfiguration proxyConf, bool isSecure) {
Iterator<_Proxy> proxies = proxyConf.proxies.iterator;
Future<_ConnectionInfo> connect(error) {
if (!proxies.moveNext()) return new Future.error(error);
_Proxy proxy = proxies.current;
String host = proxy.isDirect ? uriHost : proxy.host;
int port = proxy.isDirect ? uriPort : proxy.port;
return _getConnectionTarget(host, port, isSecure)
.connect(uriHost, uriPort, proxy, this)
// On error, continue with next proxy.
.catchError(connect);
}
return connect(new HttpException("No proxies given"));
}
從程式碼中我們可以看到根據代理設定資訊來將請求的host和port進行重置,然後建立真實的連線。
跟蹤以上原始碼我們發現dart中http請求是否走代理是需要設定的,而_findProxy變數和設定的代理資訊有關。
http__impl.dart檔案中的_HttpClient類中定義了_findProxy的預設值
Function _findProxy = HttpClient.findProxyFromEnvironment;
HttpClient類中findProxyFromEnvironment方法的實現
static String findProxyFromEnvironment(Uri url,
{Map<String, String> environment}) {
HttpOverrides overrides = HttpOverrides.current;
if (overrides == null) {
return _HttpClient._findProxyFromEnvironment(url, environment);
}
return overrides.findProxyFromEnvironment(url, environment);
}
_HttpClient類中_findProxyFromEnvironment方法的實現
static String _findProxyFromEnvironment(
Uri url, Map<String, String> environment) {
checkNoProxy(String option) {
if (option == null) return null;
Iterator<String> names = option.split(",").map((s) => s.trim()).iterator;
while (names.moveNext()) {
var name = names.current;
if ((name.startsWith("[") &&
name.endsWith("]") &&
"[${url.host}]" == name) ||
(name.isNotEmpty && url.host.endsWith(name))) {
return "DIRECT";
}
}
return null;
}
checkProxy(String option) {
if (option == null) return null;
option = option.trim();
if (option.isEmpty) return null;
int pos = option.indexOf("://");
if (pos >= 0) {
option = option.substring(pos + 3);
}
pos = option.indexOf("/");
if (pos >= 0) {
option = option.substring(0, pos);
}
// Add default port if no port configured.
if (option.indexOf("[") == 0) {
var pos = option.lastIndexOf(":");
if (option.indexOf("]") > pos) option = "$option:1080";
} else {
if (option.indexOf(":") == -1) option = "$option:1080";
}
return "PROXY $option";
}
// Default to using the process current environment.
if (environment == null) environment = _platformEnvironmentCache;
String proxyCfg;
String noProxy = environment["no_proxy"];
if (noProxy == null) noProxy = environment["NO_PROXY"];
if ((proxyCfg = checkNoProxy(noProxy)) != null) {
return proxyCfg;
}
if (url.scheme == "http") {
String proxy = environment["http_proxy"];
if (proxy == null) proxy = environment["HTTP_PROXY"];
if ((proxyCfg = checkProxy(proxy)) != null) {
return proxyCfg;
}
} else if (url.scheme == "https") {
String proxy = environment["https_proxy"];
if (proxy == null) proxy = environment["HTTPS_PROXY"];
if ((proxyCfg = checkProxy(proxy)) != null) {
return proxyCfg;
}
}
return "DIRECT";
}
從以上程式碼中可以發現代理設定從environment中讀取,設定代理時必須指定http_proxy或https_proxy等。而從_openUrl方法實現中proxyConf = new _ProxyConfiguration(_findProxy(uri));得出預設情況下environment是為空的,所以要想在Flutter的http請求中使用代理,則要指定相應的代理設定,即設定httpClient.findProxy的值。範例程式碼:
_getHttpData() async {
var httpClient = new HttpClient();
httpClient.findProxy = (url) {
return HttpClient.findProxyFromEnvironment(url, environment: {"http_proxy": 'http://192.168.124.7:8888',});
};
var uri =
new Uri.http('t.weather.sojson.com', '/api/weather/city/101210101');
var request = await httpClient.getUrl(uri);
var response = await request.close();
if (response.statusCode == 200) {
print('請求成功');
var responseBody = await response.transform(Utf8Decoder()).join();
print('responseBody = $responseBody');
} else {
print('請求失敗');
}
}
以上程式碼設定後即可使用Fiddler或Charles抓包了。
敲黑板!!!程式碼中已設定代理,手機wifi不再需要進行代理設定;192.168.124.7該IP為我們需要抓包的Charles所在電腦IP。
查了好多資料絕大多數是在程式碼中設定代理,或者是程式碼設定了,然後讓其走手機代理,或許這對於開發很容易但是對於測試,或者別人家的APP或許就不是很容易了。下面我們看看下邊的方案。
4.使用VPN
使用VPN將終端裝置的流量轉發到代理伺服器。說的好聽點就是使用VPN,難聽點就是使用Drony工具強行使APP走代理。
優勢:使用VPN軟體不用新增其他測試。
劣勢:終端上的VPN預設會直接對所有流量進行轉發,要進行合理的設定可能需要額外的學習成本。
因為我們的測試物件是手機移動APP,因為我們的測試物件是手機移動APP,所以我們首先要在手機上安裝一個VPN,這裡使用一個十分方便的VPN軟體drony (介紹在這裡https://github.com/SuppSandroB/sandrop/wiki/Drony-FAQ),drony會在你的手機上建立一個VPN,將手機上的所有流量都重定向到drony自身(不是流向vpn伺服器) ,這樣drony就可以管理所有手機上的網路流量,甚至可以對手機上不同APP的流量進行單獨設定。
4.1下載安裝Drony
1.下載對應的安裝包到手機上安裝好,宏哥這裡還是用夜神模擬器做演示,存取其下載地址:https://drony.soft112.com/ 翻不了牆的,用這個地址下載:https://www.appsapk.com/drony-1-3-155/,如下圖所示:
2.下載安裝包並安裝好。安裝完成後開啟軟體,如下圖所示:
4.2設定drony轉發
1.開啟Drony(處於OFF狀態),切換到SETTINGS(無法點選,試試左右滑動切換到SETTING),如下圖所示:
2.選擇Networks,點選Wi-Fi,如下圖所示:
3.點選Wi-Fi,進入設定介面,如果是真機或者你有多個熱點可以連線都可以在這裡顯示,這個就和我們手機連線WiFi一樣。在網路列表中選擇點選當前手機wifi連線的網路 (需要確保該網路與Fiddler代理伺服器網路是聯通的)。如下圖所示:
4.由於宏哥這裡是模擬器,因此需要宏哥編輯一下,在這介面選中那個VirtWifi(虛擬WiFi)長按,彈出Edit和Delete。如下圖所示:
5.點選「Edit」。進入網路詳情設定(Network details),如下圖所示:
6.設定代理hosetname,預設是電腦區域網ip,也就是Fiddler安裝電腦的IP,如下圖所示:
7.設定代理Port,fiddler 預設是 8888,如下圖所示:
8.設定 Proxy type,注意Proxy type代理方式要選擇 Plain http proxy。如下圖所示:
敲黑板!!!!最上邊的Proxy type,選擇代理模式為手動(Manual),如下圖所示:
9.設定Filter default value為Direct all,如下圖所示:
10.設定Rules,點選下面的Rule設定應用規則。如下圖所示:
11.點選「Edit filter Rules」,。進入新增規則頁面,如下圖所示:
12.預設您的規則裡應該是空的,這裡直接點選上面的加號新增一個規則(符合規則要求的才會被轉發),點選右上角的加號圖示,如下圖所示:
13.點選右上角的加號圖示後,進入過濾規則新增介面,如下圖所示:
(1)在Network id處 選擇當前wifi的SSID
(2)Action 選擇 Local proxy chain
(3)Application 選擇需要強制代理的APP
(4)Hostname 及 Port 不填 表示所有的都會被強制代理,因為APP可能會使用其他的網路協定不一定都是http,可能不希望把所有流量都引流到http代理伺服器,這個時候就會使用這個設定指定ip及埠才轉發
14.新增好以後,點選右上角的儲存圖示,如下圖所示:
15.點選「儲存」後,跳轉到規則介面,如下圖所示:
16.啟動Drony:返回到SETTING主頁,滑動到LOG頁,點選下面「OFF」按鈕,
17.點選「確定」,使其處於ON的狀態(表示啟用),如下圖所示:
4.3開啟代理抓包軟體
宏哥這裡代理抓包軟體使用的是Fiddler。Fiddler的使用這裡不再介紹,需要開啟遠端代理,並在手機中安裝Fiddler根證書。這裡宏哥就不做贅述,前邊都有詳細的介紹
經過上面到設定,這些APP的HTTP流量我們就可以通過代理抓包軟體獲取,https流量也可能正常解碼。
5.小結
宏哥這裡只是提供一種思路供你學習和實踐。 好了,今天時間也不早了,宏哥就講解和分享到這裡,感謝你耐心地閱讀!!!
6.拓展
6.1如何下載google play上的apk安裝包
之前一直沒有從Google Play上下載過apk檔案,也不知道怎麼下載,帶來過不便,今天下載查了一下資料,並親自實踐,發現很簡單。
前提:能FQ存取Google。
共分兩個步驟:
1,存取Google play
https://play.google.com/store/apps
搜尋你想要的應用。
開啟應用詳情頁,複製URL地址到步驟2。
2,存取
https://apps.evozi.com/apk-downloader/
將步驟1中的連結貼上到這個URL中的輸入框,點選按鈕(藍色)解析出下載apk的連結,再點選下載連結(綠色)就下載到你的電腦了。