Das Ziel dieses Projekts ist es, einen Spotify-Client zu erstellen, der meine Hörgewohnheiten lernen und einige Songs überspringen kann, die ich normalerweise überspringen würde. Ich muss zugeben, dieses Bedürfnis kommt von meiner Faulheit. Ich möchte keine Playlists erstellen oder suchen müssen, wenn ich Lust auf etwas habe. Ich möchte einen Song in meiner Bibliothek auswählen und in der Lage sein, andere Songs zu mischen und Songs, die nicht „fließen“, aus der Warteschlange zu entfernen.
Um dies zu erreichen, muss ich eine Art Modell lernen, das diese Aufgabe ausführen kann (vielleicht mehr dazu in einem zukünftigen Beitrag). Aber um ein Modell trainieren zu können, benötige ich zunächst Daten, um es zu trainieren.
Ich benötige den vollständigen Hörverlauf, einschließlich der Songs, die ich übersprungen habe. Die Historie zu bekommen ist einfach. Während die Spotify-API nur das Abrufen der letzten 50 gespielten Songs ermöglicht, können wir einen Cron-Job einrichten, um den Endpunkt wiederholt abzufragen. Der vollständige Code wurde hier veröffentlicht: https://gist.github.com/SamL98/c1200a30cdb19103138308f72de8d198
Der schwierigste Teil besteht darin, die Sprünge zu verfolgen. Die Spotify Web API stellt hierfür keine Endpunkte bereit. Zuvor habe ich einige Dienste erstellt, um die Wiedergabe mithilfe der Spotify-AppleScript-API zu steuern (der Rest dieses Artikels befasst sich mit dem MacOS-Spotify-Client). Ich könnte diese Dienste nutzen, um den Überblick über übersprungene Inhalte zu behalten, aber das fühlt sich an, als würde ich der Herausforderung ausweichen. Wie kann ich es abschließen?
Ich habe kürzlich etwas über die Hooking-Technik gelernt, mit der Sie aus der Zielbinärdatei generierte Funktionsaufrufe „abfangen“ können. Ich denke, das wäre der beste Weg, um Sprünge zu verfolgen.
Der häufigste Hakentyp ist der Zwischenhaken. Diese Art von Hook überschreibt Verschiebungen im PLT, aber was bedeutet das eigentlich?
Eine PLT- oder Prozedurverknüpfungstabelle ermöglicht es Ihrem Code, auf eine externe Funktion (z. B. libc) zu verweisen, ohne zu wissen, wo sich diese Funktion im Speicher befindet. Sie verweisen lediglich auf einen Eintrag im PLT. Der Linker führt zur Laufzeit eine „Verschiebung“ für jede Funktion oder jedes Symbol im PLT durch. Ein Vorteil dieses Ansatzes besteht darin, dass beim Laden der externen Funktion an einer anderen Adresse nur die Verschiebung im PLT geändert werden muss und nicht jeder Verweis auf die Funktion im Code.
Wenn wir also einen Interpose-Hook für printf erstellen, rufen wir immer dann, wenn der Prozess, den wir einbinden, printf aufruft, die Implementierung von printf anstelle von libc auf (unsere benutzerdefinierte Bibliothek ruft normalerweise auch den Standard-Affect auf).
Nachdem wir einige grundlegende Hintergrundkenntnisse über Hooks haben, sind wir bereit zu versuchen, einen Hook in Spotify einzufügen. Aber zuerst müssen wir herausfinden, was wir anhaken wollen.
Wie bereits erwähnt, können Sie einen Interpose-Hook nur für eine externe Funktion erstellen, daher suchen wir nach der Funktion in libc oder Objective-C Laufzeit.
Als ich recherchierte, wo man einbinden kann, dachte ich, dass ein guter Ort, um mit dem Einhängen zu beginnen, das Spotify-Handle „Mediensteuerungstasten“ oder F7-F9 auf meinem MacBook wäre. Gehen Sie davon aus, dass die Handler für diese Tasten Funktionen aufrufen, wenn die Schaltfläche „Weiter“ in der Spotify-App aufgerufen wird. Endlich habe ich die SPMediaKeyTap-Bibliothek gefunden unter: https://github.com/nevyn/spmediakeytap. Ich dachte, ich probiere es mal aus und schaue, ob Spotify den Code aus dieser Bibliothek kopiert und eingefügt hat. In der SPMediaKeyTap-Bibliothek gibt es eine Methode startWatchingMediaKeys. Ich habe den Strings-Befehl auf den Spotify-Binärdateien ausgeführt, um zu sehen, ob sie diese Methode haben, und tatsächlich:
Bingo!! Wenn wir die Spotify-Binärdatei in IDA laden (natürlich die kostenlose Version) und nach dieser Zeichenfolge suchen, finden wir die entsprechende Methode:
if When we Schauen Sie sich den Quellcode an, der dieser Funktion entspricht. Wir finden den interessanten Parameter tapEventCallback der CGEventTapCreate-Funktion: siehe sub_10010C230 Die Subroutine wird als tapEventCallback-Parameter übergeben. Wenn wir uns den Quellcode oder die Zerlegung dieser Funktion ansehen, sehen wir, dass nur eine Bibliotheksfunktion CGEventTapEnable aufgerufen wird:
Versuchen wir, diese Funktion einzubinden.
Als Erstes müssen wir eine Bibliothek erstellen, um unser benutzerdefiniertes CGEventTapEnable zu definieren. Der Code lautet wie folgt:
#include <corefoundation> #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); }</stdio.h></stdlib.h></dlfcn.h></corefoundation>
gcc -fno-common -c <filename>.c gcc -dynamiclib -o <library> <filename>.o</filename></library></filename>
现在,让我们尝试在插入钩子时运行Spotify:DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=
Spotify打开正常,但Apple的系统完整性保护(SIP)没有让我们加载未签名库:(。
幸运的是,我是Apple的reasonably priced developer项目的成员,所以我可以对库进行代码签名。这个问题算是得到了解决。让我们用100美元证书签名我们的库,运行上一个命令,然后......
失败。这一点不奇怪,Apple不允许你插入使用任何旧标识签名的库,只允许使用签名原始二进制文件时使用的库。看起来我们必须要找到另一种方法来hook Spotify了。
作为补充说明,细心的读者可能会注意到我们hook的函数CGEventTapEnable,只有在media key event超时时才会被调用。因此,即使我们可以插入钩子,我们也可能不会看到任何的输出。本节的主要目的是详细说明我最初的失败(和疏忽),并作为一个学习经验。
经过一番挖掘,我发现了一个非常棒的库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生成的中断,并运行我们的自定义代码。
既然我们已经有办法hook Spotify二进制文件中的任何函数了,那么只剩下最后一个问题......就是位置在哪?
让我们重新访问SPMediaKeyTap源码,看看如何处理媒体控制键。在回调函数中,我们可以看到如果按下F7,F8或F9(NX_KEYTYPE_PREVIOUS,NX_KEYTYPE_PLAY等),我们将执行handleAndReleaseMediaKeyEvent选择器:
然后在所述选择器中通知delegate:
让我们看看repo中的这个delegate方法:
事实证明它只是为处理keys设置了一个模板。让我们在IDA中搜索receiveMediaKeyEvent函数,并查看相应函数的图形视图:
看起来非常相似,不是吗?我们可以看到,对每种类型的键都调用了一个公共函数sub_10006FE10,只设置了一个整数参数来区分它们。让我们hook它,看看我们是否可以记录按下的键。
我们可以从反汇编中看到,sub_10006FE10获得了两个参数:1)指向SPTClientAppDelegate单例的playerDelegate属性的指针,以及2)指定发生了什么类型事件的整数(0表示暂停/播放,3表示下一个,4表示上一个)。
看看sub_10006FE10(我不会在这里包含它,但我强烈建议你自己检查一下),我们可以看到它实际上是sub_10006DE40的包装器,其中包含了大部分内容:
哇!这看起来很复杂。让我们试着把它分解一下。
从这个图的结构来看,有一个指向顶部的节点有许多outgoing edges:
正如IDA所建议的那样,这是esi(前面描述的第二个整数参数)上的switch语句。看起来Spotify的处理的不仅仅是Previous,Pause/Play和Next。让我们把关注点集中到处理Next或3 block:
Zugegebenermaßen hat es bei mir einige Zeit gedauert, aber ich möchte Sie darauf aufmerksam machen, dass Sie in der vierten Zeile unten r12 aufrufen. Wenn Sie sich einige andere Fälle ansehen, werden Sie ein sehr ähnliches Muster bei den Aufrufregistern finden. Das scheint eine nette Funktion zu sein, aber woher wissen wir, wo sie ist?
Öffnen wir ein neues Tool: Debugger. Ich hatte anfangs große Probleme beim Versuch, Spotify zu debuggen. Vielleicht liegt es daran, dass ich mit Debuggern nicht sehr vertraut bin, aber ich glaube, ich habe eine ziemlich clevere Lösung gefunden.
Wir setzen zuerst einen Haken auf sub_10006DE40 und lösen dann einen Haltepunkt im Code aus. Wir können dies tun, indem wir die Assembler-Anweisung int 3 ausführen (z. B. Debuggen wie GDB und LLDB).
So sieht ein Hook im HookCase-Framework aus:
Nachdem Sie dies zur HookCase-Vorlagenbibliothek hinzugefügt haben, müssen Sie es auch zum user_hooks-Array hinzufügen:
Dann können wir die bereitgestellten Vorlagen verwenden von Makefile HookCase, um es zu kompilieren. Die Bibliothek kann dann mit dem folgenden Befehl in Spotify eingefügt werden: HC_INSERT_LIBRARY=
Dann können wir LLDB ausführen und es wie folgt an den laufenden Spotify-Prozess anhängen:
Versuchen Sie, F9 zu drücken (wenn Spotify nicht das aktive Fenster ist, wird möglicherweise iTunes geöffnet). Die Zeile int $3 im Hook sollte den Debugger auslösen.
Jetzt können wir den Einstiegspunkt sub_10006DE40 eingeben. Beachten Sie, dass sich der PC an der Stelle befindet, die der in IDA angezeigten Adresse entspricht (ich denke, das liegt daran, wo der Prozess in den Speicher geladen wird). In meinem aktuellen Prozess befindet sich die Push-R15-Anweisung bei 0x10718ee44:
In IDA lautet die Adresse dieser Anweisung 0x10006DE44, was uns einen Offset von 0x7121000 ergibt. In IDA lautet die Adresse, an der der R12-Befehl aufgerufen wird, 0x10006E234. Wir können dann den Offset zu dieser Adresse hinzufügen und entsprechend einen Haltepunkt setzen, b -a 0x10718f234, und fortfahren.
Wenn wir auf die Zielanweisung klicken, können wir den Inhalt des Registers r12 ausdrucken:
Alles, was wir tun müssen, ist den Offset von dieser Adresse zu subtrahieren, und siehe da, wir erhalten unsere Nominaladresse: 0x100CC2E20 .
Nun binden wir diese Funktion ein:
Fügen Sie sie zum user_hooks-Array hinzu, kompilieren, führen Sie sie aus und beobachten Sie: Jedes Mal, wenn Sie F9 drücken oder auf die Schaltfläche „Weiter“ in der Spotify-App klicken, zeichnen Sie unsere Nachricht auf .
Da wir nun die Sprungfunktion aktiviert haben,
werde ich den Rest des Codes posten, aber das Reverse Engineering des Rests nicht abschließen, da dieser Beitrag bereits lang genug ist.
Kurz gesagt, ich habe auch die vorherige Funktion eingebunden (dies wird eine gute Übung sein, wenn Sie dieser folgen). Dann überprüfe ich in beiden Hooks zunächst, ob der aktuelle Song bereits zur Hälfte durch ist. Wenn ja, tue ich nichts und gehe davon aus, dass mir das Lied nur langweilig ist, anstatt es unangemessen zu finden. Dann schalte ich auf der Rückseite (F7) den letzten Sprung ein.
Ich möchte ein paar Worte dazu sagen, wie man überprüft, ob das aktuelle Lied zur Hälfte fertig ist. Mein ursprünglicher Ansatz bestand darin, tatsächlich „popen“ aufzurufen und dann den entsprechenden AppleScript-Befehl auszuführen, aber das fühlt sich nicht richtig an.
Ich habe Class-Dump für die Spotify-Binärdatei ausgeführt und zwei Klassen gefunden: SPAppleScriptObjectModel und SPAppleScriptTrack. Diese Methoden stellen die erforderlichen Eigenschaften für Wiedergabeposition, Dauer und Titel-ID bereit. Ich habe dann Getter für diese Eigenschaften eingehakt und sie mit Next- und Back-Hooks aufgerufen (ich denke, Swizzle macht mehr Sinn, aber ich bekomme es nicht zum Laufen).
Ich verwende eine Datei zum Verfolgen von Sprüngen, wobei die erste Zeile die Anzahl der Sprünge enthält. Beim Überspringen erhöhen wir diesen Zähler und schreiben die Tracking-ID und den Zeitstempel in die Datei in der durch den Zähler angegebenen Zeile. Mit der Schaltfläche „Zurück“ dekrementieren wir einfach diesen Zähler. Auf diese Weise stellen wir beim Drücken der Zurück-Taste einfach die Datei so ein, dass neue Sprünge in die zurückverfolgte Datei geschrieben werden.
Das obige ist der detaillierte Inhalt vonWie man Spotify.app zurückentwickelt und seine Funktionen einbindet, um Daten zu erhalten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!