日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

转载:实用 FRIDA 进阶 --- objection :内存漫游、hook anywhere、抓包

發布時間:2024/7/23 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 转载:实用 FRIDA 进阶 --- objection :内存漫游、hook anywhere、抓包 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載:實用FRIDA進階:內存漫游、hook anywhere、抓包:https://www.anquanke.com/post/id/197657

frida github 地址:https://github.com/frida/frida
objection github:https://github.com/sensepost/objection
objection pypi:https://pypi.org/project/objection/

本章中我們進一步介紹,大家在學習和工作中使用 Frida 的實際高頻場景,比如:

  • 動態查看 安卓應用程序 在當前內存中的狀態
  • 指哪兒就能 hook 哪兒
  • 比如 脫殼,
  • 還有使用 Frida 來自動化獲取參數、返回值等數據,
  • 主動調用 API 獲取簽名結果 sign 等。。。

最后介紹一些經常遇到的高頻問題解決思路,希望可以切實地幫助到讀者。

Objection 簡單使用

Frida hook工具 --- objection 使用:https://blog.csdn.net/wang_624/article/details/115601098

更多命令行參數可以查看 cli.py 文件得到https://github.com/sensepost/objection/blob/e7eb1d9b769edf6a98870c75a6d2a6123b7346fd/objection/console/cli.py

使用命令

pip install objection # 安裝 objection --help # 查看幫助 help frida # 不知道當前命令的作用 進入objection后就在命令前加 help 會有提示objection -g 包名 explore # 注入進程,如果objection沒有找到進程,會以spwan方式啟動進程 objection -N -h 192.168.1.3 -p 9999 -g 包名 explore # 指定ip和端口的連接# spawn啟動前就Hook objection -N -h 192.168.1.3 -p 9999 -g 包名 explore --startup-command "android hooking watch class '包名.類名'"# spawn啟動前就Hook 打印參數、返回值、函數調用棧 objection -N -h 192.168.1.3 -p 9999 -g 包名 explore --startup-command "android hooking watch class_method '包名.類名.方法' --dump-args --dump-return --dump-backtrace"android hooking list classes # 列出內存中所有的類 android hooking search classes 包名 # 在內存中所有已加載的類中搜索包含特定關鍵詞的類 android hooking list class_methods 包名.類名 # 列出類的所有方法android hooking watch class 包名.類名 # hook類的所有方法 android hooking watch class_method 包名.類名.方法 # 默認會Hook方法的所有重載# 如果只需hook其中一個重載函數 指定參數類型 多個參數用逗號分隔 android hooking watch class_method 包名.類名.方法 "參數1,參數2"# hook方法的參數、返回值和調用棧(–dump-args: 顯示參數; --dump-return: 顯示返回值; --dump-backtrace: 顯示堆棧) android hooking watch class_method 包名.類名.方法 --dump-args --dump-return --dump-backtracejobs list # 查看 hook 的任務有多少個 jobs kill jobid # 把正在 hook 的任務關閉android heap search instances 包名.類名 --fresh # 搜索堆中的實例 android heap execute 地址(hashcode的地址) 方法名 # 調用實例的方法memory list modules # 枚舉內存中所有模塊 memory list exports 文件名.so # 枚舉模塊中所有導出函數

android hooking search classes okhttp3
android hooking watch class okhttp3.OkHttpClient --dump-args --dump-return
android hooking watch class_method okhttp3.OkHttpClient.newCall --dump-args --dump-backtrace --dump-return

啟動

adb forward tcp:27042 tcp:27042frida -U -l js_okhttp.js -F com.cdsb.newsreader --no-pause frida -U -l okhttp_poker.js -F com.cdsb.newsreader --no-pause frida -U -l okhttp_poker.js -F com.huanqiu.news --no-pause frida -U -l frida_hook_js.js -f com.huanqiu.news --no-pauseobjection -g com.app.name explore -P ~/objection/plugins objection -g com.cdsb.newsreader explore -P objection_pluginspython r0capture.py -U -f com.cdsb.newsreader -v python r0capture.py -U com.cdsb.newsreader -v -p cdsb.pcap

基于 frida 的 objection 及其插件 wallbreaker 命令列表

