
Skiptracing: Reversing Spotify.app - mmastrac
https://medium.com/@lerner98/skiptracing-reversing-spotify-app-3a6df367287d
======
saagarjha
> I recently learned about a process called hooking, where you can “intercept”
> function calls made from a target binary. I thought this would be the
> perfect way to track skips.

> The most common type of hook is the interpose hook.

Note that dyld has built-in support for interposing; you don't need to mess
around with dlsym and RTLD_NEXT (which requires disabling the two-level
namespace anyways, as you seem to have figured out, which can cause some
programs to misbehave):
[https://opensource.apple.com/source/dyld/dyld-210.2.3/includ...](https://opensource.apple.com/source/dyld/dyld-210.2.3/include/mach-o/dyld-
interposing.h)

> As stated earlier, an interpose hook can only be created for an external
> function, so we’ll look for a function in the libc or in the Objective-C
> runtime.

Yes, but the Objective-C runtime lets you swizzle anything :) No need to dig
around for an external C function to patch.

> Spotify opened fine but Apple’s System Integrity Protection (SIP) didn’t let
> us load our unsigned library :(.

No, this is not System Integrity Protection: it's Library Validation, which
prevents loading libraries signed with a different Team ID than the main
binary was signed with. You can remove the binary's code signature to get
around this.

> We’ll first set a hook on sub_10006DE40 and then we will trigger a
> breakpoint from within our code. We can do this by executing the assembly
> instruction int 3 which is what debuggers like GDB and LLDB use to trigger
> breakpoints.

Or, compile with debug symbols and put a breakpoint at your function as you
normally would.

> Notice that the PC will be at an offset address from the one shown in IDA
> (honestly, I don’t have the best grasp as to why this happens but I assume
> it’s due to where the process is loaded into memory).

Most binaries are compiled position-independently on macOS, and get loaded at
a randomized address (but the debugger should usually turn off the
randomization).

------
miki123211
for those interested in the much lower-level details on how spotify works,
Librespot[1] and its Golang and Java forks for those who don't speak Rust
should definitely be interesting. It can act as a Spotify Connect receiver and
play songs, as well as access Spotify's metadata, control other devices etc.

[1] [https://github.com/librespot-org/librespot](https://github.com/librespot-
org/librespot)

------
stevenhuang
Before this article, I've always thought Spotify was an electron app (or
something of the sort), so never bothered installing it. I was wondering why
you didn't just open a Dev console and inject some JS :).

Regarding the Apple code signing requirement: is it the case then for all
hooks of this sort that one is effectively required to be in the Developer
program?

So I was curious if LD_PRELOAD is prevented from working, but seems there's a
simple work around:

> On macOS there is an additional problem. Newer versions of macOS have a
> security subsystem called System Integrity Protection. For our purposes the
> problem is that it prevents injecting code via DYLD_INSERT_LIBRARIES (the
> macOS equivalent of LD_PRELOAD) into any binary in /bin, /sbin, /usr/bin and
> /usr/sbin.

> Luckily, there’s an easy workaround. Just create a new directory, copy all
> the binaries from /bin, /sbin, /usr/bin and /usr/sbin into that directory,
> and then add it to the start of your $PATH environment variable. Once the
> binaries are out of those special directories code injection works just
> fine, and since they’re only 100MB copying them is quite fast.

From [https://www.datawire.io/code-injection-on-linux-and-
macos/](https://www.datawire.io/code-injection-on-linux-and-macos/)

~~~
auscompgeek
From my understanding, Spotify uses the Chromium Embedded Framework. I'm not
sure which parts are rendered with CEF, but since Spotify is using CEF
directly rather than Electron, it seems likely they have a fair bit of native
code as well.

~~~
saagarjha
Last I checked, they use Chromium Embedded for most of the UI, and native code
to interact with platform-specific features such as the Touch Bar.

~~~
barbecue_sauce
Any idea if Chromium Embedded has the same memory footprint as Electron? (That
seems to be the main complaint about Electron that I've seen).

~~~
coldtea
Electron is not anything much different than Chromium Embedded plus some
native API hooks, so it's not the Electron parts that eat the resources, it's
Chromium (and basically, it's the fact that the app is basically a web app
that needs a whole layered and bloated rendering engine -DOM- and a slower
runtime - js - compared to native).

~~~
testvox
Doesn't electron include the nodejs runtime as well?

~~~
coldtea
Yes, which is just v8 + some system interfacing libs, not something that is
slow/memory hungry by itself anymore than v8 alone is.

------
Fogest
Based on the hooks, injecting, and decompiling this seems like how people hack
a video game, but instead this is for a music player. Interesting twist, and
kind of neat!

I just want Spotify to give me better recommendations instead of the same
songs over and over.

------
fivre
While interesting for reasons other than the end goal, I find it somewhat
amusing that someone would hook code into the Spotify binary to provide,
effectively, more or less the same data that Spotify's native Last.fm
integration would.

------
1023bytes
While this is pretty cool, it's quite impractical. You could just poll their
HTTP API for the currently playing song and compare the playtime. Plus this
would work for any client, even mobile devices.

~~~
bashbjorn
Agreed. Although I'd much sooner recommend just using Mopidy. Mopidy supports
spotify and many other music services, and has a number of low-effort ways of
controlling playback programmatically.

But for sure the interesting part of this is the method, not the purpose.

------
dehrmann
> The hard part is tracking skips. The Spotify web API provides no endpoint
> for this.

This should do just that: [https://developer.spotify.com/documentation/web-
api/referenc...](https://developer.spotify.com/documentation/web-
api/reference/player/skip-users-playback-to-next-track/)

~~~
jchw
But that's for executing a skip, not getting a list of skips.

------
lucb1e
Interesting post, I actually want the exact same thing! I've tried making
playlists that duplicate parts of the main library, one per mood, but it
doesn't really work. If Spotify could learn which songs I consistently skip
after starting with (or completely listening to) a given other song, that
sounds like it would solve the problem.

------
thundergolfer
Ahh nice. I am right now working on that same thing, tracking plays _and_
skips. I just want to simply analyse my own listening more deeply though.

My current implementation is a hack that just keeps polling Spotify with
‘shpotify’ to grab data. This article is more the implementation I actually
wanted.

------
almc77
I would expect a skipped song would play for less than a second. Could you not
filter based on playtime

------
ClassyJacket
I was looking for something just like this in order to create an app that
displays current song information on the Touch Bar. Excellent!

Edit: Doesn't look like this would be practical for distribution to others but
I still might make it for myself.

~~~
zuppy
you can already do that without hacking anything, Spotify exposes that
information through Apple Script:

    
    
      if application "Spotify" is running then
          tell application "Spotify"
              if player state is playing then
                  return (get artist of current track) & " - " & (get name of current track)
              else
                  return ""
              end if
          end tell
      end if
    

source:
[https://vas3k.com/blog/touchbar/#scroll60](https://vas3k.com/blog/touchbar/#scroll60)

You can also install a collection of scripts from here:
[https://goldenchaos.net/goldenchaos-
btt.html](https://goldenchaos.net/goldenchaos-btt.html)

You will need Better Touch Tool for that:
[https://folivora.ai/](https://folivora.ai/)

~~~
jrowley
Yeah, I use apple script to display current track / artist in my tmux status
bar. Super handy!

------
p49k
So much of this work would be unnecessary (at least in the EU) if Spotify
wasn’t blatantly violating GDPR and refusing to provide full listening data
when requested.

