Shady behaviour by Spotify desktop client observed with Wireshark. Expertise appreciated!

A good method would also be not to keep data in typical locations. I always create separate dedicated private directories and absolutely avoid keeping them in a typical data location. What is more important, I also keep the data in the veracrypt container, which is encrypted whenever there is no need to access the content. In this way, the scope of snooping is limited by nosy applications, but not completely eliminated. For this purpose, you need to strongly limit applications with even hips.

The behavior may also be dictated by the fact that the application is searching/trying to import everything that is in the specified location.

And this behavior is nothing new as you can see…
https://www.reddit.com/r/truespotify/comments/pcdvaf/just_noticed_heavy_disk_usage_and_found_spotify/

If you still plan to use this application, then all you have to do is to block network traffic for the process towards smb servers.
And sensitive local locations will be limited with hips then they will just be empty for the application.

By the way, the telemetry that goes to the mother ship is probably interesting… :wink:

2 Likes

In combination with the shady traffic found in this thread, I also came across another unfortunate thread:

Very not impressed with this “Premium” service. Of course I give no microphone permissions on a degoogled GrapheneOS, but its still insulting to pay to be spied on.

I wonder if they try to scan files on android like they do desktop? Luckily file Discretionary Access Control (DAC) sandboxing has become a thing to mitigate this. Application Sandbox  |  Android Open Source Project

In adb there are the failed attempts to load GMS ads identifier:


And a failed attempt to read my serial identifier via TelephonyPermissions:


So despite having paid for premium to remove ads, but they still add ads to some podcasts. That’s double dipping.

Unfortunately the ads come through the same severs as the music so they aren’t filtered out by the pihole, very irritating:

https://open.spotify.com/ad/%s

Why am I paying to listen to ads? This is not acceptable.


So I did a little digging with the attempt to try and get rid of those ads by perhaps patching out the call. Ban me spotfiy I dont care, I refuse to pay to listen to ads.

In the process I found a concerning string: s_voice_ad_inaudible_tone_enabled

My note’s are a little old but here we go. The sample used was the snap package version.

The process path said
/snap/spotify/56/usr/share/spotify/spotify

I couldn’t find that path. Turns out snaps are mounted with their own virtual filesystem, and the actual .snap stored in
/var/lib/snapd/
Source: Managing Ubuntu Snaps: the stuff no one tells you | HackerNoon

I found spotify binary in
/var/lib/snapd/snaps/spotify_56.snap

Unzipping didnt work. To get into the snap, I had to mount with squashfs and dump the contents to a directory
sudo mount -t squashfs -o ro /var/lib/snapd/snaps/spotify_56.snap /path/to/extract/spotify
Source: How do I view the contents of a .snap file? - Ask Ubuntu

Now, digging through the extracted files (and following the path our original process was running) we find the actual binary in
<extractedPath>/usr/share/spotify/spotify

Open the binary in Ghidra. Once analyzed: Search > For Strings…
Filter for ‘ad’, sorted alphabetically by ‘String View’.

And right away we find some interesting enteries:
	002a262d		?? 2Fh    /	"/v1/ads/:slot"	string	14	false
	002c3b54		?? 2Fh    /	"/v1/events/:event/:ad_id"	string	25	true
	002abb2f	DAT_002abb2f	?? 2Fh    /	"/v1/playlist/:playlist-uri/metadata"	string	36	true
	002956ea		?? 2Fh    /	"/v1/podcast/metadata"	string	21	true
	002d7356		?? 2Fh    /	"/v1/podcast/nextAdSegment"	string	26	true
	002d72ed		?? 2Fh    /	"/v1/preview/:ad_id"	string	19	true
	002c1371		?? 2Fh    /	"/v1/settings/:slot/ad_server_endpoint"	string	38	true
	0028c28b		?? 2Fh    /	"/v1/settings/request_header/:header"	string	36	true
	002ba844		?? 2Fh    /	"/v1/testing/click_ad"	string	21	true
	00299a1f	DAT_00299a1f	?? 2Fh    /	"/v2/metadata"	string	13	true
	002d3e17		?? 2Fh    /	"/v2/settings/ad_server_endpoint"	string	32	true
	002bd001		?? 2Fh    /	"/v2/settings/state/ad_state_endpoint"	string	37	true
	002c586b		?? 2Fh    /	"/v2/testing/insert_ads"	string	23	true
	