( :https://www.cnblogs.com/ningskyer/articles/14611822.html )

cd commandsclearhistorysave env evaluate exit filecatdownloaduploadhttp frida import iosbundlescookiesheaphookinginfojailbreakkeychainmonitornsurlcredentialstoragtensuserdefaultspasteboardplistsslpinningui jobskilllist ls memorylistexports xx.somodulessearchwritedumpallfrom_base androidclipboardmonitordeoptimizeheapevaluateexecuteprintfieldsmethodssearchinstances xxx --freshhookinggenerateclasssimplegetcurrent_activitysetreturn_valuelistactivitiesclass_loadersclass_methodsclassesreceiversservicessearchclassesmethodswatchclassclass_methodintentlaunching_activitylaunching_servicekeystorelistclearwatchproxysetrootdisablesimulateshell_execsslpinningdisableuiFLAG_SECUREscreenshot ping pluginload pwd reconnect rm sqliteconnect

1. 內存漫游

Frida 只是提供了各種 API 供我們調用,在此基礎之上可以實現具體的功能,比如禁用證書綁定之類的腳本,就是使用 Frida 的各種 API 來組合編寫而成。于是有大佬將各種常見、常用的功能整合進一個工具,供我們直接在命令行中使用,這個工具便是objectionobjection 功能強大,命令眾多,而且不用寫一行代碼,便可實現諸如內存搜索、類和模塊搜索、方法hook打印參數返回值調用棧等常用功能,是一個非常方便的,逆向必備、內存漫游神器。

安裝命令:pip3 install objection

objection?的界面及命令如圖所示。

objection 是基于 frida 的命令行 hook 工具,可以讓你不寫代碼,?敲幾句命令就可以對 java 函數的高顆粒度 hook,?還支持 RPC 調用

objection?目前只支持 Java層的 hook,但是 objection 有提供插件接口,可以自己寫 frida 腳本去定義接口,

比如葫蘆娃大佬的脫殼插件,實名推薦:?https://github.com/hluwa/FRIDA-DEXDump

官方倉庫:?https://github.com/sensepost/objection

1.1 獲取基本信息

首先介紹幾個基本操作:

  • 鍵入命令之后,回車執行;
  • help: 不知道當前命令的效果是什么,在當前命令前加 help 比如:help env,回車之后會出現當前命令的解釋信息;
  • 按空格: 不知道輸入什么就按空格,會有提示出來,上下選擇之后再按空格選中,又會有新的提示出來;
  • jobs: 作業系統很好用,建議一定要掌握,可以同時運行 多項 ( hook ) 作業

簡單使用

  • 啟動 Frida-server,并轉發端口 (?adb forward tcp:27042 tcp:27042?)
  • 附加需要調試的 app,進入交互界面 (?objection -g [packageName] explore?)

連接逍遙模擬器,需要先進入模擬器所在目錄,使用目錄中 adb.exe?命令執行:adb.exe?connect 127.0.0.1:21503

可以使用該 env 命令枚舉與所討論的應用程序相關的其他有趣目錄:env

可以使用以下 file download 命令從遠程文件系統中下載文件:file download [file] [outfile]

com.opera.mini.native on (samsung: 6.0.1) [usb] # file download fhash.dat fhash.dat Downloading /data/user/0/com.opera.mini.native/cache/fhash.dat to fhash.dat

可以列出 app 具有的所有avtivity:android hooking list activities

com.opera.mini.native on (samsung: 6.0.1) [usb] # android hooking list activities com.facebook.ads.AudienceNetworkActivity com.google.android.gms.ads.AdActivity com.google.android.gms.auth.api.signin.internal.SignInHubActivity com.google.android.gms.common.api.GoogleApiActivity com.opera.android.AssistActivity com.opera.android.MiniActivity com.opera.android.ads.AdmobIntentInterceptor com.opera.mini.android.BrowserFound 8 classes

啟動指定 avtivity:android intent launch_activity [class_activity]

com.opera.mini.native on (samsung: 6.0.1) [usb] # android intent launch_activity com.facebook.ads.AudienceNetworkActivity Launching Activity: com.facebook.ads.AudienceNetworkActivity...

RPC 調用命令:curl -s "http://127.0.0.1:8888/rpc/invoke/androidHookingListActivities"

$ curl -s "http://127.0.0.1:8888/rpc/invoke/androidHookingListActivities" ["com.reddit.frontpage.StartActivity","com.reddit.frontpage.IntroductionActivity", ... snip ...]- RPC調用執行腳本:`url -X POST -H "Content-Type: text/javascript" http://127.0.0.1:8888/script/runonce -d "@script.js"`$ cat script.js {send(Frida.version); }[{"payload":"12.8.0","type":"send"}]

RPC WIKI:https://github.com/sensepost/objection/wiki/API

API 介紹

以下只是寫了一部分指令和功能, 詳細的功能需要合理運用?空格?和?help

Memory 指令memory list modules //枚舉當前進程模塊memory list exports [lib_name] //查看指定模塊的導出函數memory list exports libart.so --json /root/libart.json //將結果保存到json文件中memory search --string --offsets-only //搜索內存android heap 指令//堆內存中搜索指定類的實例, 可以獲取該類的實例idsearch instances search instances com.xx.xx.class//直接調用指定實例下的方法android heap execute [ins_id] [func_name]//自定義frida腳本, 執行實例的方法android heap execute [ins_id]android 指令android root disable //嘗試關閉app的root檢測android root simulate //嘗試模擬root環境android ui screenshot [image.png] //截圖android ui FLAG_SECURE false //設置FLAG_SECURE權限內存漫游android hooking list classes //列出內存中所有的類//在內存中所有已加載的類中搜索包含特定關鍵詞的類android hooking search classes [search_name] //在內存中所有已加載的方法中搜索包含特定關鍵詞的方法android hooking search methods [search_name] //直接生成hook代碼android hooking generate simple [class_name]hook 方式/*hook指定方法, 如果有重載會hook所有重載,如果有疑問可以看--dump-args : 打印參數--dump-backtrace : 打印調用棧--dump-return : 打印返回值*/android hooking watch class_method com.xxx.xxx.methodName --dump-args --dump-backtrace --dump-return//hook指定類, 會打印該類下的所有調用android hooking watch class com.xxx.xxx//設置返回值(只支持bool類型)android hooking set return_value com.xxx.xxx.methodName falseSpawn 方式 Hookobjection -g packageName explore --startup-command '[obejection_command]'activity 和 service 操作android hooking list activities //枚舉activityandroid intent launch_activity [activity_class] //啟動activityandroid hooking list services //枚舉servicesandroid intent launch_service [services_class] //啟動services任務管理器jobs list // 查看任務列表jobs kill [task_id] // 關閉任務關閉 app 的 ssl 校驗android sslpinning disable監控系統剪貼板// 獲取Android剪貼板服務上的句柄并每5秒輪詢一次用于數據。 // 如果發現新數據,與之前的調查不同,則該數據將被轉儲到屏幕上。help android clipboard執行命令行help android shell_exec [command]

插件編寫 :?objection pluging:https://github.com/sensepost/objection/wiki/Plugins

不寫一行代碼探索應用行為 --- 使用 objection

From:https://www.y4f.net/77651.html

這里拿 XCTF 的三個題目做演示,分別是mobile進階區的第3題、第8題和第17題。

示例:以安卓 內置應用 "設置"?演示基本用法

在手機上啟動 frida-server,并且點擊啟動 "設置" 圖標,手機進入設置的界面,首先查看一下 "設置" 應用的包名。

# frida-ps -U|grep -i setting7107 com.android.settings 13370 com.google.android.settings.intelligence

再使用 objection 注入 "設置"?應用。

# objection -g com.android.settings explore

啟動?objection之后,會出現提示它的?logo,這時候不知道輸入啥命令的話,可以按下空格,有提示的命令及其功能出來;
再按空格選中,又會有新的提示命令出來,這時候按回車就可以執行該命令,

見下圖 2-2 執行的應用環境信息命令?env?和?frida-server?版本信息命令。

1.2 提取內存信息

1.2.1 查看內存中加載的庫(?memory list modules )

運行命令 memory list modules,效果如下圖2-3所示。內存中加載的庫

1.2.2 查看庫的導出函數 (?memory list exports libssl.so )

運行命令 memory list exports libssl.so,效果如下圖2-4所示。 libssl.so 庫的導出函數

1.2.3 將結果保存到 json文件中

當結果太多,終端無法全部顯示的時候,可以將結果導出到文件中,然后使用其他軟件查看內容,見下圖2-5。

# memory list exports libart.so --json /root/libart.json Writing exports as json to /root/libart.json... Wrote exports to: /root/libart.json

使用 json 格式保存的 libart.so 的導出函數

1.2.4 提取整個(或部分)內存(?memory dump all from_base )

命令是 memory dump all from_base,這部分內容與下文脫殼部分有重疊,我們在脫殼部分介紹用法。

1.2.5 搜索整個內存(?memory search --string --offsets-only )

命令是 memory search --string --offsets-only,這部分也與下文脫殼部分有重疊,我們在脫殼部分詳細介紹用法。


1.3 內存堆 (heap)?上的搜索執行

1.3.1 在堆 (heap)上搜索實例

我們查看AOSP源碼關于設置里顯示系統設置的部分,發現存在著 DisplaySettings類,可以在堆上搜索是否存在著該類的實例。

首先在手機上點擊進入 "顯示" 設置,然后運行命令:android heap search instances com.android.settings.DisplaySettings

并得到相應的實例地址:

1.3.2 調用實例的方法

查看源碼得知 com.android.settings.DisplaySettings類?有一個?getPreferenceScreenResId()方法,這樣就可以直接調用該實例的 getPreferenceScreenResId()方法,

后文也會介紹在objection中直接打印類的所有方法的命令

用?excute?命令:android heap execute 0x2526 getPreferenceScreenResId

Handle 0x2526 is to class com.android.settings.DisplaySettings Executing method: getPreferenceScreenResId() 2132082764

可見結果被直接打印了出來。

1.3.3 在實例上執行 js 代碼

也可以在找到的實例上直接編寫 js 腳本,輸入android heap evaluate 0x2526 命令后,會進入一個迷你編輯器環境,

  • 輸入 console.log("evaluate result:"+clazz.getPreferenceScreenResId()) 這串腳本,
  • 按ESC退出編輯器,然后按回車,即會開始執行這串腳本,輸出結果。
# android heap evaluate 0x2526 (The handle at `0x2526` will be available as the `clazz` variable.)console.log("evaluate result:"+clazz.getPreferenceScreenResId()) JavaScript capture complete. Evaluating... Handle 0x2526 is to class com.android.settings.DisplaySettings evaluate result:2132082764

這個功能其實非常厲害,可以即時編寫、出結果、即時調試自己的代碼,不用再:編寫→注入→操作→看結果→再調整,而是直接出結果。

1.4 啟動 activity 或 service

1.4.1?直接啟動 activity

直接上代碼,想要進入顯示設置,可以在任意界面直接運行以下代碼進入顯示設置:

# android intent launch_activity com.android.settings.DisplaySettings (agent) Starting activity com.android.settings.DisplaySettings... (agent) Activity successfully asked to start.

1.4.2?查看當前可用的 activity

可以使用 android hooking list 命令來查看當前可用的 activities,然后使用上述命令進行調起。

# android hooking list activitiescom.android.settings.ActivityPicker com.android.settings.AirplaneModeVoiceActivity com.android.settings.AllowBindAppWidgetActivity com.android.settings.AppWidgetPickActivity com.android.settings.BandMode com.android.settings.ConfirmDeviceCredentialActivity com.android.settings.CredentialStorage com.android.settings.CryptKeeper$FadeToBlack com.android.settings.CryptKeeperConfirm$Blank com.android.settings.DeviceAdminAdd com.android.settings.DeviceAdminSettings com.android.settings.DisplaySettings com.android.settings.EncryptionInterstitial com.android.settings.FallbackHome com.android.settings.HelpTrampoline com.android.settings.LanguageSettings com.android.settings.MonitoringCertInfoActivity com.android.settings.RadioInfo com.android.settings.RegulatoryInfoDisplayActivity com.android.settings.RemoteBugreportActivity com.android.settings.RunningServices com.android.settings.SetFullBackupPassword com.android.settings.SetProfileOwner com.android.settings.Settings com.android.settings.Settings com.android.settings.Settings$AccessibilityDaltonizerSettingsActivity com.android.settings.Settings$AccessibilitySettingsActivity com.android.settings.Settings$AccountDashboardActivity com.android.settings.Settings$AccountSyncSettingsActivity com.android.settings.Settings$AdvancedAppsActivity

1.4.3 直接啟動 service

也可以先使用 android hooking list services 查看可供開啟的服務,

然后使用 android intent launch_service com.android.settings.bluetooth.BluetoothPairingService 命令來開啟服務。

2. Frida hook anywhere

很多新手在學習 Frida 的時候,遇到的第一個問題就是:無法找到正確的類及子類,無法定位到實現功能的準確的方法,無法正確的構造參數、繼而進入正確的重載

這時候可以使用 Frida 進行動態調試,來確定以上具體的名稱和寫法,最后寫出正確的hook代碼。


2.1 objection(內存漫游)

2.1.1 列出內存中所有的類

執行命令:android hooking list classes

# android hooking list classessun.util.logging.LoggingSupport sun.util.logging.LoggingSupport$1 sun.util.logging.LoggingSupport$2 sun.util.logging.PlatformLogger sun.util.logging.PlatformLogger$1 sun.util.logging.PlatformLogger$JavaLoggerProxy sun.util.logging.PlatformLogger$Level sun.util.logging.PlatformLogger$LoggerProxy voidFound 11885 classes

2.1.2 內存中搜索包含特定關鍵詞的類

執行命令:android hooking search classes 關鍵字。在內存中所有已加載的類中搜索包含特定關鍵詞的類。

示例( 搜索包含關鍵 display 的 類?):android hooking search classes display? ? ?

# android hooking search classes display [Landroid.hardware.display.WifiDisplay; [Landroid.icu.impl.ICUCurrencyDisplayInfoProvider$ICUCurrencyDisplayInfo$CurrencySink$EntrypointTable; [Landroid.icu.impl.LocaleDisplayNamesImpl$CapitalizationContextUsage; [Landroid.icu.impl.LocaleDisplayNamesImpl$DataTableType; [Landroid.icu.number.NumberFormatter$DecimalSeparatorDisplay; [Landroid.icu.number.NumberFormatter$SignDisplay; [Landroid.icu.text.DisplayContext$Type; [Landroid.icu.text.DisplayContext; [Landroid.icu.text.LocaleDisplayNames$DialectHandling; [Landroid.view.Display$Mode; [Landroid.view.Display; android.app.Vr2dDisplayProperties android.hardware.display.AmbientBrightnessDayStats android.hardware.display.AmbientBrightnessDayStats$1 android.hardware.display.BrightnessChangeEvent com.android.settings.wfd.WifiDisplaySettings$SummaryProvider com.android.settings.wfd.WifiDisplaySettings$SummaryProvider$1 com.android.settingslib.display.BrightnessUtils com.android.settingslib.display.DisplayDensityUtils com.google.android.gles_jni.EGLDisplayImpl javax.microedition.khronos.egl.EGLDisplayFound 144 classes

2.1.3 內存中搜索所有的方法

在內存中所有已加載的類的方法中搜索包含特定關鍵詞的方法,上文中可以發現,內存中已加載的類就已經高達11885個了,那么他們的方法一定是類的個數的數倍,整個過程會相當龐大和耗時,見下圖2-6。

# android hooking search methods display

內存中搜索所有的方法

2.1.4 列出指定類的所有方法

當搜索到了比較關心的類之后,就可以直接查看它有哪些方法,

比如:我們想要查看 com.android.settings.DisplaySettings 類 有哪些方法,就可以執行命令:android hooking list class_methods com.android.settings.DisplaySettings

# android hooking list class_methods com.android.settings.DisplaySettings private static java.util.List<com.android.settingslib.core.AbstractPreferenceController> com.android.settings.DisplaySettings.buildPreferenceControllers(android.content.Context,com.android.settingslib.core.lifecycle.Lifecycle) protected int com.android.settings.DisplaySettings.getPreferenceScreenResId() protected java.lang.String com.android.settings.DisplaySettings.getLogTag() protected java.util.List<com.android.settingslib.core.AbstractPreferenceController> com.android.settings.DisplaySettings.createPreferenceControllers(android.content.Context) public int com.android.settings.DisplaySettings.getHelpResource() public int com.android.settings.DisplaySettings.getMetricsCategory() static java.util.List com.android.settings.DisplaySettings.access$000(android.content.Context,com.android.settingslib.core.lifecycle.Lifecycle)Found 7 method(s)

列出的方法與源碼相比對之后,發現是一模一樣的。

2.1.5 自動生成 hook 代碼

上文中在列出類的方法時,還直接把參數也提供了,也就是說我們可以直接動手寫 hook 了,既然上述寫 hook 的要素已經全部都有了,objection 這個 "自動化"?工具,當然可以直接生成代碼。

自動生成 hook 代碼的命令:android hooking generate ?simple ?com.android.settings.DisplaySettings

# android hooking generate simple com.android.settings.DisplaySettingsJava.perform(function() {var clazz = Java.use('com.android.settings.DisplaySettings');clazz.getHelpResource.implementation = function() {//return clazz.getHelpResource.apply(this, arguments);} });Java.perform(function() {var clazz = Java.use('com.android.settings.DisplaySettings');clazz.getLogTag.implementation = function() {//return clazz.getLogTag.apply(this, arguments);} });Java.perform(function() {var clazz = Java.use('com.android.settings.DisplaySettings');clazz.getPreferenceScreenResId.implementation = function() {//return clazz.getPreferenceScreenResId.apply(this, arguments);} });

生成的代碼大部分要素都有了,只是參數貌似沒有填上,還是需要我們后續補充一些,看來還是無法做到完美。

2.2 objection(hook)

上述操作均是基于在內存中直接枚舉搜索,已經可以獲取到大量有用的靜態信息,我們再來介紹幾個方法,可以獲取到執行時動態的信息,當然、同樣地,不用寫一行代碼。

2.2.1 hook類的所有方法

我們以手機連接藍牙耳機播放音樂為例,看看手機藍牙接口的動態信息。

  • 首先我們將手機連接上我的藍牙耳機(一加藍牙耳機OnePlus Bullets Wireless 2),并可以正常播放音樂;
  • 然后我們按照上文的方法,搜索一下與藍牙相關的類,搜到一個高度可疑的類:android.bluetooth.BluetoothDevice
  • 運行命令,hook 這個類:# android hooking watch class android.bluetooth.BluetoothDevice

使用 jobs list 命令可以看到 objection 為我們創建的 Hooks 數為 57也就是將 android.bluetooth.BluetoothDevice類 下的所有方法都 hook了。這時候我們在 設置→聲音→媒體播放到上進行操作,在藍牙耳機與“此設備”之間切換時,會命中這些hook之后,此時objection就會將方法打印出來,會將類似這樣的信息“吐”出來:

com.android.settings on (google: 9) [usb] # (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getService() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.isConnected() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getService() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getAliasName() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getAlias() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getName() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.equals(java.lang.Object) (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getService() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.isConnected() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getService() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getAliasName() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getAlias() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getName() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.equals(java.lang.Object) (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.equals(java.lang.Object) (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getBatteryLevel() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.equals(java.lang.Object) (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getBatteryLevel() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.equals(java.lang.Object) (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getBondState() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getAliasName() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getAlias() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getName() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getBatteryLevel() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.equals(java.lang.Object) (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getBatteryLevel() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.equals(java.lang.Object) (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getBondState() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getAliasName() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getAlias() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getName() (agent) [h0u5g7uclo] Called android.bluetooth.BluetoothDevice.getService()

可以看到我們的切換操作,調用到了 android.bluetooth.BluetoothDevice 類中的多個方法。

2.2.2 hook 方法的參數、返回值、調用棧

在這些方法中,我們對哪些方法感興趣,就可以查看哪些個方法的參數、返回值和調用棧,比如想看 getName()方法,則運行以下命令:# android hooking watch class_method android.bluetooth.BluetoothDevice.getName --dump-args --dump-return --dump-backtrace

注意最后加上的三個選項 --dump-args --dump-return --dump-backtrace,為我們成功打印出來了我們想要看的信息,其實返回值 Return Value 就是 getName()方法的返回值,我的藍牙耳機的型號名字 OnePlus Bullets Wireless 2;從調用棧可以反查如何一步一步調用到 getName()這個方法的;雖然這個方法沒有參數,大家可以再找個有參數的試一下。

2.2.3 hook 方法的所有重載

objection 的 help 中指出,在 hook 給出的單個方法的時候,會 hook 它的所有重載

# help android hooking watch class_method Command: android hooking watch class_methodUsage: android hooking watch class_method <fully qualified class method> <optional overload>(optional: --dump-args) (optional: --dump-backtrace)(optional: --dump-return)Hooks a specified class method and reports on invocations, together with the number of arguments that method was called with. This command will also hook all of the methods available overloads unless a specific overload is specified. If the --include-backtrace flag is provided, a full stack trace that lead to the methods invocation will also be dumped. This would aid in discovering who called the original method. Examples: android hooking watch class_method com.example.test.loginandroid hooking watch class_method com.example.test.helper.executeQuery android hooking watch class_method com.example.test.helper.executeQuery "java.lang.String,java.lang.String"android hooking watch class_method com.example.test.helper.executeQuery --dump-backtraceandroid hooking watch class_method com.example.test.login --dump-args --dump-return

那我們可以用 File 類的構造器來試一下效果。

# android hooking watch class_method java.io.File.$init --dump-args

可以看到 objection 為我們 hook 了 File 構造器的所有重載,一共是6個。在設置界面隨意進出幾個子設置界面,可以看到命中很多次該方法的不同重載,每次參數的值也都不同,

見下圖。 方法重載的參數和值都不同

2.3 objection 插件 ---?Wallbreaker

葫蘆娃 github:https://github.com/hluwa?tab=repositories

Wallbreaker:從內存里面進行 逆向

Wallbreaker 是一個有用的工具,用于實時分析 Java 堆,由frida提供支持。提供一些命令從內存中搜索對象或類,并精美地可視化目標的真實結構。

想知道真實的數據內容嗎?項目清單?地圖條目?想知道接口的實現嗎?嘗試一下!你所看到的就是你得到的!

使用方法:參看 github:https://github.com/hluwa/Wallbreaker

使用 Wallbreaker 快速分析 Java 類/對象結構

From:https://bbs.pediy.com/thread-260110.htm

Wallbreaker?取自?wikipedia?上對《三體》"破壁者"的翻譯。

wallbreaker 是一個超級懶人(我)為了減少編寫重復性垃圾代碼而產生的一個工具,主要作用是將內存中 Java 類或對象的結構數據進行可視化。

就像介個亞子:

應用場景

如何使用

目前我是比較喜歡以?objection?插件的形式來使用,本來我也想自己寫交互式控制臺,但我覺得?objection?已經寫得挺好,直接上車就好了,所以暫時不打算自己實現了。
開發的時候就使用?ipython?或者寫?testcase?調試。

  • 安裝 objection:pip3 install objection
  • 下載?wallbreaker?到自己的插件目錄:git clone https://github.com/hluwa/Wallbreaker ~/.objection/plugins/Wallbreaker
  • 啟動 frida-server,使用?-P?參數帶著插件啟動?objection:objection -g com.app.name explore -P ~/.objection/plugins
  • 然后就可以愉快的使用?wallbreaker 的幾個命令了:

    • 搜索 plugin wallbreaker classsearch <type-pattern>
      ? ? 根據給的 pattern 對所有類名進行匹配,列出匹配到的所有類名。[return all matched class]
    • 搜索 實例對象:plugin wallbreaker objectsearch <instance-class-name>
      ? ? 根據類名搜索內存中已經被創建的實例,列出?handle?和?toString()?的結果。?[return all matched object-handle and toString]
    • ClassDump,輸出類的結構:plugin wallbreaker classdump <class-name> [--fullname]
      ? ? 輸出類的結構, 若加了?--fullname?參數,打印的數據中類名會帶著完整的包名。
      ? ? ? ? [
      ? ? ? ? ? ?pretty print class structure: fields declare, static field value, methods declare.
      ? ? ? ? ? ? ? set --fullname to display package name of type name.
      ? ? ? ? ]
    • ObjectDump:plugin wallbreaker objectdump <object-handle> [--fullname] [--as-class class-name]
      ? ? 在 ClassDump 的基礎上,輸出指定對象中的每個字段的數據。
      ? ? ? ? [
      ? ? ? ? ? ?pretty print object structure: fields declare and value, methods declare.
      ? ? ? ? ? ? ? set --fullname to display package name of type name;
      ? ? ? ? ? ? ? set --as-class to cast instance type(super class, not interface).
      ? ? ? ? ? ?if instance is a collection or map, dump all entries.
      ? ? ? ? ]

    DEMO:https://asciinema.org/a/XZf8yLWJylCKJfcaYzcKlNbIy

    2.3 ZenTracer(hook)

    前文中介紹的 objection 已經足夠強大,優點是 hook 準確、粒度細。這里再推薦個好友自己寫的批量 hook 查看調用軌跡的工具ZenTracer?(?https://github.com/hluwa/ZenTracer ),可以更大范圍地 hook,幫助讀者輔助分析。

    # pyenv install 3.8.0 # git clone https://github.com/hluwa/ZenTracer # cd ZenTracer # pyenv local 3.8.0 # python -m pip install --upgrade pip # pip install PyQt5 # pip install frida-tools # python ZenTracer.py

    上述命令執行完畢之后,會出現一個 PyQt 畫出來的界面,如圖 2-10 所示。

    點擊 Action之后,會出現匹配模板(Match RegEx)和過濾模板(Black RegEx)。匹配就是包含的關鍵詞,過濾就是不包含的關鍵詞,見下圖2-11。其代碼實現就是

    通過如下的代碼實現,hook 出來的結果需要通過匹配模板進行匹配,并且篩選剔除掉過濾模板中的內容。

    var matchRegEx = {MATCHREGEX}; var blackRegEx = {BLACKREGEX}; Java.enumerateLoadedClasses({onMatch: function (aClass) {for (var index in matchRegEx) {// console.log(matchRegEx[index]);// 通過匹配模板進行匹配if (match(matchRegEx[index], aClass)) {var is_black = false;for (var i in blackRegEx) {//如果也包含在過濾模板中,則剔除if (match(blackRegEx[i], aClass)) {is_black = true;log(aClass + "' black by '" + blackRegEx[i] + "'");break;}}if (is_black) {break;}log(aClass + "' match by '" + matchRegEx[index] + "'");traceClass(aClass);}}},onComplete: function () {log("Complete.");} });

    通過下述代碼實現的模糊匹配和精準匹配:

    function match(ex, text) {if (ex[1] == ':') {var mode = ex[0];if (mode == 'E') {ex = ex.substr(2, ex.length - 2);return ex == text;} else if (mode == 'M') {ex = ex.substr(2, ex.length - 2);} else {log("Unknown match mode: " + mode + ", current support M(match) and E(equal)")}}return text.match(ex) }

    通過下述代碼實現的導入導出調用棧及觀察結果:

    def export_onClick(self):jobfile = QFileDialog.getSaveFileName(self, 'export', '', 'json file(*.json)')if isinstance(jobfile, tuple):jobfile = jobfile[0]if not jobfile:returnf = open(jobfile, 'w')export = {}export['match_regex'] = self.app.match_regex_listexport['black_regex'] = self.app.black_regex_listtree = {}for tid in self.app.thread_map:tree[self.app.thread_map[tid]['list'][0].text()] = gen_tree(self.app.thread_map[tid]['list'][0])export['tree'] = treef.write(json.dumps(export))f.close()def import_onClick(self):jobfile = QFileDialog.getOpenFileName(self, 'import', '', 'json file(*.json)')if isinstance(jobfile, tuple):jobfile = jobfile[0]if not jobfile:returnf = open(jobfile, 'r')export = json.loads(f.read())for regex in export['match_regex']: self.app.match_regex_list.append(regex), self.app.match_regex_dialog.setupList()for regex in export['black_regex']: self.app.black_regex_list.append(regex), self.app.black_regex_dialog.setupList()for t in export['tree']:tid = t[0: t.index(' - ')]tname = t[t.index(' - ') + 3:]for item in export['tree'][t]:put_tree(self.app, tid, tname, item)

    示例 (?ZenTracer ):hook java.io.File類的所有方法

    我們來完整的演示一遍,比如現在看java.io.File類的所有方法,我們可以這樣操作,首先是精準匹配:

    • 點擊打開 "設置"?應用;
    • 選擇 Action → Match RegEx
    • 輸入E:java.io.File,點擊add,然后關閉窗口
    • 點擊 Action → Start

    可以看到 java.io.File 類的所有方法都被 hook 了,并且像 java.io.File.createTempFile 方法的所有重載也被 hook 了。

    1. 在 "設置"?應用上進行操作,打開幾個子選項的界面之后,觀察方法的參數和返回值;

    2. 導出 json 來觀察方法的調用樹,選擇 File → Export json,導出為 tmp.json,使用 vscode 來 format Document 之后,效果如下:

    {"match_regex": ["E:java.io.File"],"black_regex": [],"tree": {"2 - main": [{"clazz": "java.io.File","method": "exists()","args": [],"child": [],"retval": "false"},{"clazz": "java.io.File","method": "toString()","args": [],"child": [{"clazz": "java.io.File","method": "getPath()","args": [],"child": [],"retval": "/data/user/0/com.android.settings"}],"retval": "/data/user/0/com.android.settings"},{"clazz": "java.io.File","method": "equals(java.lang.Object)","args": ["/data/user/0/com.android.settings"],"child": [{"clazz": "java.io.File","method": "toString()","args": [],"child": [{"clazz": "java.io.File","method": "getPath()","args": [],"child": [],"retval": "/data/user/0/com.android.settings"}],"retval": "/data/user/0/com.android.settings"},{"clazz": "java.io.File","method": "compareTo(java.io.File)","args": ["/data/user/0/com.android.settings"],"child": [{"clazz": "java.io.File","method": "getPath()","args": [],"child": [],"retval": "/data/user_de/0/com.android.settings"},{"clazz": "java.io.File","method": "getPath()","args": [],"child": [],"retval": "/data/user/0/com.android.settings"}],"retval": "48"}],"retval": "false"},

    3. 點擊?Action→Stop,再點擊 Action→Clean,本次觀察結束。

    也可以使用模糊匹配模式,比如輸入M:java.io.File之后,會將諸如 java.io.FileOutputStream 類的諸多方法也都 hook上,見下圖2-14。

    ZenTracer 的目前已知的缺點,無法打印調用棧,無法 hook 構造函數,也就是 $init。當然這些 “缺點” 無非也就是加幾行代碼的事情,整個工具非常不錯,值得用于輔助分析。

    3. Frida 用于抓包

    我們拿到一個app,做的第一件事情往往是先抓包來看,它發送和接收了哪些數據。收包發包是一個app的命門,企業為用戶服務過程中最為關鍵的步驟——注冊、流量商品、游戲數據、點贊評論、下單搶票等行為,均通過收包發包來完成。如果對收包發包的數據沒有校驗,黑灰產業可以直接制作相應的協議刷工具,脫離app本身進行實質性業務操作,為企業和用戶帶來巨大的損失。

    抓包方法

    • 移動應用安全基礎篇:APP抓包姿勢總結:https://www.freebuf.com/column/207041.html
    • Android硬核https抓包, 在多個app親測ok, 自定義ssl也無用:https://www.52pojie.cn/thread-1405917-1-1.html
    • 安卓應用層抓包通殺腳本:https://github.com/r0ysue/r0capture
      ? ? ? ? 安卓應用層協議/框架通殺抓包:實戰篇:https://www.sohu.com/a/445865909_120045376
    • 安卓應用層抓包通殺腳本發布:https://bbs.pediy.com/thread-264283.htm
      ? ? ? ? ? ? ? ? ? ? ?frida_ssl_logger:https://github.com/NetCapture/frida_ssl_logger
    • 安卓強制代理。例如:proxydroid (代理機器人) :https://github.com/madeye/proxydroid? ?apk下載:http://www.xfdown.com/soft/120355.html

    JustTrustMePlus:https://github.com/Mocha-L/JustTrustMePlus

    自識別類名 自動化Hook JustTrustMe 升級版:https://bbs.pediy.com/thread-254114.htm

    重點說下手機抓包

  • JustTrustMePlus 是升級版能夠對抗 okhttp 混淆加密
  • 對于某些 apk 無法使用 charles 抓包(我也不知道原因)
  • 手機端抓包可以使用 httpCarry
  • APP 抓包和微信小程序抓包-Charles 的精簡使用教程:https://blog.csdn.net/liqing0013/article/details/83010531

    解決安卓手機 charles 抓包網絡請求 https 抓包證書認證不通過:https://www.cnblogs.com/wuxianyu/p/14271564.html

    Android 使用 Wireshark 抓包:https://github.com/lasting-yang/frida_bypass_ssl_example/tree/master/Android/android_wireshark_tls???????

    frida hook 住 okhttp 實現抓包:https://github.com/siyujie/OkHttpLogger-Frida

    ① 首先將?okhttpfind.dex?拷貝到?/data/local/tmp/?目錄下。執行命令啟動?frida -U -l okhttp_poker.js -f com.example.demo --no-pause?可追加?-o [output filepath]保存到文件。注意:?-f 參數是不管 app 啟動沒有,都直接啟動 app,如果 app 已經啟動,則可以使用 -F 參數,直接附加到 已經啟動的 app 上

    (? )

    ② 調用函數開始執行

    • find() 要等完全啟動并執行過網絡請求后再進行調用
    • hold() 要等完全啟動再進行調用
    • history() & resend() 只有可以重新發送的請求

    坑、坑、坑、坑、坑、坑:

    • 1. https://github.com/siyujie/OkHttpLogger-Frida:這個使用 -F 參數可以成功抓到包。(?okhtpfind.dex?內包含了 更改了包名的okio以及Gson )
    • 2. https://bbs.pediy.com/thread-252129.htm:這個使用 -F 沒法抓到包,使用 -f 參數可以抓到包。( 這個沒有?更改包名?okio以及Gson )

    打印堆棧:

    function showStacks() {send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); }

    在 okhttp_poker.js 文件中的?printerRequest 函數中添加打印堆棧,就可以在打印抓包時候,把堆棧調用也打印出來

    運行截圖:

    抓包結果:

    okhttp_poker.js

    function showStacks() {send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); }function hook_okhttp3(classLoader) {Java.perform(function () {console.log("開始執行注入");var ByteString = classLoader.use("com.android.okhttp.okio.ByteString");console.log("com.android.okhttp.okio.ByteString ---> load");var Buffer = classLoader.use("com.android.okhttp.okio.Buffer");console.log("com.android.okhttp.okio.Buffer ---> load");var Interceptor = classLoader.use("okhttp3.Interceptor");console.log("okhttp3.Interceptor ---> load");var MyInterceptor = Java.registerClass({name: "okhttp3.MyInterceptor",implements: [Interceptor],methods: {intercept: function (chain) {var request = chain.request();try {console.log("#################################################################################")showStacks();console.log("\n")console.log("MyInterceptor.intercept onEnter:", request, "\nrequest headers:\n", request.headers());var requestBody = request.body();var contentLength = requestBody ? requestBody.contentLength() : 0;if (contentLength > 0) {var BufferObj = Buffer.$new();requestBody.writeTo(BufferObj);try {console.log("\nrequest body String:\n", BufferObj.readString(), "\n");} catch (error) {try {console.log("\nrequest body ByteString:\n", ByteString.of(BufferObj.readByteArray()).hex(), "\n");} catch (error) {console.log("error 1:", error);}}}} catch (error) {console.log("error 2:", error);}console.log("#################################################################################")console.log("\n")var response = chain.proceed(request);try {console.log("MyInterceptor.intercept onLeave:", response, "\nresponse headers:\n", response.headers());var responseBody = response.body();var contentLength = responseBody ? responseBody.contentLength() : 0;if (contentLength > 0) {console.log("\nresponsecontentLength:", contentLength, "responseBody:", responseBody, "\n");var ContentType = response.headers().get("Content-Type");console.log("ContentType:", ContentType);if (ContentType.indexOf("video") == -1) {if (ContentType.indexOf("application") == 0) {var source = responseBody.source();if (ContentType.indexOf("application/zip") != 0) {try {console.log("\nresponse.body StringClass\n", source.readUtf8(), "\n");} catch (error) {try {console.log("\nresponse.body ByteString\n", source.readByteString().hex(), "\n");} catch (error) {console.log("error 4:", error);}}}}}}} catch (error) {console.log("error 3:", error);}console.log("#################################################################################")console.log("\n")return response;}}});var ArrayList = classLoader.use("java.util.ArrayList");var OkHttpClient = classLoader.use("okhttp3.OkHttpClient");console.log(OkHttpClient);OkHttpClient.$init.overload('okhttp3.OkHttpClient$Builder').implementation = function (Builder) {console.log("OkHttpClient.$init:", this, Java.cast(Builder.interceptors(), ArrayList));this.$init(Builder);};var MyInterceptorObj = MyInterceptor.$new();var Builder = classLoader.use("okhttp3.OkHttpClient$Builder");console.log(Builder);Builder.build.implementation = function () {this.interceptors().clear();//var MyInterceptorObj = MyInterceptor.$new();this.interceptors().add(MyInterceptorObj);var result = this.build();return result;};Builder.addInterceptor.implementation = function (interceptor) {this.interceptors().clear();//var MyInterceptorObj = MyInterceptor.$new();this.interceptors().add(MyInterceptorObj);return this;//return this.addInterceptor(interceptor);};console.log("hook_okhttp3...");}); }Java.perform(function() {var application = Java.use("android.app.Application");application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先執行原來的attach方法var classloader = context.getClassLoader(); // 獲取classloaderJava.classFactory.loader = classloader;hook_okhttp3(Java.classFactory);}});

    okhttp_poker.js

    /** 使用說明 首先將 okhttpfind.dex 拷貝到 /data/local/tmp/ 目錄下 例:frida -U -l okhttp_poker.js -f com.example.demo --no-pause 接下來使用okhttp的所有請求將被攔截并打印出來; 擴展函數:find() 檢查是否使用了Okhttp & 是否可能被混淆 & 尋找okhttp3關鍵類及函數switchLoader(\"okhttp3.OkHttpClient\") 參數:靜態分析到的okhttpclient類名hold() 開啟HOOK攔截history() 打印可重新發送的請求 resend(index) 重新發送請求 備注 : okhtpfind.dex 內包含了 更改了包名的okio以及Gson,以及Java寫的尋找okhttp特征的代碼。 okhttpfind.dex 源碼鏈接 https://github.com/siyujie/okhttp_find原理:由于所有使用的okhttp框架的App發出的請求都是通過RealCall.java發出的,那么我們可以hook此類拿到request和response, 也可以緩存下來每一個請求的call對象,進行再次請求,所以選擇了此處進行hook。*/ var Cls_Call = "okhttp3.Call"; var Cls_CallBack = "okhttp3.Callback"; var Cls_OkHttpClient = "okhttp3.OkHttpClient"; var Cls_Request = "okhttp3.Request"; var Cls_Response = "okhttp3.Response"; var Cls_ResponseBody = "okhttp3.ResponseBody"; var Cls_okio_Buffer = "okio.Buffer"; var F_header_namesAndValues = "namesAndValues"; var F_req_body = "body"; var F_req_headers = "headers"; var F_req_method = "method"; var F_req_url = "url"; var F_rsp$builder_body = "body"; var F_rsp_body = "body"; var F_rsp_code = "code"; var F_rsp_headers = "headers"; var F_rsp_message = "message"; var F_rsp_request = "request"; var M_CallBack_onFailure = "onFailure"; var M_CallBack_onResponse = "onResponse"; var M_Call_enqueue = "enqueue"; var M_Call_execute = "execute"; var M_Call_request = "request"; var M_Client_newCall = "newCall"; var M_buffer_readByteArray = "readByteArray"; var M_contentType_charset = "charset"; var M_reqbody_contentLength = "contentLength"; var M_reqbody_contentType = "contentType"; var M_reqbody_writeTo = "writeTo"; var M_rsp$builder_build = "build"; var M_rspBody_contentLength = "contentLength"; var M_rspBody_contentType = "contentType"; var M_rspBody_create = "create"; var M_rspBody_source = "source"; var M_rsp_newBuilder = "newBuilder";//---------------------------------- var JavaStringWapper = null; var JavaIntegerWapper = null; var JavaStringBufferWapper = null; var GsonWapper = null; var ListWapper = null; var ArrayListWapper = null; var ArraysWapper = null; var CharsetWapper = null; var CharacterWapper = null;var OkioByteStrngWapper = null; var OkioBufferWapper = null;var OkHttpClientWapper = null; var ResponseBodyWapper = null; var BufferWapper = null; var Utils = null; //---------------------------------- var CallCache = [] var hookedArray = [] var filterArray = ["JPG", "jpg", "PNG", "png", "WEBP", "webp", "JPEG", "jpeg", "GIF", "gif",".zip", ".data"]function buildNewResponse(responseObject) {var newResponse = null;Java.perform(function () {try {var logString = JavaStringBufferWapper.$new()logString.append("").append("\n");logString.append("┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────").append("\n");newResponse = printAll(responseObject, logString)logString.append("└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────").append("\n");logString.append("").append("\n");console.log(logString)} catch (error) {console.log("printAll ERROR : " + error);}})return newResponse; }function printAll(responseObject, logString) {try {var request = getFieldValue(responseObject, F_rsp_request)printerRequest(request, logString)} catch (error) {console.log("print request error : ", error.stack)return responseObject;}var newResponse = printerResponse(responseObject, logString)return newResponse; }function printerRequest(request, logString) {var defChatset = CharsetWapper.forName("UTF-8")//URLvar httpUrl = getFieldValue(request, F_req_url)logString.append("| URL: " + httpUrl).append("\n")logString.append("|").append("\n")logString.append("| Method: " + getFieldValue(request, F_req_method)).append("\n")logString.append("|").append("\n")var requestBody = getFieldValue(request, F_req_body);var hasRequestBody = trueif (null == requestBody) {hasRequestBody = false}//Headersvar requestHeaders = getFieldValue(request, F_req_headers)var headersList = headersToList(requestHeaders)var headersSize = getHeaderSize(headersList)logString.append("| Request Headers: ").append("" + headersSize).append("\n")if (hasRequestBody) {var requestBody = getWrapper(requestBody)var contentType = requestBody[M_reqbody_contentType]()if (null != contentType) {logString.append("| ┌─" + "Content-Type: " + contentType).append("\n")}var contentLength = requestBody[M_reqbody_contentLength]()if (contentLength != -1) {var tag = headersSize == 0 ? "└─" : "┌─"logString.append("| " + tag + "Content-Length: " + contentLength).append("\n")}}if (headersSize == 0) {logString.append("| no headers").append("\n")}for (var i = 0; i < headersSize; i++) {var name = getHeaderName(headersList, i)if (!JavaStringWapper.$new("Content-Type").equalsIgnoreCase(name) && !JavaStringWapper.$new("Content-Length").equalsIgnoreCase(name)) {var value = getHeaderValue(headersList, i)var tag = i == (headersSize - 1) ? "└─" : "┌─"logString.append("| " + tag + name + ": " + value).append("\n")}}var shielded = filterUrl(httpUrl.toString())if (shielded) {logString.append("|" + " File Request Body Omit.....").append("\n")return;}logString.append("|").append("\n")if (!hasRequestBody) {logString.append("|" + "--> END ").append("\n")} else if (bodyEncoded(headersList)) {logString.append("|" + "--> END (encoded body omitted > bodyEncoded)").append("\n")} else {logString.append("| Request Body:").append("\n")var buffer = BufferWapper.$new()requestBody[M_reqbody_writeTo](buffer)var reqByteString = getByteString(buffer)var charset = defChatsetvar contentType = requestBody[M_reqbody_contentType]()if (null != contentType) {var appcharset = contentType[M_contentType_charset](defChatset);if (null != appcharset) {charset = appcharset;}}//LOG Request Bodytry {if (isPlaintext(reqByteString)) {logString.append(splitLine(readBufferString(reqByteString, charset), "| ")).append("\n")logString.append("|").append("\n")logString.append("|" + "--> END ").append("\n")} else {logString.append(splitLine(hexToUtf8(reqByteString.hex()), "| ")).append("\n")logString.append("|").append("\n");logString.append("|" + "--> END (binary body omitted -> isPlaintext)").append("\n")}} catch (error) {logString.append(splitLine(hexToUtf8(reqByteString.hex()), "| ")).append("\n")logString.append("|").append("\n");logString.append("|" + "--> END (binary body omitted -> isPlaintext)").append("\n")}}logString.append("|").append("\n"); }function printerResponse(response, logString) {var newResponse = null;try {var defChatset = CharsetWapper.forName("UTF-8")var request = getFieldValue(response, F_rsp_request)var url = getFieldValue(request, F_req_url)var shielded = filterUrl(url.toString())if (shielded) {logString.append("|" + " File Response Body Omit.....").append("\n")return response;}//URLlogString.append("| URL: " + url).append("\n")logString.append("|").append("\n")logString.append("| Status Code: " + getFieldValue(response, F_rsp_code) + " / " + getFieldValue(response, F_rsp_message)).append("\n")logString.append("|").append("\n")var responseBodyObj = getFieldValue(response, F_rsp_body)var responseBody = getWrapper(responseBodyObj)var contentLength = responseBody[M_rspBody_contentLength]()//Headersvar resp_headers = getFieldValue(response, F_rsp_headers)var respHeadersList = headersToList(resp_headers)var respHeaderSize = getHeaderSize(respHeadersList)logString.append("| Response Headers: ").append("" + respHeaderSize).append("\n")if (respHeaderSize == 0) {logString.append("| no headers").append("\n")}for (var i = 0; i < respHeaderSize; i++) {var tag = i == (respHeaderSize - 1) ? "└─" : "┌─"logString.append("| " + tag + getHeaderName(respHeadersList, i) + ": " + getHeaderValue(respHeadersList, i)).append("\n")}//Bodyvar content = "";var nobody = !hasBody(response, respHeadersList)if (nobody) {logString.append("| No Response Body : " + response).append("\n")logString.append("|" + "<-- END HTTP").append("\n")} else if (bodyEncoded(respHeadersList)) {logString.append("|" + "<-- END HTTP (encoded body omitted)").append("\n")} else {logString.append("| ").append("\n");logString.append("| Response Body:").append("\n")var source = responseBody[M_rspBody_source]()var rspByteString = getByteString(source)var charset = defChatsetvar contentType = responseBody[M_rspBody_contentType]()if (null != contentType) {var appcharset = contentType[M_contentType_charset](defChatset)if (null != appcharset) {charset = appcharset}}//newResponsevar mediaType = responseBody[M_rspBody_contentType]()var newBody = null;try {newBody = ResponseBodyWapper[M_rspBody_create](mediaType, rspByteString.toByteArray())} catch (error) {newBody = ResponseBodyWapper[M_rspBody_create](mediaType, readBufferString(rspByteString, charset))}var newBuilder = null;if ("" == M_rsp_newBuilder) {var ResponseBuilderClazz = response.class.getDeclaredClasses()[0]newBuilder = Java.use(ResponseBuilderClazz.getName()).$new(response)} else {newBuilder = response[M_rsp_newBuilder]()}var bodyField = newBuilder.class.getDeclaredField(F_rsp$builder_body)bodyField.setAccessible(true)bodyField.set(newBuilder, newBody)newResponse = newBuilder[M_rsp$builder_build]()if (!isPlaintext(rspByteString)) {logString.append("|" + "<-- END HTTP (binary body omitted)").append("\n");}if (contentLength != 0) {try {var content = readBufferString(rspByteString, charset)logString.append(splitLine(content, "| ")).append("\n")} catch (error) {logString.append(splitLine(hexToUtf8(rspByteString.hex()), "| ")).append("\n")}logString.append("| ").append("\n");}logString.append("|" + "<-- END HTTP").append("\n");}} catch (error) {logString.append("print response error : " + error).append("\n")if (null == newResponse) {return response;}}return newResponse; }/*** hex to string*/ function hexToUtf8(hex) {try {return decodeURIComponent('%' + hex.match(/.{1,2}/g).join('%'));} catch (error) {return "hex[" + hex + "]";} }/***/ function getFieldValue(object, fieldName) {var field = object.class.getDeclaredField(fieldName);field.setAccessible(true)var fieldValue = field.get(object)if (null == fieldValue) {return null;}var FieldClazz = Java.use(fieldValue.$className)var fieldValueWapper = Java.cast(fieldValue, FieldClazz)return fieldValueWapper } /***/ function getWrapper(javaobject) {return Java.cast(javaobject, Java.use(javaobject.$className)) }/***/ function headersToList(headers) {var gson = GsonWapper.$new()var namesAndValues = getFieldValue(headers, F_header_namesAndValues)var jsonString = gson.toJson(namesAndValues)var namesAndValuesList = Java.cast(gson.fromJson(jsonString, ListWapper.class), ListWapper)return namesAndValuesList; }function getHeaderSize(namesAndValuesList) {return namesAndValuesList.size() / 2 }function getHeaderName(namesAndValuesList, index) {return namesAndValuesList.get(index * 2) } function getHeaderValue(namesAndValuesList, index) {return namesAndValuesList.get((index * 2) + 1) }function getByHeader(namesAndValuesList, name) {var nameString = JavaStringWapper.$new(name)Java.perform(function () {var length = namesAndValuesList.size()var nameByList = "";do {length -= 2;if (length < 0) {return null;}// console.log("namesAndValuesList: "+namesAndValuesList.$className)nameByList = namesAndValuesList.get(JavaIntegerWapper.valueOf(length).intValue())} while (!nameString.equalsIgnoreCase(nameByList));return namesAndValuesList.get(length + 1);}) }function bodyEncoded(namesAndValuesList) {if (null == namesAndValuesList) return false;var contentEncoding = getByHeader(namesAndValuesList, "Content-Encoding")var bodyEncoded = contentEncoding != null && !JavaStringWapper.$new("identity").equalsIgnoreCase(contentEncoding)return bodyEncoded}function hasBody(response, namesAndValuesList) {var request = getFieldValue(response, F_rsp_request)var m = getFieldValue(request, F_req_method);if (JavaStringWapper.$new("HEAD").equals(m)) {return false;}var Transfer_Encoding = "";var respHeaderSize = getHeaderSize(namesAndValuesList)for (var i = 0; i < respHeaderSize; i++) {if (JavaStringWapper.$new("Transfer-Encoding").equals(getHeaderName(namesAndValuesList, i))) {Transfer_Encoding = getHeaderValue(namesAndValuesList, i);break}}var code = getFieldValue(response, F_rsp_code)if (((code >= 100 && code < 200) || code == 204 || code == 304)&& response[M_rspBody_contentLength] == -1&& !JavaStringWapper.$new("chunked").equalsIgnoreCase(Transfer_Encoding)) {return false;}return true; }function isPlaintext(byteString) {try {var bufferSize = byteString.size()var buffer = NewBuffer(byteString)for (var i = 0; i < 16; i++) {if (bufferSize == 0) {console.log("bufferSize == 0")break}var codePoint = buffer.readUtf8CodePoint()if (CharacterWapper.isISOControl(codePoint) && !CharacterWapper.isWhitespace(codePoint)) {return false;}}return true;} catch (error) {// console.log(error)// console.log(Java.use("android.util.Log").getStackTraceString(error))return false;} }function getByteString(buffer) {var bytearray = buffer[M_buffer_readByteArray]();var byteString = OkioByteStrngWapper.of(bytearray)return byteString; }function NewBuffer(byteString) {var buffer = OkioBufferWapper.$new()byteString.write(buffer)return buffer; }function readBufferString(byteString, chatset) {var byteArray = byteString.toByteArray();var str = JavaStringWapper.$new(byteArray, chatset)return str; }function splitLine(string, tag) {var newSB = JavaStringBufferWapper.$new()var newString = JavaStringWapper.$new(string)var lineNum = Math.ceil(newString.length() / 150)for (var i = 0; i < lineNum; i++) {var start = i * 150;var end = (i + 1) * 150newSB.append(tag)if (end > newString.length()) {newSB.append(newString.substring(start, newString.length()))} else {newSB.append(newString.substring(start, end))}newSB.append("\n")}var lineStr = "";if (newSB.length() > 0) {lineStr = newSB.deleteCharAt(newSB.length() - 1).toString()}return lineStr }/*** */ function alreadyHook(str) {for (var i = 0; i < hookedArray.length; i++) {if (str == hookedArray[i]) {return true;}}return false; }/*** */ function filterUrl(url) {for (var i = 0; i < filterArray.length; i++) {if (url.indexOf(filterArray[i]) != -1) {// console.log(url + " ?? " + filterArray[i])return true;}}return false; }function hookRealCall(realCallClassName) {Java.perform(function () {console.log(" ........... hookRealCall : " + realCallClassName)var RealCall = Java.use(realCallClassName)if ("" != Cls_CallBack) {//異步RealCall[M_Call_enqueue].overload(Cls_CallBack).implementation = function (callback) {// console.log("-------------------------------------HOOK SUCCESS 異步--------------------------------------------------")var realCallBack = Java.use(callback.$className)realCallBack[M_CallBack_onResponse].overload(Cls_Call,Cls_Response).implementation = function(call, response){var newResponse = buildNewResponse(response)this[M_CallBack_onResponse](call,newResponse)}this[M_Call_enqueue](callback)realCallBack.$dispose}}//同步 RealCall[M_Call_execute].overload().implementation = function () {// console.log("-------------------------------------HOOK SUCCESS 同步--------------------------------------------------")var response = this[M_Call_execute]()var newResponse = buildNewResponse(response)return newResponse;}}) }/*** check className & filter*/ function checkClass(name) {if (name.startsWith("com.")|| name.startsWith("cn.")|| name.startsWith("io.")|| name.startsWith("org.")|| name.startsWith("android")|| name.startsWith("kotlin")|| name.startsWith("[")|| name.startsWith("java")|| name.startsWith("sun.")|| name.startsWith("net.")|| name.indexOf(".") < 0|| name.startsWith("dalvik")) {return false;}return true; }/** * print request history */ function history() {Java.perform(function () {try {console.log("")console.log("History Size : " + CallCache.length)for (var i = 0; i < CallCache.length; i++) {var call = CallCache[i]if ("" != M_Call_request) {console.log("-----> index[" + i + "]" + " >> " + call[M_Call_request]())} else {console.log("-----> index[" + i + "]" + " ???? M_Call_execute = \"\"")}console.log("")}console.log("")} catch (error) {console.log(error)}}) }/** * resend request */ function resend(index) {Java.perform(function () {try {console.log("resend >> " + index)var call = CallCache[index]if ("" != M_Call_execute) {call[M_Call_execute]()} else {console.log("M_Call_execute = null")}} catch (error) {console.log("Error : " + error)}}) }/*** 開啟HOOK攔截*/ function hold() {Java.perform(function () {//Utils = Java.use("com.singleman.okhttp.Utils")//Init commonJavaStringWapper = Java.use("java.lang.String")JavaStringBufferWapper = Java.use("java.lang.StringBuilder")JavaIntegerWapper = Java.use("java.lang.Integer")GsonWapper = Java.use("com.singleman.gson.Gson")ListWapper = Java.use("java.util.List")ArraysWapper = Java.use("java.util.Arrays")ArrayListWapper = Java.use("java.util.ArrayList")CharsetWapper = Java.use("java.nio.charset.Charset")CharacterWapper = Java.use("java.lang.Character")OkioByteStrngWapper = Java.use("com.singleman.okio.ByteString")OkioBufferWapper = Java.use("com.singleman.okio.Buffer")//Init OKHTTPOkHttpClientWapper = Java.use(Cls_OkHttpClient)ResponseBodyWapper = Java.use(Cls_ResponseBody)BufferWapper = Java.use(Cls_okio_Buffer)//Start HookOkHttpClientWapper[M_Client_newCall].overload(Cls_Request).implementation = function (request) {var call = this[M_Client_newCall](request)try {CallCache.push(call["clone"]())} catch (error) {console.log("not fount clone method!")}var realCallClassName = call.$classNameif (!alreadyHook(realCallClassName)) {hookedArray.push(realCallClassName)hookRealCall(realCallClassName)}return call;}}) }function switchLoader(clientName) {Java.perform(function () {if ("" != clientName) {try {var clz = Java.classFactory.loader.findClass(clientName)console.log("")console.log(">>>>>>>>>>>>> ", clz, " <<<<<<<<<<<<<<<<")} catch (error) {console.log(error)Java.enumerateClassLoaders({onMatch: function (loader) {try {if (loader.findClass(clientName)) {Java.classFactory.loader = loaderconsole.log("")console.log("Switch ClassLoader To : ", loader)console.log("")}} catch (error) {// console.log(error)}},onComplete: function () {console.log("")console.log("Switch ClassLoader Complete !")console.log("")}})}}Java.openClassFile("/data/local/tmp/okhttpfind.dex").load()}) }/*** find & print used location*/ function find() {Java.perform(function () {ArraysWapper = Java.use("java.util.Arrays")ArrayListWapper = Java.use("java.util.ArrayList")var isSupport = false;var clz_Protocol = null;try {var clazzNameList = Java.enumerateLoadedClassesSync()if (clazzNameList.length == 0) {console.log("ERROR >> [enumerateLoadedClasses] return null !!!!!!")return}for (var i = 0; i < clazzNameList.length; i++) {var name = clazzNameList[i]if (!checkClass(name)) {continue}try {var loadedClazz = Java.classFactory.loader.loadClass(name);if (loadedClazz.isEnum()) {var Protocol = Java.use(name);var toString = ArraysWapper.toString(Protocol.values());if (toString.indexOf("http/1.0") != -1&& toString.indexOf("http/1.1") != -1&& toString.indexOf("spdy/3.1") != -1&& toString.indexOf("h2") != -1) {clz_Protocol = loadedClazz;break;}}} catch (error) {}}if (null == clz_Protocol) {console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~ 尋找okhttp特征失敗,請確認是否使用okhttp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")return}//enum values >> Not to be confused with!var okhttp_pn = clz_Protocol.getPackage().getName();var likelyOkHttpClient = okhttp_pn + ".OkHttpClient"try {var clz_okclient = Java.use(likelyOkHttpClient).classif (null != clz_okclient) {console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 未 混 淆 (僅參考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")isSupport = true;}} catch (error) {console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 被 混 淆 (僅參考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")isSupport = true;}} catch (error) {console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~未使用okhttp~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")isSupport = false;}if (!isSupport) {console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~ 尋找okhttp特征失敗,請確認是否使用okhttp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")return}var likelyClazzList = ArrayListWapper.$new()for (var i = 0; i < clazzNameList.length; i++) {var name = clazzNameList[i]if (!checkClass(name)) {continue}try {var loadedClazz = Java.classFactory.loader.loadClass(name);likelyClazzList.add(loadedClazz)} catch (error) {}}console.log("likelyClazzList size :" + likelyClazzList.size())if (likelyClazzList.size() == 0) {console.log("Please make a network request and try again!")}console.log("")console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start Find~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")console.log("")try {var OkHttpFinder = Java.use("com.singleman.okhttp.OkHttpFinder")OkHttpFinder.getInstance().findClassInit(likelyClazzList)console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Result~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")var OkCompatClazz = Java.use("com.singleman.okhttp.OkCompat").classvar fields = OkCompatClazz.getDeclaredFields();for (var i = 0; i < fields.length; i++) {var field = fields[i]field.setAccessible(true);var name = field.getName()var value = field.get(null)console.log("var " + name + " = \"" + value + "\";")}console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Complete~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")} catch (error) {console.log(error)//console.log(Java.use("android.util.Log").getStackTraceString(error))}}) }/***/ function main() {Java.perform(function () {Java.openClassFile("/data/local/tmp/okhttpfind.dex").load()var version = Java.use("com.singleman.SingleMan").class.getDeclaredField("version").get(null)console.log("");console.log("------------------------- OkHttp Poker by SingleMan [" + version + "]------------------------------------");console.log("API:")console.log(" >>> find() 檢查是否使用了Okhttp & 是否可能被混淆 & 尋找okhttp3關鍵類及函數");console.log(" >>> switchLoader(\"okhttp3.OkHttpClient\") 參數:靜態分析到的okhttpclient類名");console.log(" >>> hold() 開啟HOOK攔截");console.log(" >>> history() 打印可重新發送的請求");console.log(" >>> resend(index) 重新發送請求");console.log("----------------------------------------------------------------------------------------");}) }setImmediate(main)

    http 抓包

    • 正常代理來抓
    • 強制代理抓
    • JustTrustMe 來抓
    • JustTrustMePlus 來抓
    • frida_ssl_logger 來抓
    • r0capture 來抓
    • 硬核 https 抓包


    3.1 推薦抓包環境

    由上所述,抓包是每一位安全工程師必須掌握的技能。而抓包一般又分為以下兩種情形:

    • 應用層:Http(s)協議抓包。
      ? ? ? ? 如果是抓應用層Http(s),推薦的專業工具是BurpSuite,如果只是想簡單的抓包、用的舒服輕松,也可以使用花瓶(Charles)。
      ? ? ? ? 不推薦使用 fiddle,因為它無法導入客戶端證書(p12、Client SSL Certificates),對于服務器校驗客戶端證書的情況無法 Bypass;
    • 會話層:Socket?端口通信抓包。
      ? ? ? ? 如果是會話層抓包,則選擇 tcpdump 和 WireShark 相組合的方式。

    使用 jnettop 還可以實時查看流量走勢和對方IP地址,更為直觀和生動。

    在手機上設置代理時,推薦使用 VPN 來將流量導出到抓包軟件上,而不是通過給 WIFI 設置 HTTP 代理的方式。使用 VPN 可以同時抓到 Http(s) 和 Socket 的包,且不管其來自 Java層還是so層。我們常用的代理軟件是老牌的 Postern,開 VPN 服務通過連接到開啟 Socks5 服務端的抓包軟件,將流量導出去。?

    當然有些應用會使用 System.getProperty("http.proxyHost")、System.getProperty("http.proxyPort");?這兩個API來查看當前系統是否掛了VPN,這時候只能用 Frida 或 Xposed 來 hook 這個接口、修改其返回值,或者重打包來 nop 掉。當然還有一種最為終極、最為強悍的方法,那就是制作路由器,抓所有過網卡的包。

    制作路由器的方法也很簡單,給筆記本電腦裝 Kali Linux,eth0口插網線上網,wlan0口使用系統自帶的熱點功能,手機連上熱點上網。史上最強,安卓應用是無法對抗的。

    另外,曾經有人問我,像這樣的一個場景如何抓包:

    問:最近在分析手機搬家類軟件的協議,不知道用什么去抓包,系統應用,不可卸載那種。搬家場景:兩臺手機打開搬家軟件,一臺會創建熱點,另一臺手機連接該熱點后,通過搬家軟件傳輸數據。求大佬指點抓包方法。

    這個場景是有點難度的,我們把開熱點的手機假設為A,連接熱點的手機假設為B。另外準備一臺抓包電腦,連接上A開的熱點。在B上安裝VPN軟件Postern,服務器設置為抓包電腦,這樣B應該可以正常連接到A,B的所有流量也是從抓包電腦走的,可以抓到所有的包。

    在抓包的對抗上體現的也是兩個原則,一是理解的越成熟思路越多,二是對抗的戰場越深上層越無法防御。

    3.2 Http(s) 多場景分析

    從防護的強度來看,Https 的強度是遠遠大于 Http 的;從大型分布式 C/S 架構的設計來看,如果服務器數量非常多、app版本眾多,app在實現Https的策略上通常會采取客戶端校驗服務器證書的策略,如果服務器數量比較少,全國就那么幾臺、且app版本較少、對app版本管控較為嚴格,app在實現Https的策略時會加上服務器校驗客戶端證書的策略。

    接下來我們具體分析每一種情況。

    3.2.1 Http 抓包

    對于Http的抓包,只要在電腦的Charles上配置好Socks5服務器,手機上用Postern開啟VPN連上電腦上的Charles的Socks5服務器,所有流量即可導出到Charles上。當然使用BurpSuite也是一樣的道理。至于具體的操作步驟網上文檔浩如煙海,讀者可以自行取閱。

    一般大型app、服務器數量非常多的,尤其還配置了多種CDN在全國范圍、三網內進行內容分發和加速分發的,通常app里絕大多數內容都是走的Http。

    當然他們會在最關鍵的業務上,比如用戶登錄時,配置Https協議,來保證最基本的安全。

    3.2.2 Https客戶端校驗服務器

    這時候我們抓 app 的 Http 流量的時候一切正常,圖片、視頻、音樂都直接下載和轉儲。

    但是作為用戶要登錄的時候,就會發現抓包失敗,這時候開啟 Charles 的 SSL 抓包功能,手機瀏覽器輸入Charles的證書下載地址chls.pro/ssl,下載證書并安裝到手機中。

    注意在高版本的安卓上,用戶安裝的證書并不會安裝到系統根證書目錄中去,需要root手機后將用戶安裝的證書移動到系統根證書目錄中去,具體操作步驟網上非常多,這里不再贅述。

    當?Charles?的證書安裝到系統根目錄中去之后,系統就會信任來自Charles的流量包了,我們的抓包過程就會回歸正常。

    當然,這里還是會有讀者疑惑,為什么導入Charles的證書之后,app抓包就正常了呢?

    應用層 Https 抓包的根本原理

    這里我們就需要理解一下應用層 Https 抓包的根本原理,

    見下圖2-15(會話層Socket抓包并不是這個原理,后文會介紹Socket抓包的根本原理)。

    有了 Charles 置于中間之后,本來 C/S 架構的通信過程會 “分裂” 為兩個獨立的通信過程,app本來驗證的是服務器的證書,服務器的證書手機的根證書是認可的,直接內置的;但是分裂成兩個獨立的通信過程之后,app驗證的是Charles的證書,它的證書手機根證書并不認可,它并不是由手機內置的權威根證書簽發機構簽發的,所以手機不認,然后app也不認;所以我們要把Charles的證書導入到手機根證書目錄中去,這樣手機就會認可,如果app沒有進行額外的校驗(比如在代碼中對該證書進行校驗,也就是SSL pinning系列API,這種情況下一小節具體闡述)的話,app也會直接認可接受。

    3.3.3 Https服務器校驗客戶端

    既然 app 客戶端會校驗服務器證書,那么服務器可不可能校驗app客戶端證書呢?答案是肯定的。

    在許多業務非常聚焦并且當單一,比如行業應用、銀行、公共交通、游戲等行業,C/S架構中服務器高度集中,對應用的版本控制非常嚴格,這時候就會在服務器上部署對app內置證書的校驗代碼。

    上一小節中已經看到,單一通信已經分裂成兩個互相獨立的通信,這時候與服務器進行通信的已經不是app、而是Charles了,所以我們要將app中內置的證書導入到Charles中去。

    這個操作通常需要完成兩項內容:

  • 找到證書文件
  • 找到證書密碼
  • 找到證書文件很簡單,一般 apk 進行解包,直接過濾搜索后綴名為 p12 的文件即可,一般常用的命令為 tree -NCfhl |grep -i p12,直接打印出 p12 文件的路徑,當然也有一些 app 比較 “狡猾”,比如我們通過搜索 p12 沒有搜到證書,然后看 jadx 反編譯的源碼得出它將證書偽裝成 border_ks_19 文件,我們找到這個文件用 file 命令查看果然不是后綴名所顯示的 png 格式,將其改成 p12 的后綴名嘗試打開時要求輸入密碼,可見其確實是一個證書,見下圖2-17。

    想要拿到密碼也很簡單,一般在 jadx 反編譯的代碼中或者so庫拖進IDA后可以看到硬編碼的明文;也可以使用下面這一段腳本,直接打印出來,終于到了Frida派上用場的時候。

    function hook_KeyStore_load() {Java.perform(function () {var StringClass = Java.use("java.lang.String");var KeyStore = Java.use("java.security.KeyStore");KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) {printStack("KeyStore.load1");console.log("KeyStore.load1:", arg0);this.load(arg0);};KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) {printStack("KeyStore.load2");console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null);this.load(arg0, arg1);};console.log("hook_KeyStore_load...");}); }

    打印出來的效果如下圖2-18,直接將密碼打印了出來。

    當然其實也并不一定非要用?Frida,用?Xposed?也可以,只是?Xposed?很久不更新了,最近流行的大趨勢是?Frida。

    有了證書和密碼之后,就可以將其導入到抓包軟件中,在 Charles中是位于 Proxy→SSL Proxy Settings→Client Certificates→Add 添加新的證書,輸入指定的域名或IP使用指定的證書即可,見下圖2-19。

    3.3 SSL Pinning Bypass

    上文中我們還有一種情況沒有分析,就是客戶端并不會默認信任系統根證書目錄中的證書,而是在代碼里再加一層校驗,這就是證書綁定機制——SSL pinning,如果這段代碼的校驗過不了,那么客戶端還是會報證書錯誤。

    Https客戶端代碼校驗服務器證書

    遇到這種情況的時候,我們一般有三種方式,當然目標是一樣的,都是hook住這段校驗的代碼,使這段判斷的機制失效即可。

    方法1:hook 住 checkServerTrusted,將其所有重載都置空;

    function hook_ssl() {Java.perform(function() {var ClassName = "com.android.org.conscrypt.Platform";var Platform = Java.use(ClassName);var targetMethod = "checkServerTrusted";var len = Platform[targetMethod].overloads.length;console.log(len);for(var i = 0; i < len; ++i) {Platform[targetMethod].overloads[i].implementation = function () {console.log("class:", ClassName, "target:", targetMethod, " i:", i, arguments);//printStack(ClassName + "." + targetMethod);}}}); }

    方法2:使用 objection,直接將 SSL pinning 給 disable 掉

    # android sslpinning disable

    方法3:如果還有一些情況沒有覆蓋的話,可以來看看大佬的代碼

    • 目錄 ObjectionUnpinningPlus 增加了 ObjectionUnpinning 沒覆蓋到的鎖定場景.(objection)
      • 使用方法1 attach : frida -U com.example.mennomorsink.webviewtest2 —no-pause -l hooks.js
      • 使用方法2 spawn : python application.py com.example.mennomorsink.webviewtest2
      • 更為詳細使用方法:參考我的文章?Frida.Android.Practice(ssl unpinning)?實戰ssl pinning bypass 章節 .
    • ObjectionUnpinningPlus hook list:
      • SSLcontext(ART only)
      • okhttp
      • webview
      • XUtils(ART only)
      • httpclientandroidlib
      • JSSE
      • network_security_config (android 7.0+)
      • Apache Http client (support partly)
      • OpenSSLSocketImpl
      • TrustKit

    應該可以覆蓋到目前已知的所有種類的證書綁定了。

    3.4?Socket 多場景分析

    當我們在使用 Charles 進行抓包的時候,會發現針對某些 IP 的數據傳輸一直顯示 CONNECT,無法 Complete,顯示 Sending request body,并且數據包大小持續增長,這時候說明我們遇到了Socket端口通信。

    Socket 端口通信運行在會話層,并不是應用層,Socket抓包的原理與應用層Http(s)有著顯著的區別。準確的說,Http(s)抓包是真正的“中間人”抓包,而Socket抓包是在接口上進行轉儲;Http(s)抓包是明顯的將一套C/S架構通信分裂成兩套完整的通信過程,而Socket抓包是在接口上將發送與接收的內容存儲下來,并不干擾其原本的通信過程。

    對于安卓應用來說,Socket通信天生又分為兩種Java層Socket通信和Native層Socket通信。

    • Java層:使用的是java.net.InetAddress、java.net.Socket、java.net.ServerSocket等類,與證書綁定的情形類似,也可能存在著自定義框架的Socket通信,這時候就需要具體情況具體分析,比如谷歌的protobuf框架等;
    • Native層:一般使用的是C Socket API,一般hook住send()和recv()函數可以得到其發送和接受的內容

    抓包方法分為三種,接口轉儲、驅動轉儲和路由轉儲:

    • 接口轉儲:比如給outputStream.write下hook,把內容存下來看看,可能是經過壓縮、或加密后的包,畢竟是二進制,一切皆有可能;
    • 驅動轉儲:使用tcpdump將經過網口驅動時的數據包轉儲下來,再使用Wireshark進行分析;
    • 路由轉儲:自己做個路由器,運行jnettop,觀察實時進過的流量和IP,可以使用WireShark實時抓包,也可以使用tcpdump抓包后用WireShark分析。

    總結

    以上是生活随笔為你收集整理的转载:实用 FRIDA 进阶 --- objection :内存漫游、hook anywhere、抓包的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    www.天天操 | 久久人操 | www.国产视频 | 天天射成人| 手机av电影在线观看 | 国产中文字幕网 | 色综合激情久久 | 深夜男人影院 | 五月婷婷狠狠 | av免费在线网站 | av久久在线 | 精品国产乱码久久久久 | 911久久香蕉国产线看观看 | 日韩动漫免费观看高清完整版在线观看 | 人人爽人人做 | 亚洲日本韩国一区二区 | av免费电影在线 | 二区三区精品 | 国产精品一区二区免费视频 | 夜添久久精品亚洲国产精品 | 五月综合网 | 五月激情丁香婷婷 | 亚洲国产片色 | 久久国产亚洲 | 久久免费视频99 | 美女在线免费视频 | 97中文字幕| 日韩成人免费在线 | 国产原创中文在线 | 成人黄色在线 | 97超级碰碰碰碰久久久久 | 亚洲日日射| 久久久久久久久免费 | 免费在线成人av | 久久国产色 | 免费观看性生交 | 国产精品久久片 | 中文字幕一区二区三区四区视频 | 人人精品久久 | 日韩在线影视 | 久久久久久黄 | 深夜免费网站 | 黄色三级免费看 | 丁香婷婷久久 | 日韩精品大片 | 久久免费视频在线观看30 | 久久久久久久久久久久电影 | 天天插天天射 | 亚洲天天草| 婷婷视频导航 | 久久黄色小说视频 | 天海翼一区二区三区免费 | 日本激情中文字幕 | 成人羞羞视频在线观看免费 | 亚洲国产中文字幕在线观看 | 久久午夜影院 | 国产免费作爱视频 | 国产成人精品一区二区三区福利 | 怡红院av久久久久久久 | 国产精品永久免费观看 | 中文在线| 日韩欧美国产免费播放 | 免费网站观看www在线观看 | 在线观看国产区 | 国产成人精品一区在线 | 色久五月| 99视频这里有精品 | 美女黄色网在线播放 | 在线免费黄色av | www.色五月.com | 探花视频免费在线观看 | 99精品视频在线 | 91精选| 1024在线看片| 色综合天天色 | 伊人欧美| www.夜夜操.com| 精品久久久久国产免费第一页 | 玖玖在线精品 | 99在线观看视频网站 | 毛片1000部免费看 | 日韩久久精品一区二区三区下载 | 日韩欧美黄色网址 | 美女露久久 | 国产一级在线 | 手机av资源 | 一级a性色生活片久久毛片波多野 | 九九九视频精品 | 亚洲理论视频 | av片在线观看 | 国产99色 | 日韩二区三区在线 | 麻豆国产精品va在线观看不卡 | 久久草在线精品 | 国产96在线观看 | av电影久久 | 国产高清在线免费视频 | 天天干人人干 | 91精品久久久久久综合五月天 | 久久深夜福利免费观看 | 日韩av成人在线观看 | 久久午夜剧场 | 国产盗摄精品一区二区 | 色综合久久中文综合久久牛 | 国产婷婷vvvv激情久 | 国产爽妇网 | 中文字幕 国产 一区 | 欧美在线视频一区二区三区 | 国产字幕在线播放 | 免费试看一区 | 欧美性成人 | 东方av在| 精品色综合 | 亚洲高清资源 | 成人国产精品久久久 | 中文字幕在线视频一区二区三区 | 九九视频在线 | av女优中文字幕在线观看 | 91精品在线观看视频 | 91av免费看| 开心综合网 | av在线播放网址 | 亚洲欧洲xxxx | 国产高清免费 | 人人超碰免费 | 国产资源在线播放 | 在线视频成人 | 操操操av| 亚洲国产精品免费 | 精选久久 | 久久乐九色婷婷综合色狠狠182 | 狠狠网站 | sm免费xx网站 | 人人澡人人爽 | 欧美日韩国产精品久久 | 97香蕉视频 | 爱av在线网 | 美女视频免费一区二区 | 一区二区三区电影大全 | 在线高清一区 | 国产99久久久国产精品免费二区 | 在线国产精品一区 | 日本护士三级少妇三级999 | 久久久久免费精品国产小说色大师 | 99国产精品视频免费观看一公开 | 香蕉网在线 | 国产亚洲视频在线 | 日韩视频免费在线观看 | 亚洲精品在线二区 | 99精品99 | 性色xxxxhd | 美女久久 | 国产九九精品视频 | 欧美一区二区精品在线 | 91在线视频导航 | 久久精品爱爱视频 | 欧美日韩69 | 日韩黄色免费在线观看 | 99高清视频有精品视频 | 久久伦理网 | 亚洲黄色成人网 | 人人澡人人爽 | 97香蕉久久国产在线观看 | 又色又爽又激情的59视频 | 天天综合网在线 | 91九色视频在线 | 成人精品亚洲 | 国产一区二区精品91 | 日本中文一区二区 | 五月天亚洲综合小说网 | 有码中文字幕在线观看 | 337p日本欧洲亚洲大胆裸体艺术 | 五月天婷婷在线观看视频 | 一级性生活片 | 91视频免费视频 | 97超碰影视| 黄色小说视频网站 | 综合色婷婷 | 久久99精品国产99久久 | 亚洲三级国产 | 99综合电影在线视频 | 天天干天天弄 | 日韩精品一区二区三区外面 | 天堂av色婷婷一区二区三区 | 久久久伦理 | 成人四虎影院 | 不卡的av在线播放 | 天天爽夜夜爽人人爽一区二区 | 亚洲精品视频一二三 | 99视频在线观看免费 | 久久精品一区二区三区四区 | 亚洲国内在线 | 久久人人97超碰精品888 | 亚洲黄色高清 | 在线观看免费91 | 波多野结衣网址 | 国产精品婷婷午夜在线观看 | 九九精品久久久 | 日本久久久久久 | 欧美日韩性视频 | 成人蜜桃网 | 中文字幕成人 | 免费网站观看www在线观看 | 日本电影久久 | 天天综合导航 | 91视频在线观看免费 | 成人九九视频 | 一区二区三区 亚洲 | 欧美激情精品久久久 | 91手机视频在线 | 日本久久久亚洲精品 | 色婷婷导航| 国产糖心vlog在线观看 | 欧美精品一区在线发布 | 色综合久久五月 | 亚洲国产日韩精品 | 欧美天天射 | 久久久久国产精品视频 | 国产成人亚洲在线电影 | 麻豆国产网站入口 | 丁香婷婷综合网 | 国产精品毛片一区视频播不卡 | 亚洲精品一区二区在线观看 | 超碰人人做 | 亚洲最大av在线播放 | 在线观看黄色国产 | 日韩专区在线观看 | 国产明星视频三级a三级点| 在线国产黄色 | 国产喷水在线 | 成人av网址大全 | 中文字幕一区二区三区乱码不卡 | 亚洲成人国产精品 | 日韩r级电影在线观看 | 国产精品18久久久久久久网站 | 色视频在线免费观看 | 91精品久久久久久粉嫩 | 在线观看一级片 | 国产成人三级 | 久久久久女人精品毛片 | 精品国产视频在线观看 | 91精品国产乱码久久桃 | www.久久com | 在线国产小视频 | 波多野结衣电影一区 | 亚洲作爱视频 | 精品伦理一区二区三区 | 日韩美女免费线视频 | 亚洲精品tv | 欧洲亚洲激情 | 国产xxxxx在线观看 | 久久免费高清 | 久久久久久久免费观看 | 一区二区三区四区五区在线视频 | 在线看片日韩 | 深夜福利视频一区二区 | 精品人妖videos欧美人妖 | 国产中的精品av小宝探花 | av黄色影院| 亚洲一区免费在线 | 国产在线看一区 | 婷婷六月天丁香 | 韩国三级一区 | 在线免费视频a | 在线综合色 | a天堂最新版中文在线地址 久久99久久精品国产 | 精品国模一区二区 | 激情久久五月天 | 国产在线高清视频 | 免费精品 | 国产又粗又猛又色 | 2019中文字幕第一页 | 亚洲国产99| 97偷拍在线视频 | 在线免费高清视频 | 狠狠色噜噜狠狠狠狠2022 | 中文字幕av日韩 | 4438全国亚洲精品在线观看视频 | 久久久久黄| 少妇搡bbbb搡bbb搡忠贞 | 最近最新mv字幕免费观看 | 最新精品国产 | 日韩av一区在线观看 | 中文字幕一区二区在线播放 | 久久综合狠狠综合久久综合88 | 精品国产欧美 | 久久久久国产一区二区 | 九九免费观看全部免费视频 | 人人看人人艹 | av大全免费在线观看 | 免费av福利 | 亚洲精品中文字幕在线观看 | 久久福利在线 | 全黄网站| 综合网欧美 | 亚洲综合色网站 | 久久成人国产精品入口 | 成人免费在线观看av | 国产成人一区二区三区 | 成人免费视频视频在线观看 免费 | 精品uu| 国产男女爽爽爽免费视频 | av免费电影网站 | 精品国产美女在线 | 国产视频一区二区三区在线 | 在线观看一区 | 不卡av在线免费观看 | 91喷水| 黄色日本免费 | 亚洲精品视频网 | 91热| 波多野结衣在线观看一区 | 亚洲精品视频免费观看 | av一级在线| 四虎在线观看 | 国产亚洲精品久久久久久电影 | 一区二区三区中文字幕在线 | 91综合在线| 黄色软件网站在线观看 | 国产精品美女久久久久aⅴ 干干夜夜 | 国产精品一区二区在线 | 五月天天色 | 日韩电影久久久 | 在线观看完整版 | 日日爽日日操 | 久久成人精品电影 | 精品久久在线 | 日韩一区二区免费播放 | 狠狠久久 | 91精品国产三级a在线观看 | 中文字幕精品一区二区三区电影 | 久久永久免费 | 涩涩网站在线播放 | 日韩在线免费视频观看 | 精品久久影院 | 国产玖玖精品视频 | 91热爆视频 | 亚洲精品国产片 | 国产精品一区二区在线播放 | 成人h视频在线播放 | 国产剧情一区二区在线观看 | 91亚洲在线观看 | 久草在在线视频 | 91手机在线看片 | 黄色特级片 | 伊人电影天堂 | 日韩精品极品视频 | 在线 影视 一区 | 欧美日韩一区二区三区在线免费观看 | 日韩在线视频播放 | 久草在线在线精品观看 | 成人黄色小说在线观看 | 日韩精品一区二区三区高清免费 | 色在线亚洲 | 激情喷水 | 国产精品女人久久久久久 | 久草com| 色资源网在线观看 | 中文字幕观看在线 | 成人久久久精品国产乱码一区二区 | 超碰在线人人爱 | 国产一级视频在线观看 | 亚洲国产97在线精品一区 | 国产精品久久久久影院日本 | 欧美一级特黄aaaaaa大片在线观看 | 欧美日韩国产一区二区三区在线观看 | 久久免费看片 | 久久成人毛片 | 免费在线激情电影 | 久久99国产一区二区三区 | 国产美女精品 | 免费视频色 | 在线视频你懂得 | 日韩字幕在线观看 | 中国一 片免费观看 | 4438全国亚洲精品在线观看视频 | 久久一级片 | 久久成电影 | 又黄又爽又色无遮挡免费 | 亚洲在线精品视频 | 美女性爽视频国产免费app | 精品女同一区二区三区在线观看 | 在线国产视频一区 | 国产传媒中文字幕 | 欧美成人在线免费 | 玖玖爱国产在线 | 日韩视频精品在线 | 日日操天天操夜夜操 | 国产色久| 精品欧美乱码久久久久久 | 成人一级视频在线观看 | 精品视频久久 | 天天色天天色天天色 | jizz18欧美18 | 久插视频 | 日韩精品五月天 | 亚洲国产理论片 | 麻豆视频在线观看免费 | 97国产精品亚洲精品 | 黄色av免费 | 91av在线电影 | 深爱激情五月网 | 欧美伦理一区二区三区 | 97av超碰| 国产99亚洲 | 国产一区二区视频在线 | 成人四虎影院 | 国产成人免费在线观看 | 福利一区在线 | 水蜜桃亚洲一二三四在线 | 国产精品自产拍在线观看中文 | 久久亚洲精品国产亚洲老地址 | 精品视频999 | 九九视频在线播放 | 中文字幕在线视频国产 | 国产精品自产拍在线观看桃花 | 在线观看免费福利 | 高清av免费看 | 日韩经典一区二区三区 | 成人在线免费看 | 91精品在线免费观看 | 2021国产在线视频 | 婷婷久久网 | 中文免费在线观看 | 国产高清免费观看 | 国产视频 亚洲精品 | 亚洲精品中文在线资源 | 亚洲视频精品在线 | 五月婷婷久久丁香 | 久久久免费观看完整版 | 久久免费视频播放 | 99999精品视频 | 国产一级精品绿帽视频 | 日韩视频在线不卡 | 亚州精品在线视频 | 青青草久草在线 | a在线v| 91在线欧美| 日韩高清www | 99热这里只有精品国产首页 | 99资源网 | www九九热| 在线观看日韩专区 | 亚洲国产精品久久久久婷婷884 | 国产精品久久久久久影院 | 天天色婷婷| 免费在线成人av | 久久久99久久 | 国产在线精品国自产拍影院 | av中文字幕av | 天天干天天操天天 | 国产资源在线视频 | 激情综合啪啪 | 久久久久亚洲天堂 | 美女啪啪图片 | 久久久久久久久久久高潮一区二区 | 日本丶国产丶欧美色综合 | 日本丰满少妇免费一区 | 在线观看免费观看在线91 | 亚洲激情网站免费观看 | 久草爱视频 | 国产精品video爽爽爽爽 | 成年人在线观看网站 | 一级一片免费视频 | 福利电影一区二区 | 91成人网在线观看 | 91视频3p | 精品国产人成亚洲区 | 国产成人精品一区二区三区福利 | 免费又黄又爽视频 | 国产午夜三级一区二区三 | 亚洲成人av影片 | 日韩免费三级 | av在线播放中文字幕 | 中文字幕欧美日韩va免费视频 | 91最新网址 | 久久不卡国产精品一区二区 | 久久久亚洲成人 | 亚洲色图美腿丝袜 | 波多野结衣一区 | 精品a级片| 亚洲精品国产精品乱码不99热 | 日韩精品一区二区三区免费观看 | 国产视频 亚洲精品 | 黄a在线| 日韩欧美高清一区二区 | 成年人免费在线 | 国产热re99久久6国产精品 | 欧美色久 | 久久九九精品久久 | 夜夜躁日日躁狠狠躁 | 国产精品网在线观看 | 亚洲精品麻豆视频 | 日韩欧美xx| 在线视频手机国产 | 久久人人爽人人爽人人片 | 九九热免费观看 | av官网在线 | www.五月天激情 | 欧美作爱视频 | 亚洲精品日韩一区二区电影 | 天天综合在线观看 | 在线精品亚洲一区二区 | 国产日产精品一区二区三区四区的观看方式 | 97色涩| 在线综合色 | 黄色小说免费在线观看 | 日韩成人在线一区二区 | 国产97在线看 | 国产精品一区二区在线播放 | 草久在线 | 久久久久久久久久伊人 | 国产精品久久久久久久久久久久午夜 | 久久99久久99精品免观看粉嫩 | 亚洲欧美日韩国产 | 国产中文字幕亚洲 | 黄色免费视频在线观看 | 亚洲精品国产精品99久久 | 91大神电影 | 久久综合五月婷婷 | 国产精品av在线 | 日本中文字幕一二区观 | 丁香5月婷婷久久 | 天天干天天做天天操 | 高清不卡毛片 | 婷婷丁香视频 | 日韩精品一区二区三区免费视频观看 | 免费看久久 | 香蕉视频4aa| 国产精品视频地址 | www久久国产 | 国产免费中文字幕 | 久久久久久久久久久精 | 免费视频区 | 亚洲有 在线 | 天天爽夜夜爽精品视频婷婷 | 免费麻豆视频 | 久久国产精品色婷婷 | 亚洲高清色综合 | 久久黄色小说视频 | 综合精品久久 | 精品国产自 | 亚洲全部视频 | 国产精品大片免费观看 | 欧美激情精品久久久久久变态 | 99精品国产在热久久下载 | 婷婷资源站| www91在线 | 999久久国产 | 久草在线视频免赞 | 日韩在线不卡视频 | 91大片网站| 欧美另类色图 | www178ccom视频在线 | 久久国产精品久久w女人spa | 久久成人精品 | 免费国产在线精品 | 免费欧美精品 | 偷拍区另类综合在线 | 国产91在线观 | 色www精品视频在线观看 | 色com| 五月天堂网 | 激情五月婷婷综合 | 欧美日韩视频一区二区三区 | 色婷婷视频在线 | 伊人国产在线观看 | 欧美日韩一区二区免费在线观看 | 欧美日韩国产精品久久 | 2023av在线| 在线免费观看欧美日韩 | 国产无遮挡又黄又爽在线观看 | 国产精品一区二区在线播放 | 中文字幕在线观看网站 | av在线h| 欧美二区三区91 | 国产精品毛片一区视频播不卡 | 久久久久久97三级 | 日日干夜夜骑 | 免费在线观看视频一区 | 中文字幕 国产视频 | 国产精品网站一区二区三区 | 国产破处在线视频 | 日韩高清三区 | 色综合天天狠天天透天天伊人 | 国产不卡精品 | 在线观看中文av | 久久久久久黄色 | 狠狠色狠狠色综合日日92 | 日韩欧美一区二区三区在线 | 日日爽天天操 | 亚洲免费观看在线视频 | 天天天操天天天干 | 一区免费在线 | 亚洲欧美色婷婷 | 午夜在线免费观看视频 | 看全黄大色黄大片 | 91秒拍国产福利一区 | 人人玩人人添人人 | 国产精品一区二 | 精品福利视频在线 | 又黄又刺激 | 亚洲婷婷综合色高清在线 | 国产精品伦一区二区三区视频 | av片中文 | 免费中文字幕在线观看 | 国产亚洲精品久久久久久无几年桃 | 国产中文字幕一区二区 | 91手机视频| www.狠狠色.com | av一级网站| 国产日韩精品一区二区三区 | 国产99久 | 中文字幕免费国产精品 | 日韩在线视频免费播放 | 天天干天天怕 | 亚洲综合色网站 | 欧美精品亚洲精品日韩精品 | 日韩一二区在线观看 | 国产高清一 | 国产精品12345 | 欧美精品在线一区 | 在线电影a| 日韩理论电影在线观看 | 久草在线播放视频 | 国产午夜麻豆影院在线观看 | 日韩精品首页 | 久久精品屋 | 成人午夜电影久久影院 | 91完整版在线观看 | 在线播放一区二区三区 | 亚洲成av人片在线观看www | 日韩精品免费专区 | 国精产品永久999 | 国产精品久久久久一区二区三区共 | 成人久久免费 | 成人在线播放免费观看 | 黄色在线网站噜噜噜 | 亚洲精品mv在线观看 | 美女在线免费视频 | 中文字幕高清免费日韩视频在线 | 97人人澡人人添人人爽超碰 | 99热最新精品 | 狠狠色丁香久久婷婷综合五月 | 在线观看av黄色 | 国产91成人 | 色婷婷综合久久久久 | 久久久影片 | 天天色天天草天天射 | 欧美日韩精品免费观看视频 | 欧美十八 | 99riav1国产精品视频 | 黄色成年 | 欧美精品成人在线 | 日韩a级黄色片 | 亚州国产视频 | 国产精品激情 | 中文字幕日本电影 | 99久久国产免费免费 | 国产美女视频免费观看的网站 | 午夜.dj高清免费观看视频 | 国产无区一区二区三麻豆 | 午夜电影 电影 | 免费看黄在线看 | 69欧美视频| 日韩激情一二三区 | 国产在线高清精品 | 国产亚洲精品久久久网站好莱 | 最新免费中文字幕 | 99久久精品无码一区二区毛片 | 麻豆一区在线观看 | 日韩影视在线 | 日韩精品一区在线观看 | 色噜噜色噜噜 | 欧美一级在线 | 夜夜看av| 超碰在线天天 | 色欧美日韩 | 国产中文字幕亚洲 | 密桃av在线 | 337p日本大胆噜噜噜噜 | 麻豆91网站 | www.黄色在线 | 久久影院一区 | 久久精品一区二 | 国产精品女教师 | 97电影网手机版 | 美女视频网站久久 | 久久99热精品 | 蜜臀久久99精品久久久无需会员 | 超碰97人人干 | 国产黄免费看 | 婷婷六月天天 | 国产精品欧美久久 | 一区二区三区国产欧美 | 国产精品美女免费看 | 日韩免费一区二区在线观看 | 国产午夜三级 | 久久理论片| 日韩乱色精品一区二区 | 久久韩国免费视频 | 亚洲欧洲国产精品 | 91成人亚洲 | 久久这里只有精品1 | 91久色蝌蚪| 六月丁香六月婷婷 | 久久久久北条麻妃免费看 | 四虎永久精品在线 | 国产精品二区在线 | 国产高清专区 | 国产中文字幕一区二区三区 | 日韩在线欧美在线 | 最近日本韩国中文字幕 | 色a综合| 免费看污片 | 精品96久久久久久中文字幕无 | av天天澡天天爽天天av | 九九热在线精品 | 国产婷婷久久 | 国产成人精品一区二区三区福利 | 92中文资源在线 | 99热这里只有精品1 av中文字幕日韩 | 日韩免费av网址 | 久久久久免费精品国产小说色大师 | 啪嗒啪嗒免费观看完整版 | 99热官网| 久久精品视频在线看 | 色资源二区在线视频 | 国产成人av电影在线 | 午夜久久福利视频 | 97精品电影院 | 久久综合中文字幕 | 99久久国产免费,99久久国产免费大片 | 久久久久久久久久久久久国产精品 | 一区二区三区www | 日本黄色黄网站 | av电影在线免费观看 | 97在线免费 | 国产午夜视频在线观看 | 久久97精品| 欧美性爽爽 | 香蕉影院在线播放 | 97精品国自产拍在线观看 | 在线国产精品一区 | 欧美综合在线观看 | 日韩欧美精选 | 久久dvd| 99久高清在线观看视频99精品热在线观看视频 | 99精品国产一区二区三区麻豆 | 久久在现 | avav99| 91av中文字幕 | 激情网站网址 | 日韩亚洲欧美中文字幕 | 九九在线免费视频 | 涩涩网站在线看 | 天天插日日操 | 色综合久久88色综合天天 | 日日碰夜夜爽 | 免费的黄色av | 在线观看免费视频你懂的 | 4438全国亚洲精品在线观看视频 | 久久久精品免费看 | 狠狠躁天天躁 | 日韩区视频 | 九九欧美 | 久久99精品国产一区二区三区 | 成年人在线看视频 | 天天曰夜夜爽 | 欧美一区二区三区免费观看 | 伊人色综合久久天天网 | 久操操 | 成人黄色在线视频 | 91久久久国产精品 | 国产成在线观看免费视频 | 成年人免费在线观看 | 国产一区二区手机在线观看 | 免费大片黄在线 | 91视频最新网址 | 日本中文字幕网站 | 久久不卡免费视频 | 国产精品视频观看 | 激情综合网婷婷 | 日本公乱妇视频 | 91精品麻豆 | 久久一区二区三区超碰国产精品 | 久久久精品视频网站 | 天天弄天天操 | 热久久免费国产视频 | 91九色在线播放 | 国产色一区 | 夜添久久精品亚洲国产精品 | 成人午夜电影久久影院 | 国产亚洲久久 | 久久天天综合网 | 国产精品中文字幕av | 午夜精品福利一区二区三区蜜桃 | 日韩区欠美精品av视频 | 亚洲一区天堂 | www狠狠操| 国产一二三区在线观看 | 亚洲国产精品传媒在线观看 | 日韩伦理片hd | 日韩精品免费在线观看视频 | 久久综合九色99 | 香蕉97视频观看在线观看 | 亚洲欧美综合精品久久成人 | 粉嫩aⅴ一区二区三区 | 国产 欧美 日本 | 天天射狠狠干 | 中文字幕a∨在线乱码免费看 | 亚洲欧美国产日韩在线观看 | 在线观看国产成人av片 | 久久久黄视频 | 一区在线电影 | 精品美女久久久久 | 黄色资源网站 | 黄视频色网站 | 久久成人黄色 | 一区二区三区精品在线 | 在线视频欧美亚洲 | 蜜桃传媒一区二区 | 69绿帽绿奴3pvideos | 国产精品久久一 | 人人干人人搞 | 免费看国产曰批40分钟 | 久久精品99国产精品日本 | 久久99精品久久久久久 | 97av在线 | 欧美精品一区二区三区四区在线 | 日韩久久精品一区二区三区 | 婷婷五月在线视频 | 免费av大片 | 国产又黄又爽又猛视频日本 | 九九久久成人 | 国产一区二区三区四区在线 | 中文字幕在线观看第二页 | 99精品视频在线看 | 美女视频黄频大全免费 | 99视频导航 | 久久99久久99精品免观看软件 | 91欧美日韩国产 | 玖玖在线资源 | 国产免费精彩视频 | 免费手机黄色网址 | 一区二区毛片 | 国产999精品久久久影片官网 | 96视频免费在线观看 | 91精品办公室少妇高潮对白 | 在线视频欧美精品 | 天天综合网 天天综合色 | 精品在线观看一区二区三区 | 视频在线观看亚洲 | 亚洲免费在线观看视频 | av五月婷婷 | 免费在线播放 | 最近中文字幕高清字幕在线视频 | 91麻豆精品国产91久久久更新时间 | 亚洲婷婷丁香 | 久久五月激情 | 中文网丁香综合网 | 国产精品资源在线 | 国产成人免费av电影 | 亚洲 欧美 国产 va在线影院 | 狠狠的操你 | 天天射狠狠干 | 欧美夫妻性生活电影 | 99久久婷婷 | 国产在线观看99 | 一区二区三区www | 天天久久夜夜 | 亚洲午夜不卡 | av天天在线观看 | 欧美孕交vivoestv另类 | 成人小视频在线免费观看 | 久久亚洲精品国产亚洲老地址 | 亚洲电影在线看 | 免费高清在线视频一区· | 久久久久久久久久久成人 | www.久艹 | 日韩精品一区二区久久 | 久久激情视频网 | 日韩在线一级 | 99情趣网视频 | 在线va视频| 日韩av福利在线 | 久久久精品 | 日本资源中文字幕在线 | 国产成人性色生活片 | 欧美精品一区二区在线观看 | 亚洲综合小说 | 午夜成人影视 | 久久视频免费在线观看 | 99综合电影在线视频 | 麻豆播放 | 视频福利在线 | 中文字幕在线视频国产 | 亚洲综合在线观看视频 | 最近最新中文字幕 | 不卡电影免费在线播放一区 | 精品日韩中文字幕 | 免费日韩 精品中文字幕视频在线 | 精品免费99久久 | 久久艹艹| 精品一区二区免费在线观看 | 日韩色综合 | 国产成人黄色片 | 午夜视频在线观看一区二区三区 | 国产黄色视| 热re99久久精品国产66热 | 久久久久久久久久久免费 | 天天色婷婷 | 日韩剧情 | 日韩高清久久 | 亚洲电影第一页av | 一区在线免费观看 | 久久视频一区 | 一区二区三区视频 | 久久久久久久久久久久久久av | 人交video另类hd | 国产裸体无遮挡 | 成年人电影毛片 | 婷婷中文在线 | 婷婷开心久久网 | 久久成人国产精品一区二区 | 久久玖 | 亚洲乱码国产乱码精品天美传媒 | 精品久久国产 | 中文字幕在线字幕中文 | 中文字幕在线观 | 精品亚洲免费视频 | 亚洲三级网 | 免费看片网站91 | 香蕉影视在线观看 | 国产91欧美 | 精品一区二区免费在线观看 | 成 人 黄 色 片 在线播放 | 亚洲久在线 | 99在线视频播放 | 99精品国产成人一区二区 | 91九色视频在线播放 | 久久久激情视频 | 国产精品永久久久久久久www | 欧美一区二区三区在线视频观看 | 成人av电影在线播放 | 国内精品久久久久久久久久久 | 亚洲综合在线五月天 | 91av原创| a视频免费在线观看 | 日日躁夜夜躁aaaaxxxx | 美女视频网站久久 | 91夫妻视频 | 娇妻呻吟一区二区三区 | 在线三级av | 久久久久久久久久久高潮一区二区 | 成人精品在线 | 日韩精品视频在线观看免费 | 国产精品原创在线 | 日韩中文字幕免费电影 | 精品主播网红福利资源观看 | 精品国产三级 | 一区二区男女 | 黄色三级免费网址 | 人人干狠狠操 | 最近高清中文字幕 | 久久www免费人成看片高清 | 91麻豆精品国产91久久久无需广告 | av不卡网站 | 欧美日韩亚洲在线 | 香蕉在线观看 | 国产精品久久久久久久久岛 | 日韩女同一区二区三区在线观看 | 免费高清在线观看电视网站 | 午夜久久久久 | a天堂最新版中文在线地址 久久99久久精品国产 | 久久黄色小说 | 成人毛片在线观看 | 99久久精品免费看国产麻豆 | 色综合五月 | 久久免费视频观看 | 韩日电影在线观看 | 亚洲成aⅴ人在线观看 | 在线观看中文字幕2021 | 在线观看爱爱视频 | 成人午夜影院在线观看 | 欧美日韩一区二区三区视频 | 涩涩网站在线观看 | 99久久99久久免费精品蜜臀 | 欧美99久久| 丁香婷婷激情国产高清秒播 | 国产日韩欧美在线影视 | 狠狠干 狠狠操 | 天天爽夜夜爽精品视频婷婷 | 国产高清视频色在线www | 免费观看91 | 天天干天天操天天爱 | 亚洲在线观看av | www.天天干.com |