#include "pch.h" #include "Utils.h" #include "Hooks.h" typedef int (__cdecl* keyToLE_v25)(unsigned int* dest, int* key, int bits); keyToLE_v25 keyToLE_v25_hook = nullptr; typedef int (__cdecl* keyToLE_v28)(unsigned int* dest, int* key, int bits, bool isEncoded); keyToLE_v28 keyToLE_v28_hook = nullptr; typedef int* (__thiscall* authToken_v45)(void* This, int* a2); authToken_v45 authToken_v45_hook = nullptr; typedef int* (__thiscall* openTrack_v45)(void* This, int a2, void* a3, int a4, __int64 position, char a6, void* a7); openTrack_v45 openTrack_v45_hook = nullptr; typedef int* (__thiscall* log_v45)(void* This, int a2, int a3, void* a4, const char* classStr, int a6, DWORD* logThing); log_v45 log_v45_hook; typedef void (__thiscall* fileIdWriter_v45)(void* This, int* a2); fileIdWriter_v45 fileIdWriter_v45_hook = nullptr; typedef void (__thiscall* signalEmitter_v45)(void* This, int a1, int a2); signalEmitter_v45 signalEmitter_v45_hook = nullptr; typedef void (__cdecl* keyDecoder_v47)(); keyDecoder_v47 keyDecoder_v47_hook = nullptr; typedef int (__thiscall* startUpdate_v47)(char* This, char* newVerChars, char** updateUrlPtr, void* a4, int a5, void* a6, int a7); startUpdate_v47 startUpdate_v47_hook = nullptr; typedef void (__thiscall* uriHandler_v47)(DWORD* This, void* a2, int a3, char a4); uriHandler_v47 uriHandler_v47_hook = nullptr; typedef bool (__cdecl* isValidUrl_v25)(int* urlThingArg, int a2); isValidUrl_v25 isValidUrl_v25_hook = nullptr; std::string authToken = std::string(); std::string keyStr = std::string(); std::string uriStr = std::string(); std::string fileId = std::string(); __int64 newPosition = 0; bool signalled = false; int destKeyPtr = 0; char* keyBuffer_v47; std::string lastUri = std::string(); void TryDownload() { if (signalled && lastUri.compare(uriStr) != 0) { signalled = false; lastUri = uriStr; std::thread t2(Utils::DownloadSong, std::string(fileId), uriStr, keyStr, authToken); t2.detach(); } } int __cdecl keyToLE_hook_v25(unsigned int* dest, int* key, int bits) { if (bits == 128) { BYTE keyBuffer[16]; BYTE* keyBufPtr = keyBuffer; memcpy(keyBufPtr, key, 16); // Only print out key if it is different std::string newKeyStr = Utils::HexString(keyBufPtr, 16); if (newKeyStr.compare(keyStr) != 0) std::cout << "Key: " << newKeyStr << std::endl << std::endl; keyStr = newKeyStr; } return keyToLE_v25_hook(dest, key, bits); } int __cdecl keyToLE_hook_v28(unsigned int* dest, int* key, int bits, bool isEncoded) { if (bits == 128) { void* decodedKeyPtr = key; if (isEncoded) { // key is encoded with some sort of algorithm; decode it here unsigned int keyDecoded[4]; unsigned int uVar1; unsigned int keyPtr; unsigned int uVar3; int index; keyPtr = *key; index = 15; uVar3 = key[1]; keyDecoded[0] = key[2]; keyDecoded[1] = key[3]; keyDecoded[2] = key[4]; keyDecoded[3] = key[5]; do { uVar1 = keyDecoded[index - 1 & 3]; keyDecoded[index & 3] = keyDecoded[index & 3] + (((((uVar1 + index + keyPtr & uVar1 * 16 + uVar3) * 2 + uVar1 * -17) - index) - uVar3) - keyPtr); index--; } while (index >= 0); decodedKeyPtr = &keyDecoded; } // Copy key bytes to new buffer char keyBuffer[16]; char* keyBufPtr = keyBuffer; memcpy(keyBufPtr, decodedKeyPtr, 16); // Only print out key if it is different std::string newKey = std::string(keyBufPtr, 16); if (newKey.compare(keyStr) != 0) { std::cout << "Key: " << Utils::HexString(reinterpret_cast(&newKey[0]), 16) << std::endl << std::endl; keyStr = newKey; if (Utils::GetSpotifyVersion() >= 45) TryDownload(); } } return keyToLE_v28_hook(dest, key, bits, isEncoded); } int* __fastcall authToken_hook_v45(void* This, void* _EDX, int* a2) { char* authToken = (char*) *(DWORD*)((a2) + 2); // 8/4 = 2 //std::cout << "authToken: " << authToken << std::endl << std::endl; ::authToken = std::string(authToken); return authToken_v45_hook(This, a2); } int* __fastcall openTrack_hook_v45(void* This, void* _EDX, int a2, void* a3, int a4, __int64 position, char a6, void* a7) { //std::cout << "openTrack!!!" << std::endl << std::endl; return openTrack_v45_hook(This, a2, a3, a4, newPosition, a6, a7); } int* __fastcall log_hook_v45(void* This, void* _EDX, int a2, int a3, void* a4, const char* classStr, int a6, DWORD* logThing) { if (!Utils::BadPtr(logThing)) { char* logChars = (char*)(*logThing); if (!Utils::BadPtr(logChars)) { std::string logStr = std::string(logChars); if (logStr.length() > 32 && logStr.compare(8, 9, "track_uri") == 0) // 19 + 13 = 32 { if (logStr.compare(19, 13, "spotify:track") == 0) { //std::cout << "Track URI: " << logStr.substr(19, std::string::npos) << std::endl; uriStr = logStr.substr(19, std::string::npos); newPosition = 0; } else if (logStr.length() > 34 && logStr.compare(19, 15, "spotify:episode") == 0) { //std::cout << "Episode URI: " << logStr.substr(19, std::string::npos) << std::endl; uriStr = logStr.substr(19, std::string::npos); newPosition = 0; } // TODO: e.g. spotify:ad:000000014990f3ec000000203522e473 //else if (logStr.compare(19, 11, "spotify:ad") == 0) // Possibly this works? /*else { std::cout << "Skipping ad: " << logStr.substr(19, std::string::npos) << std::endl; newPosition = 29000; // 29 seconds: duration of ad }*/ } } } return log_v45_hook(This, a2, a3, a4, classStr, a6, logThing); } void __fastcall fileIdWriter_hook_v45(void* This, void* _EDX, int* a2) { // [[ebp+8]+28] char* fileId = (char*) *(DWORD*)(a2 + 16); // 0x40 / 4 = 16 ::fileId = std::string(fileId); /*std::cout << "fileId = " << fileId << std::endl; std::cout << "uriStr = " << uriStr<< std::endl;*/ if (uriStr.length() > 15 && uriStr.compare(0, 15, "spotify:episode") == 0) TryDownload(); return fileIdWriter_v45_hook(This, a2); } int signalEmitterInitCount = 0; const int signalEmitterRequired = 3; void __fastcall signalEmitter_hook_v45(void* This, void* _EDX, int a1, int a2) { // Required in order to guarentee accurate data needed if (signalEmitterInitCount < signalEmitterRequired) signalEmitterInitCount++; else { //std::cout << "signalled = true" << std::endl; signalled = true; } return signalEmitter_v45_hook(This, a1, a2); } int __cdecl keyToLE_hook_v47(unsigned int* dest, int* key, int bits, bool isEncoded) { // Same hook as previously, except key is not decoded in this function anymore if (bits == 128 && isEncoded) destKeyPtr = (int)dest; return keyToLE_v28_hook(dest, key, bits, isEncoded); } void __cdecl KeyBufferToKeyStr() { std::string newKey = std::string(keyBuffer_v47, 16); if (keyStr.compare(newKey) != 0) { keyStr = newKey; std::cout << "Key: " << Utils::HexString(reinterpret_cast(&newKey[0]), 16) << std::endl; TryDownload(); } } __declspec(naked) void keyDecoder_hook_v47() { // The spot where this hooks and runs is very weird. Spotify now handles decoding of the key in an instruction set // held in the .data section (where disassemblers won't typically explore). Looks intentional. :) __asm { pushfd // Push flags and registers to stack to preserve state before pushad mov ebx, esi sub ebx, 12 // Subtract 12 from esi to compare; esi will match on the last 4 bytes of key cmp destKeyPtr, ebx // Check if destKeyPtr matches ebx (esi - 12) jne END // If destKeyPtr and esi - 12 don't match, jump to end mov eax, offset keyBuffer_v47 // Get address from char pointer keyBuffer_v47 mov edi, [eax] mov cx, 4 // Iterate 4 times (copy 4 bytes each time) BSWAP_LOOP: // Loop to swap endianness of each dword mov eax, [ebx] bswap eax mov [edi], eax add ebx, 4 add edi, 4 dec cx jnz BSWAP_LOOP call KeyBufferToKeyStr // Set keyStr to chars from keyBuffer_v47 (and print key) END: popad // Pop flags and registers back from the stack to preserve state before popfd jmp keyDecoder_v47_hook // Jump to trampoline function to run overwritten instructions and continue } } int __fastcall startUpdate_hook_v47(char* This, void* _EDX, char* newVerChars, char** updateUrlPtr, void* a4, int a5, void* a6, int a7) { *updateUrlPtr[0] = '\x00'; // Breaks the URL to prevent updating return startUpdate_v47_hook(This, newVerChars, updateUrlPtr, a4, a5, a6, a7); } void __fastcall uriHandler_hook_v47(DWORD* This, void* _EDX, void* a2, int a3, char a4) { int uriType = *This; // Check if uri type is an ad (12) if (uriType == 12) { DWORD* adUri = This - 42; // 168 / 4 = 42 char* adUriChars = (char*)*adUri; for (int index = 0; index < 32; index++) adUriChars[index] = '0'; //std::cout << "Ad uri: spotify:ad:" << std::string((char*)*adUri, 32) << std::endl; return; } return uriHandler_v47_hook(This, a2, a3, a4); } bool __cdecl isValidUrl_hook_v25(int* urlThingArg, int arg_4) { unsigned char* source = (unsigned char*)*(void**)*(urlThingArg + 1); std::u16string u16_str(reinterpret_cast(source)); std::string utf8Url = std::wstring_convert, char16_t>{}.to_bytes(u16_str); //std::cout << "URL: " << utf8Url << std::endl; if (utf8Url.find("doubleclick") != std::string::npos) { //printf("Blocked URL: %s\n", utf8Url.c_str()); return false; } else return isValidUrl_v25_hook(urlThingArg, arg_4); } char* GetKeyFuncAddrV26() { BYTE ref_v19 = 0x55; BYTE* byteAtAddrStr = (BYTE*)0x010800C0; // Byte at byteAtAddr in 1.1.26-19 is 0x55 if (*byteAtAddrStr == ref_v19) return (char*)0x010800C0; else return (char*)0x0107FEC0; } char* GetKeyFuncAddrV27() { BYTE ref_v7 = 0x55; BYTE* byteAtAddrStr = (BYTE*)0x01068F90; // Byte at byteAtAddr in 1.1.27-7 is 0x55 if (*byteAtAddrStr == ref_v7) return (char*)0x01068F90; else return (char*)0x01068F20; } /* This will return addrOne if addrOneByte matches the BYTE at addrOne, or return addrTwo if not */ char* GetKeyFunc(char* addrOne, char* addrTwo, BYTE addrOneByte) { if (*(BYTE*)addrOne == addrOneByte) return addrOne; return addrTwo; } void Hooks::Init() { int spotifyVer = Utils::GetSpotifyVersion(); // Method is stripped from Release build if this isn't here :/ std::cout << "Spotify version: 1.1." << Utils::GetSpotifyVersion() << std::endl << std::endl; switch (spotifyVer) { case 25: keyToLE_v25_hook = (keyToLE_v25)Utils::TrampHook32((char*)0x0106B920, (char*)keyToLE_hook_v25, 6); break; case 26: // Two 1.1.26 versions keyToLE_v25_hook = (keyToLE_v25)Utils::TrampHook32(GetKeyFuncAddrV26(), (char*)keyToLE_hook_v25, 6); break; case 27: // Two 1.1.27 versions keyToLE_v25_hook = (keyToLE_v25)Utils::TrampHook32(GetKeyFuncAddrV27(), (char*)keyToLE_hook_v25, 6); break; case 28: keyToLE_v28_hook = (keyToLE_v28)Utils::TrampHook32((char*)0x01074650, (char*)keyToLE_hook_v28, 6); break; case 29: keyToLE_v28_hook = (keyToLE_v28)Utils::TrampHook32((char*)0x010861B0, (char*)keyToLE_hook_v28, 6); break; case 30: keyToLE_v28_hook = (keyToLE_v28)Utils::TrampHook32((char*)0x0108E840, (char*)keyToLE_hook_v28, 6); break; case 44: keyToLE_v28_hook = (keyToLE_v28)Utils::TrampHook32((char*)0x010CABC0, (char*)keyToLE_hook_v28, 6); break; case 45: keyToLE_v28_hook = (keyToLE_v28)Utils::TrampHook32((char*)0x010CF780, (char*)keyToLE_hook_v28, 6); authToken_v45_hook = (authToken_v45)Utils::TrampHook32((char*)0x00BF75F0, (char*)authToken_hook_v45, 7); //openTrack_v45_hook = (openTrack_v45)Utils::TrampHook32((char*)0x00CA5740, (char*)openTrack_hook_v45, 5); log_v45_hook = (log_v45)Utils::TrampHook32((char*)0x010F2370, (char*)log_hook_v45, 5); fileIdWriter_v45_hook = (fileIdWriter_v45)Utils::TrampHook32((char*)0x00CBB560, (char*)fileIdWriter_hook_v45, 5); signalEmitter_v45_hook = (signalEmitter_v45)Utils::TrampHook32((char*)0x00B095A0, (char*)signalEmitter_hook_v45, 5); break; case 46: keyToLE_v28_hook = (keyToLE_v28)Utils::TrampHook32((char*)0x010C2FB0, (char*)keyToLE_hook_v28, 6); authToken_v45_hook = (authToken_v45)Utils::TrampHook32((char*)0x00BEC8E0, (char*)authToken_hook_v45, 7); log_v45_hook = (log_v45)Utils::TrampHook32((char*)0x010E59E0, (char*)log_hook_v45, 5); fileIdWriter_v45_hook = (fileIdWriter_v45)Utils::TrampHook32((char*)0x00CB00D0, (char*)fileIdWriter_hook_v45, 5); signalEmitter_v45_hook = (signalEmitter_v45)Utils::TrampHook32((char*)0x00B02270, (char*)signalEmitter_hook_v45, 5); break; case 47: keyBuffer_v47 = new char[16]; // 128 bits = 16 bytes keyToLE_v28_hook = (keyToLE_v28)Utils::TrampHook32((char*)0x010C5B00, (char*)keyToLE_hook_v47, 6); keyDecoder_v47_hook = (keyDecoder_v47)Utils::TrampHook32((char*)0x0153148D, (char*)keyDecoder_hook_v47, 7); authToken_v45_hook = (authToken_v45)Utils::TrampHook32((char*)0x00BED0F0, (char*)authToken_hook_v45, 7); log_v45_hook = (log_v45)Utils::TrampHook32((char*)0x010E8750, (char*)log_hook_v45, 5); fileIdWriter_v45_hook = (fileIdWriter_v45)Utils::TrampHook32((char*)0x00CB0630, (char*)fileIdWriter_hook_v45, 5); signalEmitter_v45_hook = (signalEmitter_v45)Utils::TrampHook32((char*)0x00AFBB50, (char*)signalEmitter_hook_v45, 5); //startUpdate_v47_hook = (startUpdate_v47)Utils::TrampHook32((char*)0x009FB530, (char*)startUpdate_hook_v47, 5); //uriHandler_v47_hook = (uriHandler_v47)Utils::TrampHook32((char*)0x010A39E0, (char*)uriHandler_hook_v47, 5); //isValidUrl_v25_hook = (isValidUrl_v25)Utils::TrampHook32((char*)0x0105CD80, (char*)isValidUrl_hook_v25, 6); break; } }