Rumah > Artikel > Operasi dan penyelenggaraan > Cara membuat kejuruteraan songsang Spotify.app dan menghubungkan fungsinya untuk mendapatkan data
Matlamat projek ini adalah untuk membina pelanggan Spotify yang boleh mempelajari tabiat mendengar saya dan melangkau beberapa lagu yang biasanya saya langkau. Saya harus mengakui, keperluan ini datang dari kemalasan saya. Saya tidak mahu perlu mencipta atau mencari senarai main apabila saya berada dalam mood untuk sesuatu. Apa yang saya mahu ialah memilih lagu dalam pustaka saya dan dapat mengocok lagu lain dan mengalih keluar lagu yang tidak "mengalir" daripada baris gilir.
Untuk mencapai matlamat ini, saya perlu mempelajari beberapa jenis model yang boleh melaksanakan tugasan ini (mungkin lebih lanjut mengenainya dalam siaran akan datang). Tetapi untuk dapat melatih model, saya terlebih dahulu memerlukan data untuk melatihnya.
Saya memerlukan sejarah pendengaran yang lengkap, termasuk lagu yang saya langkau. Mendapatkan sejarah adalah mudah. Walaupun API Spotify hanya membenarkan mendapatkan 50 lagu terakhir yang dimainkan, kami boleh menyediakan tugas cron untuk meninjau titik akhir berulang kali. Kod penuh telah disiarkan di sini: https://gist.github.com/SamL98/c1200a30cdb19103138308f72de8d198
Bahagian paling sukar ialah menjejaki langkauan. API Web Spotify tidak menyediakan sebarang titik akhir untuk ini. Sebelum ini saya mencipta beberapa perkhidmatan untuk mengawal main balik menggunakan Spotify AppleScript API (selebihnya artikel ini akan merangkumi klien MacOS Spotify). Saya boleh menggunakan perkhidmatan ini untuk menjejaki kandungan yang dilangkau, tetapi rasanya seperti mengetepikan cabaran. Bagaimana saya boleh melengkapkannya?
Baru-baru ini saya belajar tentang pengait, teknik di mana anda boleh "memintas" panggilan fungsi yang dijana daripada perduaan sasaran. Saya fikir ini akan menjadi cara terbaik untuk menjejaki langkauan.
Jenis cangkuk yang paling biasa ialah cangkuk interpose. Jenis cangkuk ini mengatasi penempatan semula dalam PLT, tetapi apakah maksudnya sebenarnya?
Jadual pautan PLT atau prosedur membenarkan kod anda merujuk fungsi luaran (fikir libc) tanpa mengetahui di mana fungsi itu berada dalam ingatan, anda hanya merujuk masukan dalam PLT. Penyambung melakukan "penempatan semula" pada masa jalan untuk setiap fungsi atau simbol dalam PLT. Satu faedah daripada pendekatan ini ialah jika fungsi luaran dimuatkan pada alamat yang berbeza, hanya penempatan semula dalam PLT perlu diubah, bukannya setiap rujukan kepada fungsi dalam kod.
Jadi apabila kami mencipta cangkuk interpose untuk printf, setiap kali proses kami mengaitkan memanggil printf, kami akan memanggil pelaksanaan printf dan bukannya libc (pustaka tersuai kami biasanya akan memanggil pelaksanaan standard juga) .
Selepas mempunyai pengetahuan latar belakang asas tentang cangkuk, kami bersedia untuk cuba memasukkan cangkuk ke dalam Spotify. Tetapi pertama-tama kita perlu memikirkan apa yang kita mahu kait.
Seperti yang dinyatakan sebelum ini, anda hanya boleh mencipta cangkuk interpose untuk fungsi luaran, jadi kami akan mencari fungsi dalam libc atau masa jalan Objektif-C.
Semasa menyelidik tempat untuk menyambung, saya fikir tempat yang baik untuk memulakan cangkuk ialah pemegang Spotify "kekunci kawalan media" atau F7-F9 pada MacBook saya. Anggapkan pengendali untuk kekunci ini berfungsi apabila butang Seterusnya dipanggil dalam apl Spotify. Saya akhirnya menemui perpustakaan SPMediaKeyTap di: https://github.com/nevyn/spmediakeytap. Saya fikir saya akan mencubanya dan melihat sama ada Spotify menyalin dan menampal kod daripada perpustakaan ini. Dalam perpustakaan SPMediaKeyTap, terdapat kaedah startWatchingMediaKeys. Saya menjalankan perintah rentetan pada perduaan Spotify untuk melihat sama ada mereka mempunyai kaedah ini, dan sudah pasti:
Bingo!! Jika kita memuatkan perduaan Spotify ke dalam IDA (versi percuma sudah tentu) dan mencari rentetan ini, kita dapati kaedah yang sepadan:
Jika kita melihat fungsi ini sepadan Dalam kod sumber, kita akan menemui parameter menarik tapEventCallback bagi fungsi CGEventTapCreate:
Jika kita melihat kembali pada pembongkaran, kita dapat melihat bahawa subrutin sub_10010C230 diluluskan sebagai tapEventCallback parameter. Jika kita melihat pada kod sumber atau pembongkaran fungsi ini, kita melihat bahawa hanya satu fungsi perpustakaan CGEventTapEnable dipanggil:
Mari cuba sangkut fungsi ini.
Perkara pertama yang perlu kita lakukan ialah mencipta perpustakaan untuk menentukan CGEventTapEnable tersuai kami. Kodnya adalah seperti berikut:
#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>
panggilan fungsi dlsym untuk mendapatkan alamat fungsi CGEventTapEnable perpustakaan sebenar. Kemudian kita memanggil pelaksanaan lama supaya kita tidak sengaja melanggar apa-apa. Mari kita susun perpustakaan kita seperti ini (https://ntvalk.blogspot.com/2013/11/hooking-explained-detouring-library.html):
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:
Diakui, saya mengambil sedikit masa untuk melakukan ini, tetapi saya ingin menarik perhatian anda untuk menghubungi r12 di talian keempat di bahagian bawah. Jika anda melihat beberapa kes lain, anda akan menemui corak panggilan yang hampir serupa. Ini kelihatan seperti fungsi yang bagus, tetapi bagaimana kita tahu di mana ia?
Mari buka alat baharu: penyahpepijat. Saya menghadapi banyak masalah pada mulanya cuba menyahpepijat Spotify. Sekarang mungkin kerana saya tidak begitu biasa dengan penyahpepijat, tetapi saya fikir saya telah menghasilkan penyelesaian yang cukup bijak.
Kami mula-mula menetapkan cangkuk pada sub_10006DE40, dan kemudian kami mencetuskan titik putus dalam kod. Kita boleh melakukan ini dengan melaksanakan arahan pemasangan int 3 (cth. penyahpepijatan seperti GDB dan LLDB).
Begini rupa cangkuk dalam rangka kerja HookCase:
Selepas menambah ini pada perpustakaan templat HookCase, anda juga mesti menambahkannya pada tatasusunan user_hooks :
Kemudian kita boleh menggunakan templat yang disediakan oleh Makefile HookCase untuk menyusunnya. Pustaka kemudiannya boleh dimasukkan ke dalam Spotify menggunakan arahan berikut: HC_INSERT_LIBRARY=
Kami kemudiannya boleh menjalankan LLDB dan melampirkannya pada proses Spotify yang sedang berjalan seperti ini:
Cuba tekan F9 (jika Spotify bukan tetingkap aktif, ia boleh membuka iTunes). Baris int $3 dalam cangkuk harus mencetuskan penyahpepijat.
Kini kita boleh memasuki titik masuk sub_10006DE40. Ambil perhatian bahawa PC akan berada di lokasi yang sepadan dengan alamat yang ditunjukkan dalam IDA (saya rasa ini disebabkan oleh tempat proses dimuatkan ke dalam memori). Dalam proses semasa saya, arahan push r15 adalah pada 0x10718ee44:
Dalam IDA, alamat arahan ini ialah 0x10006DE44, yang memberikan kita offset 0x7121000. Dalam IDA, alamat di mana arahan r12 dipanggil ialah 0x10006E234. Kami kemudiannya boleh menambah offset ke alamat itu dan menetapkan titik putus dengan sewajarnya, b -a 0x10718f234, dan teruskan.
Apabila kita mencapai arahan sasaran, kita boleh mencetak kandungan daftar r12:
Apa yang perlu kita lakukan ialah menolak offset daripada alamat ini , lihat, kami mendapat alamat nominal kami: 0x100CC2E20.
Sekarang, mari kita sambung fungsi ini:
Tambahkannya pada tatasusunan user_hooks, kompil, jalankan dan Pemerhatian: Setiap kali kami menekan F9 atau klik butang seterusnya dalam apl Spotify, mesej kami dilog.
Sekarang kita telah mengaitkan fungsi langkau,
Saya akan menyiarkan kod yang selebihnya, tetapi saya tidak akan menyelesaikan kejuruteraan terbalik yang lain kerana artikel ini Artikel sudah cukup panjang.
Ringkasnya, saya juga mengaitkan fungsi sebelumnya (ini adalah senaman yang bagus jika anda mengikuti ini). Kemudian, dalam kedua-dua cangkuk, saya mula-mula menyemak sama ada lagu semasa sudah separuh jalan. Jika ya, saya tidak melakukan apa-apa, menganggap saya hanya bosan dengan lagu itu daripada menganggapnya tidak sesuai. Kemudian di belakang (F7), saya pop langkau terakhir.
Saya ingin menyatakan beberapa perkataan tentang cara menyemak sama ada lagu semasa sudah separuh jalan. Pendekatan asal saya adalah untuk benar-benar memanggil popen dan kemudian menjalankan arahan AppleScript yang sepadan, tetapi itu tidak sesuai.
Saya menjalankan pembuangan kelas pada binari Spotify dan menemui dua kelas: SPAppleScriptObjectModel dan SPAppleScriptTrack. Kaedah ini mendedahkan sifat yang diperlukan untuk kedudukan main balik, tempoh dan ID trek. Saya kemudian memaut getter untuk sifat ini dan memanggilnya menggunakan cangkuk seterusnya dan belakang (saya rasa Swizzle lebih masuk akal, tetapi saya tidak boleh membuatnya berfungsi).
Saya menggunakan fail untuk menjejak langkauan, di mana baris pertama mengandungi bilangan langkauan, pada langkauan kami menambah pembilang ini dan menulis ID penjejakan dan cap masa pada fail pada baris yang ditentukan oleh kaunter. Pada butang belakang, kami hanya mengurangkan kaunter ini. Dengan cara ini, apabila kami menekan butang belakang, kami hanya menetapkan fail untuk menulis langkauan baharu ke fail yang diundur.
Atas ialah kandungan terperinci Cara membuat kejuruteraan songsang Spotify.app dan menghubungkan fungsinya untuk mendapatkan data. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!