逆向分析Spotify.app并hook其功能獲取數據

2019-06-23 114782人圍觀 數據安全

在開始本文的正式內容之前我想先來吐槽下。大多數的軟件開發人員可能都有著這樣一個煩惱,就是由于工作和其他責任,不得不擱置自己的一些個人項目甚至是最終完全的遺忘和埋沒。而本文的所述的就是一個被我遺忘已久的項目,而我寫這篇文章的目的就是希望能迫使我自己最終完成這個項目。好了,介紹就到這了讓我們開始吧。

項目

該項目的目標是構建一個Spotify客戶端,讓它能夠學習我的聽曲習慣并跳過一些我通常會跳過的歌曲。不得不承認,這種需求來自于我的懶惰。我不想在當我有心情想要聽某些音樂時,創建或查找播放列表。我希望的是在我的庫中選擇一首歌,然后可以隨機播放其他歌曲,并從隊列中刪除不“flow(節奏與旋律的流暢)”的歌曲。

為了實現這一點,我需要學習某種能夠執行此任務的模型(在未來的帖子中可能更多)。但是為了能夠訓練一個模型,我首先需要數據來訓練它。

數據

我需要完整的聽歌歷史記錄,包括我跳過的那些歌曲。獲取歷史記錄很簡單。雖然Spotify API僅允許獲取最近50首播放的歌曲,但我們可以設置一個cron job來重復輪詢該端點。完整代碼已經發布在此處:https://gist.github.com/SamL98/c1200a30cdb19103138308f72de8d198

最困難的部分是跟蹤跳過。Spotify Web API并沒有為此提供任何的端點。之前我使用Spotify AppleScript API創建了一些控制播放的服務(本文的其余部分將涉及到MacOS Spotify客戶端)。我可以使用這些服務來跟蹤跳過的內容,但這感覺像是在回避挑戰。我怎么能完成它呢?

Hooking

我最近學習了解了有關hooking的技術,你可以在其中“攔截”從目標二進制文件生成的函數調用。我認為這將是跟蹤跳過的最佳方法。

最常見的鉤子類型是interpose hook。這種類型的鉤子會覆蓋PLT中的重定位,但這究竟意味著什么呢?

PLT或過程鏈接表允許你的代碼引用外部函數(想想libc)而不知道該函數在內存中的位置,你只需引用PLT中的一個條目。鏈接器在運行時為PLT中的每個函數或符號執行“重定位”。這種方法的一個好處是,如果外部函數在不同的地址加載,則只需要更改PLT中的重定位,而不是每次對代碼中該函數的引用。

因此,當我們為printf創建一個interpose hook時,每當我們hooking的進程調用printf時,我們將調用printf的實現而不是libc(我們的自定義庫通常也會調用標準實現)。

在對鉤子有了一些基本的知識背景后,下面我們準備嘗試在Spotify中插入一個鉤子。但首先我們需要弄清楚我們想要hook的是什么。

尋找 hook 的位置

如前所述,只能為外部函數創建一個interpose hook,因此我們將在libc或Objective-C runtime中查找函數。

在研究在哪hook時,我認為一個開始hooking的好地方是Spotify處理“media control keys”或我MacBook上的F7-F9。假設這些鍵的處理程序在spotify應用程序中單擊Next按鈕被調用時會調用函數。我最終在:https://github.com/nevyn/spmediakeytap上找到了SPMediaKeyTap庫。我想我可以試一試,看看Spotify是否復制并粘貼了這個庫中的代碼。在SPMediaKeyTap庫中,有一個方法startWatchingMediaKeys。我在Spotify二進制文件上運行了strings命令,看看他們是否有這個方法,果然:

1.png

Bingo!!如果我們將Spotify二進制文件加載到IDA(當然是免費版本)并搜索此字符串,我們就會找到相應的方法:

2.png

如果我們查看這個函數對應的源碼,我們會發現CGEventTapCreate函數的有趣參數tapEventCallback:

3.png

如果我們回顧一下反匯編,我們可以看到sub_10010C230子例程作為tapEventCallback參數傳遞。如果我們查看這個函數的源碼或反匯編,我們看到只調用了一個庫函數CGEventTapEnable:

4.png

讓我們嘗試hook這個函數。

我們需要做的第一件事是創建一個庫來定義我們的自定義CGEventTapEnable。代碼如下:

#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
void CGEventTapEnable(CFMachPortRef tap, bool enable) 
{
  typeof(CGEventTapEnable) *old_tap_enable;
  printf(“I'm hooked!\n”);
  old_tap_enable = dlsym(RTLD_NEXT, “CGEventTapEnable”);
  (*old_tap_enable)(tap, enable);
}

dlsym函數調用獲取實際庫CGEventTapEnable函數的地址。然后我們調用舊的實現,這樣我們就不會意外地破壞任何東西。讓我們像這樣編譯我們的庫(https://ntvalk.blogspot.com/2013/11/hooking-explained-detouring-library.html):

gcc -fno-common -c <filename>.c 
gcc -dynamiclib -o <library name> <filename>.o

現在,讓我們嘗試在插入鉤子時運行Spotify:DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=<library name> /Applications/Spotify.app/Contents/MacOS/Spotify。點擊進入:

5.png

Spotify打開正常,但Apple的系統完整性保護(SIP)沒有讓我們加載未簽名庫:(。

幸運的是,我是Apple的reasonably priced developer項目的成員,所以我可以對庫進行代碼簽名。這個問題算是得到了解決。讓我們用100美元證書簽名我們的庫,運行上一個命令,然后……

6.png

失敗。這一點不奇怪,Apple不允許你插入使用任何舊標識簽名的庫,只允許使用簽名原始二進制文件時使用的庫。看起來我們必須要找到另一種方法來hook Spotify了。

作為補充說明,細心的讀者可能會注意到我們hook的函數CGEventTapEnable,只有在media key event超時時才會被調用。因此,即使我們可以插入鉤子,我們也可能不會看到任何的輸出。本節的主要目的是詳細說明我最初的失敗(和疏忽),并作為一個學習經驗。

HookCase

經過一番挖掘,我發現了一個非常棒的庫HookCase:https://github.com/steven-michaud/HookCase。HookCase讓我們實現一種比插入鉤子( patch hook)更為強大的鉤子類型。

通過修改你希望hook的函數觸發中斷插入Patch hooks。然后,內核可以處理此中斷,然后將執行轉移到我們的個人代碼中。對于那些感興趣的人,我強烈建議你閱讀HookCase文檔,因為它更為詳細。

Patch hooks不僅允許我們對外部函數的hook調用,而且允許我們hook目標二進制文件內的任何函數(因為它不依賴于PLT)。HookCase為我們提供了一個框架來插入patch和/或interpose hooks,以及內核擴展來處理patch hooks生成的中斷,并運行我們的自定義代碼。

尋找 sub_100CC2E20

既然我們已經有辦法hook Spotify二進制文件中的任何函數了,那么只剩下最后一個問題……就是位置在哪?

讓我們重新訪問SPMediaKeyTap源碼,看看如何處理媒體控制鍵。在回調函數中,我們可以看到如果按下F7,F8或F9(NX_KEYTYPE_PREVIOUS,NX_KEYTYPE_PLAY等),我們將執行handleAndReleaseMediaKeyEvent選擇器:

7.png

然后在所述選擇器中通知delegate:

8.png

讓我們看看repo中的這個delegate方法:

9.png

事實證明它只是為處理keys設置了一個模板。讓我們在IDA中搜索receiveMediaKeyEvent函數,并查看相應函數的圖形視圖:

10.png

看起來非常相似,不是嗎?我們可以看到,對每種類型的鍵都調用了一個公共函數sub_10006FE10,只設置了一個整數參數來區分它們。讓我們hook它,看看我們是否可以記錄按下的鍵。

我們可以從反匯編中看到,sub_10006FE10獲得了兩個參數:1)指向SPTClientAppDelegate單例的playerDelegate屬性的指針,以及2)指定發生了什么類型事件的整數(0表示暫停/播放,3表示下一個,4表示上一個)。

看看sub_10006FE10(我不會在這里包含它,但我強烈建議你自己檢查一下),我們可以看到它實際上是sub_10006DE40的包裝器,其中包含了大部分內容:

11.png

哇!這看起來很復雜。讓我們試著把它分解一下。

從這個圖的結構來看,有一個指向頂部的節點有許多outgoing edges:

12.png

正如IDA所建議的那樣,這是esi(前面描述的第二個整數參數)上的switch語句。看起來Spotify的處理的不僅僅是Previous,Pause/Play和Next。讓我們把關注點集中到處理Next或3 block:

13.png

不可否認,為此我花了一些時間,但我想請你注意底部第四行的call r12。如果你查看其他的一些情況,你會發現一個非常相似的調用寄存器的模式。這似乎是一個很好的函數,但我們如何知道它在哪呢?

讓我們打開一個新工具:debugger(調試器)。我最初嘗試調試Spotify時遇到了很多麻煩。現在可能是因為我對調試器不太熟悉的原因,但我認為我想出了一個相當聰明的解決方案。

我們首先在sub_10006DE40上設置一個hook,然后我們在代碼中觸發一個斷點。我們可以通過執行匯編指令int 3來做到這一點(例如像GDB和LLDB之類的調試)。

以下是在HookCase框架中hook的樣子:

14.png

將此添加到HookCase模板庫后,你還必須將其添加到user_hooks數組:

15.png

然后我們可以使用Makefile HookCase提供的模板來編譯它。然后可以使用以下命令將庫插入Spotify:HC_INSERT_LIBRARY=<full path to hook dylib> /Applications/Spotify.app/Contents/MacOS/Spotify。

然后我們可以運行LLDB并將其attach到正在運行的Spotify進程,如下所示:

16.png

嘗試按F9(如果Spotify不是活動窗口,它可能會打開iTunes)。鉤子中的int $3行應該觸發了調試器。

現在我們可以進入到sub_10006DE40入口點這步。請注意,PC將位于與IDA中顯示的地址相對應的位置(我認為這是由于進程加載到內存的位置所導致的)。在我當前的進程中,push r15指令位于0x10718ee44:

17.png

在IDA中,該指令的地址為0x10006DE44,它給了我們一個偏移量0×7121000。在IDA中,調用r12指令的地址為0x10006E234。然后我們可以將偏移量添加到該地址,并相應地設置一個斷點,b -a 0x10718f234,然后繼續。

當我們點擊目標指令時,我們可以打印出寄存器r12的內容:

18.png

我們要做的就是從這個地址減去偏移量,看,我們獲取到了我們名義上的地址:0x100CC2E20。

Hooking sub_100CC2E20

現在,讓我們來hook這個函數:

19.png

將其添加到user_hooks數組,編譯,運行,并觀察:每次按F9或單擊Spotify應用程序中的next按鈕,都會記錄我們的消息。

現在我們已經hook了skip功能,

20.JPG

我將發布剩余的代碼,但我不會完成其余部分的逆向工作,因為這篇文章已經夠長的了。

簡而言之,我也hook了previous功能(如果你照著做的話,這會是一個很好的練習)。然后,在這兩個鉤子中,我首先檢查當前的歌曲是否已經過了一半。如果是的話,我什么都不做,假設我只是對這首歌感到厭倦,而不是覺得它不合適。然后在backs (F7),我彈出last skip。

針對如何檢查當前歌曲是否已經過了一半的方法我想說幾句。我最初的方法是實際調用popen,然后運行相應的AppleScript命令,但感覺這不太對。

我在Spotify二進制文件上運行了class-dump,發現了兩個類:SPAppleScriptObjectModel和SPAppleScriptTrack。這些方法公開了播放位置,持續時間和曲目ID所需的必要屬性。然后,我為這些屬性hook了getter,并使用next和back hooks調用它們(我認為Swizzle更合理,但我無法讓它正常工作)。

我使用一個文件來跟蹤skips,其中第一行包含跳過次數,在跳過時我們增加這個計數器,并將跟蹤ID和時間戳寫入計數器指定行上的文件。在back按鈕,我們只是減少這個計數器。這樣,當我們按下back按鈕時,我們只是將文件設置為對已回溯文件寫入new skips。無論如何,這里的代碼是:https://gist.github.com/SamL98/0cd20b00951b9a5cca6b5c9380ec5642

總結

希望通過本文你可以學習到一些新的知識,至少在這個過程中我已學到了很多東西。另外,如果你有任何更好的想法或建議請將它告訴我!謝謝!

*參考來源:medium,FB小編secist編譯,轉載請注明來自FreeBuf.COM

相關推薦
取消
Loading...
secist

每個人的心中都有一個夢。。

467 文章數 64 評論數 67 關注者

特別推薦

填寫個人信息

姓名
電話
郵箱
公司
行業
職位
css.php 重庆百变王牌走势图 皇家棋牌直营 开户买股票哪个平台 看贵州快三开奖结果 福建22选5开奖走势图大星 一起玩温州麻将app 大发快三一分钟全天计划 山西今天十一选五 深圳彩票官网 天天北京pk拾计划软件 内蒙古快3开奖跨度走势图 大快乐透开奖 上证指数每日行情300727 世界足球明星 麻将单机版闯关 幸运十一选五开奖结果 抢银行扑克牌玩法