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

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

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

項目

該項目的目標是構建一個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...

特別推薦

推薦關注

填寫個人信息

姓名
電話
郵箱
公司
行業
職位
css.php 重庆百变王牌走势图