Some more:
	0028e53e	s_active_ads_0028e53e	ds "active_ads"	"active_ads"	string	11	true
	002c151b	s_ad_fetch_error_(_002c151b	ds "ad fetch error ("	"ad fetch error ("	string	17	true
	00290fce	s_Ad_format_(_00290fce	ds "Ad format ("	"Ad format ("	string	12	true
	002957e6	s_ad_missing_id_002957e6	ds "ad missing id"	"ad missing id"	string	14	true
	002d9391	s_Ad_not_found_for_provided_ad_id_002d9391	ds "Ad not found for provided ad_id"	"Ad not found for provided ad_id"	string	32	true
	002a27d5	s_ad_parse_error_-_002a27d5	ds "ad parse error - "	"ad parse error - "	string	18	true
	002a6f22	s_ad_response_error_(_002a6f22	ds "ad response error ("	"ad response error ("	string	20	true
	0029796f	s_ad-logic/flashpoint_0029796f	ds "ad-logic/flashpoint"	"ad-logic/flashpoint"	string	20	true
	002c80ee	s_ad-logic/state/config_002c80ee	ds "ad-logic/state/config"	"ad-logic/state/config"	string	22	true
	002cc6d9	s_ad-session-persistence_002cc6d9	ds "ad-session-persistence"	"ad-session-persistence"	string	23	true
	00299ca3	s_ad-state-storage.bnk_00299ca3	ds "ad-state-storage.bnk"	"ad-state-storage.bnk"	string	21	true
	002b88e5	s_ad-use-adlogic_002b88e5	ds "ad-use-adlogic"	"ad-use-adlogic"	string	15	true
	002a6f10	s_ad.is_saved_track_002a6f10	ds "ad.is_saved_track"	"ad.is_saved_track"	string	18	true
	00295818	s_ad.non_explicit_00295818	ds "ad.non_explicit"	"ad.non_explicit"	string	16	true
	002d3ff2	s_ad.original_provider_002d3ff2	ds "ad.original_provider"	"ad.original_provider"	string	21	true
	002bf4fc	s_ad.token_002bf4fc	ds "ad.token"	"ad.token"	string	9	true
	0029344c	s_ad.video_orientation_0029344c	ds "ad.video_orientation"	"ad.video_orientation"	string	21	true
	002c147b	s_ad_break_in_progress_002c147b	ds "ad_break_in_progress"	"ad_break_in_progress"	string	21	true
	002dc2e4	s_ad_break_state_002dc2e4	ds "ad_break_state"	"ad_break_state"	string	15	true
	002c80ce	s_ad_break_time_002c80ce	ds "ad_break_time"	"ad_break_time"	string	14	true
	0034abb0	s_ad_core_sponsored_session_0034abb0	ds "ad_core_sponsored_session"	"ad_core_sponsored_session"	string	26	true
	002b091b	s_ad_disallow_002b091b	ds "ad_disallow"	"ad_disallow"	string	12	true
	002c58b2	s_ad_enabled_002c58b2	ds "ad_enabled"	"ad_enabled"	string	11	true
	002a6d5b	s_ad_id_002a6d5b	ds "ad_id"	"ad_id"	string	6	false
	0029df74	s_ad_injection_seek_0029df74	ds "ad_injection_seek"	"ad_injection_seek"	string	18	true
	002abe4e	s_ad_manager_002abe4e	ds "ad_manager"	"ad_manager"	string	11	true
	002957c9	s_ad_not_sponsored_by_rewarder_002957c9	ds "ad_not_sponsored_by_rewarder"	"ad_not_sponsored_by_rewarder"	string	29	true
	0028e3ff	s_ad_playback_0028e3ff	ds "ad_playback"	"ad_playback"	string	12	true
	002bd05a	s_ad_playback_id_002bd05a	ds "ad_playback_id"	"ad_playback_id"	string	15	true
	002c1490	s_ad_rule_disallows_002c1490	ds "ad_rule_disallows"	"ad_rule_disallows"	string	18	true
	002a4555	s_ad_server_endpoint_002a4555	ds "ad_server_endpoint"	"ad_server_endpoint"	string	19	true
	002d93c8	s_ad_slot_id_002d93c8	ds "ad_slot_id"	"ad_slot_id"	string	11	true
	002b892f	s_ad_state_pusher_error:_%s_002b892f	ds "ad_state_pusher error: %s"	"ad_state_pusher error: %s"	string	26	true
	002bd052	s_ad_type_002bd052	ds "ad_type"	"ad_type"	string	8	true
	
And some more:
	0034ab80	s_core-ads_0034ab80	ds "core-ads"	"core-ads"	string	9	true
	0034aba3	s_core-ads_0034aba3	ds "core-ads"	"core-ads"	string	9	true
	0034abcb	s_core-ads_0034abcb	ds "core-ads"	"core-ads"	string	9	true
	0034ac30	s_core-ads_0034ac30	ds "core-ads"	"core-ads"	string	9	true	
	00290fe9	s_core-podcast-ad-requester_00290fe9	ds "core-podcast-ad-requester"	"core-podcast-ad-requester"	string	26	true
	00337130	s_core-podcast-ads_00337130	ds "core-podcast-ads"	"core-podcast-ads"	string	17	true
	00337180	s_core-podcast-ads_00337180	ds "core-podcast-ads"	"core-podcast-ads"	string	17	true
	003350d0	s_core-podcast-ads-feature_003350d0	ds "core-podcast-ads-feature"	"core-podcast-ads-feature"	string	25	true
	00335120	s_core-podcast-ads-feature_00335120	ds "core-podcast-ads-feature"	"core-podcast-ads-feature"	string	25	true
	002ceabd	s_core-proxy-ad-requester_002ceabd	ds "core-proxy-ad-requester"	"core-proxy-ad-requester"	string	24	true
	00334178	s_core-voice-ad_00334178	ds "core-voice-ad"	"core-voice-ad"	string	14	true
	
Ooh! An advertisement URL -> https://open.spotify.com/ad/%s
	002a1d1a	s_https://open.spotify.com/ad/%s_002a1d1a	ds "https://open.spotify.com/ad/%s"	"https://open.spotify.com/ad/%s"	string	31	true	
	
And of course more:
	002dd036	s_injected-ad_002dd036	ds "injected-ad"	"injected-ad"	string	12	true	
	0029dfdc	s_invalid_ad_error__0029dfdc	ds "invalid_ad_error_"	"invalid_ad_error_"	string	18	true
	002c4ce8	s_invalid_download_error__002c4ce8	ds "invalid_download_error_"	"invalid_download_error_"	string	24	true
	0029f1e8	s_is_advertisement_0029f1e8	ds "is_advertisement"	"is_advertisement"	string	17	true
	002b642c	s_is_cpcl_ad_002b642c	ds "is_cpcl_ad"	"is_cpcl_ad"	string	11	true
	0029d0cc	s_is_podcast_advertisement_0029d0cc	ds "is_podcast_advertisement"	"is_podcast_advertisement"	string	25	true
	00383e60	s_isAvailableInMetadataCatalogue_00383e60	ds "isAvailableInMetadataCatalogue"	"isAvailableInMetadataCatalogue"	string	31	true
	00354ff7	s_isLoading_00354ff7	ds "isLoading"	"isLoading"	string	10	true
	00326080	s_isLoadingContents_00326080	ds "isLoadingContents"	"isLoadingContents"	string	18	true
	002abad0	s_isPremiumOnly_002abad0	ds "isPremiumOnly"	"isPremiumOnly"	string	14	true
	002cead5	s_skippable_ad_delay_002cead5	ds "skippable_ad_delay"	"skippable_ad_delay"	string	19	true
	002b3f8e	s_spotify:ad:_002b3f8e	ds "spotify:ad:"	"spotify:ad:"	string	12	true
	00290693	s_spotify:ad:%s_00290693	ds "spotify:ad:%s"	"spotify:ad:%s"	string	14	true
	002ba87b	s_test_ad_002ba87b	ds "test_ad"	"test_ad"	string	8	true
	0028c2af	s_The_ad_slot_could_not_be_trigger_0028c2af	ds "The ad slot could not be triggered"	"The ad slot could not be triggered"	string	35	true
	002ce91c	s_The_ad_slot_has_not_been_created_002ce91c	ds "The ad slot has not been created yet"	"The ad slot has not been created yet"	string	37	true
	002bd10d	s_too_many_redirect_ads_002bd10d	ds "too_many_redirect_ads"	"too_many_redirect_ads"	string	22	true
	002c811c	s_TriggerAdNotSuccessEvent_002c811c	ds "TriggerAdNotSuccessEvent"	"TriggerAdNotSuccessEvent"	string	25	true
	002bd172	s_TriggerAdSuccessEvent_002bd172	ds "TriggerAdSuccessEvent"	"TriggerAdSuccessEvent"	string	22	true
	0028e51d	s_triggered_ad_duration_0028e51d	ds "triggered_ad_duration"	"triggered_ad_duration"	string	22	true
	002b40a5	s_triggered_dummy_ad_duration_002b40a5	ds "triggered_dummy_ad_duration"	"triggered_dummy_ad_duration"	string	28	true
	00295837	s_UpdatePendingAd_NextContext_00295837	ds "UpdatePendingAd_NextContext"	"UpdatePendingAd_NextContext"	string	28	true
	002a6f36	s_UpdatePendingAd_Stream_002a6f36	ds "UpdatePendingAd_Stream"	"UpdatePendingAd_Stream"	string	23	true
	002af8ec	s_UpdatePendingAdEvent_002af8ec	ds "UpdatePendingAdEvent"	"UpdatePendingAdEvent"	string	21	true
	
Hmm, another URI? This looks like a callback redirect URI
	002cc5b9	s_Usage:_GET_sp://ads/v1/ads/<slot_002cc5b9	ds "Usage: GET sp://ads/v1/ads/<slot>"	"Usage: GET sp://ads/v1/ads/<slot>"	string	34	true
	
Oh, you thought that was it for ad related strings...
	00337150	s_use_new_podcast_ad_segments_prov_00337150	ds "use_new_podcast_ad_segments_provider"	"use_new_podcast_ad_segments_provider"	string	37	true
	002af805	s_useAdvertiserImage_002af805	ds "useAdvertiserImage"	"useAdvertiserImage"	string	19	true
	0029cad1	s_user_space_0029cad1	ds "user space"	"user space"	string	11	true
	002caadc	s_Users_002caadc	ds "Users"	"Users"	string	6	true
	002cc185	s_video/ad_002cc185	ds "video/ad"	"video/ad"	string	9	true
	002b6492	s_VND.Spotify.Ads-Payload_002b6492	ds "VND.Spotify.Ads-Payload"	"VND.Spotify.Ads-Payload"	string	24	true
	002ca2bc	s_voice_ad_002ca2bc	ds "voice_ad"	"voice_ad"	string	9	true
	002920ac	s_voice_ad_audio_sink_002920ac	ds "voice_ad_audio_sink"	"voice_ad_audio_sink"	string	20	true
	00293151	s_VoiceAdBuilder_00293151	ds "VoiceAdBuilder"	"VoiceAdBuilder"	string	15	true
	002af483	s_VoiceAdCosmosBuilder_002af483	ds "VoiceAdCosmosBuilder"	"VoiceAdCosmosBuilder"	string	21	true

Now this string is particularly interesting and mildly concerning:

	00334190	s_voice_ad_inaudible_tone_enabled_00334190	ds "voice_ad_inaudible_tone_enabled"	"voice_ad_inaudible_tone_enabled"	string	32	true

An inaudible tone huh?!

I highly suspect this to be an Ultrasonic Beacon:

Ultrasonic beacons are sounds with frequencies from 18 kHz to 20 kHz. Humans can’t hear it, but most mobile devices’ microphones can easily detect the ultrasonic beacons. When emitting from retail stores or embedded to advertisements and even websites, these inaudible sounds are picked up by microphones of mobile devices.

I have not confirmed this.

When I find the time I might revisit this. I’d like to maybe set up some sort of test with a microphone to confirm. Techniques for detecting this are described here: https://www.sec.cs.tu-bs.de/pubs/2017a-eurosp.pdf

6 Likes

So between all this nonsense, I will be cancelling my Spotify:

  1. shady traffic
  2. shady file scanning
  3. shady microphone patent to infer emotional state, age, gender, and accent
  4. shady inaudible tone → likely an Ultrasonic Beacon (unconfirmed)
  5. ads on podcasts while paying for premium to get rid of ads

Not cool spotify.

Besides, I’d rather pay artists more directly than pay spotify who gives fractions of pennies to artists:


I like Bandcamp, but Epic bought it in 2022. Epic employs dark-patterns in their games: The ‘dark patterns’ used by Epic Games that led to the largest ever FTC penalty - MarketWatch
And a certain large untrustworthy entity has a ~40% share in Epic. So I don’t want to support them either.
edit: Epic sold to Songtradr

Alternatives:

1 Like

I think they unbought it recently.

Not sure who owns it now though.

A company named SongTrader bought it from Epic for an undisclosed amount. No idea yet if this is a suspicious company.


Ever since the the release of the ultrasonic tone and other shady stuff like listening from the mic of your cellphone, I’ve stopped subbing to Music streaming services.

2 Likes

oh. well that’s better at least!

Bandcamp pays artists 82 percent of every transaction, while Spotify is widely reported to pay a small fraction of a cent per stream.

2 Likes

not much showing up in google.
but i found this…

turns out voice_ad_inaudible_tone_enabled might be a mitigation against a mytre attack
the other stuff looks to be to do with drm.

il load up wireshark myself in a bit and have a look at my free account… il let you know if i find similar… (i shouldnt if they are premium features)

3 Likes

I also want to dig deeper into refInaudibleTone.

will follow xrefs and report any findings when/if I find the time.

2 Likes

nope you were rite it is there ad platform not a mitigation.
seems Spotify has tiers for which you get ads even with premium.
seems you can watch/listen to some artists in premium and get ads by design.
but if you buy there video/album there is a drm check and it stops the ad playing.

yeah its double dipping but no different to buying espn then having to pay to watch a boxing match ppv.

and yes IT SUX!

3 Likes

also, is there a way to block specific directories on a domain at the router level? the pihole can only filter the DNS as far as I understand.

would an OpenWrt or DD-WRT type setup be able to provide more advanced filtering? or some sort of firewall like pfSense?

1 Like

you block specific vm scripts from triggering from the dev console in the browser…
i have no idea on mobile.
it seems a lot of the crap your seeing isnt in the web browser, just there mobile app.

oh and thats the web browser shitting itself on spotify… so many errors, and its typical when you use ublock origin.
if you can, put something like opera gx on your mobile and sign into spotify with that.
you can then add ublock to opera as an extension which you cant with the spotify app.

The PLL doesnt like to be @ lol. Hi PLL. Dont die on the mountains, you dummy, if you are reading this.

1 Like

noted. fixed. apologies.

1 Like

your solution?.

I think the occasional @ is ok. IIRC someone was in the Lounge @ him a lot and he got annoyed. Ill go bother him in his thread.

It because of that thread I @ cuz knowledge

1 Like

last time i asked about black holing dns calls… he sent me to this.
he also has some stuff on firewalls, rainbowtables and the likes.
tbh its all a bit overwhelming but i still go back for reference :slight_smile:

3 Likes

this is good stuff! i have actually been meaning to do some extras on the pihole, its pretty stock currently

Its so out of date but yeah still applies. I havent done much in a while. Got a lot of other projects

4 Likes

If it is American in origin, you can be assured they are spying on you. When Google forced location services on for every app, regardless of actual need, that told me how valued that data was to them. US politicians have received so much lobby financing cash that there is almost no protections in law. The laws that do exist are continually amended to be weaker and weaker.

At least the EU has decent privacy protections and isn’t subject to the same levels of legislative bribery. However, they are a weak voice in the face of a hurricane.

And the truth is, the vast majority simply don’t know or even care. As long as they get onto FaceTube and have access to their daily cat videos, they are happy.

“a well-informed electorate is a prerequisite for a democracy”

I can’t seem to find any results for “does Spotify use ultrasonic beacon?”

Surely I am not the first to stumble on this.


So… two potential routes to attack this.

Path #1: Audio analysis.

The strings suggest it happens during voice ads. Since the tones are meant to be picked up be phones, I think a simple phone recording would suffice. If not, I could borrow a zoom recorder for a higher quality samples.

Put on the podcast, wait for ads and record a bunch of audio samples during voice ads.

Then throw it in a spectogram to easily visualize the patterns in the 18kHz+ part of the spectrum. (I’m a big fan of the spectogram in foobar2k)

Looking for something like this:


Source: from the ‘Privacy Threats through Ultrasonic Side Channels on Mobile Devices’ paper link in previous post.

And dig more into the existing detection techniques.

Path #2: chase the calls

Checking my notes again, this is where I got lazy last time. I remember now trying to chase the paths and xrefs, and ran into a bunch of “do nothing functions”

Meaning a bunch of calls were wrapped with funtions that did nothing and then returned. Obfuscation.

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined FUN_026f8900()
             undefined         AL:1           <RETURN>
                             FUN_026f8900                                    XREF[3]:     0083bc88, 010dee18(*), 
                                                                                          thunk_FUN_026f8900:026f88f0(T), 
                                                                                          thunk_FUN_026f8900:026f88f0(j)  
        026f8900 48 89 f8        MOV        RAX,RDI
        026f8903 c3              RET

Which essentially translates to

undefined8 FUN_026f8900(undefined8 param_1) {
    return param_1;
}

I had to learn what is a ‘thunk’

Thunk function: a thunk is a subroutine used to inject a calculation into another subroutine. Thunks are primarily used to delay a calculation until its result is needed, or to insert operations at the beginning or end of the other subroutine: Thunk - Wikipedia

A thunk is another word for a function. But it’s not just any old function. It’s a special (and uncommon) name for a function that’s returned by another. Basically a wrapped function:

function wrapper_function() {
    // this one is a "thunk" because it defers work for later:
    return function thunk() {   // it can be named, or anonymous
        console.log('do stuff now');
    };
}

Anywho, this was a common pattern. I started renaming these functions manually as func_donothing_xxxxxx()
But there were to many, I got tired of renaming these manually. Who’d a thunc it?

So I started looking at Ghidra’s scripting capabilities.

The pattern itself is very easy to detect.

  • they all seem to do the same thing which is just return the parameter that was passed to the function. or just return immediately and do absolutely literally nothing.
  • they seem to be in close proximity
026f10c0		48 89 f8 c3 cc cc cc cc cc cc cc cc cc cc cc cc <-- bs func A
026f10d0		48 89 f8 c3 cc cc cc cc cc cc cc cc cc cc cc cc <-- bs func B
026f10e0		48 89 f8 c3 cc cc cc cc cc cc cc cc cc cc cc cc <-- bs func C
026f10f0		c3 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc <-- variation of BS func that just ret instead of returning the parameter

I wanted to write a function to detect these “do nothing” thuncs and funcs and rename them for me so I don’t waste time stepping into them.

I started reading about the scripting functions but got busy with other things. This is where I stopped.

This is a great channel: https://www.youtube.com/@0x6d696368

Probably better to exhaust approach #1 first as it seems like less work.

2 Likes