From 5fa5698d9e12478d9a410a06984db931c00eec6e Mon Sep 17 00:00:00 2001 From: _ <🐱> Date: Sun, 29 Nov 2020 00:52:38 -0700 Subject: [PATCH] Add full support for 1.1.47 with new key extraction mechanism; stop duplicate downloads --- README.md | 1 + SpotifyKeyDumper/Hooks.cpp | 81 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c0071ba..a3c5fb7 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Crossed out items support key dumping but not automatic downloading * ~~1.1.44~~ * 1.1.45 * 1.1.46 +* 1.1.47 ## Using 1. Make sure `SpotifyKeyDumperInjector.exe` and `SpotifyKeyDumper.dll` are located in the same folder as Spotify (`Spotify.exe`). diff --git a/SpotifyKeyDumper/Hooks.cpp b/SpotifyKeyDumper/Hooks.cpp index 9408184..a1dd91c 100644 --- a/SpotifyKeyDumper/Hooks.cpp +++ b/SpotifyKeyDumper/Hooks.cpp @@ -24,12 +24,17 @@ 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; + std::string authToken = std::string(); std::string keyStr = std::string(); std::string trackUriStr = std::string(); __int64 newPosition = 0; bool signalled = false; +int destKeyPtr = 0; +char* keyBuffer_v47; int __cdecl keyToLE_hook_v25(unsigned int* dest, int* key, int bits) { @@ -160,6 +165,7 @@ int* __fastcall log_hook_v45(void* This, void* _EDX, int a2, int a3, void* a4, c return log_v45_hook(This, a2, a3, a4, classStr, a6, logThing); } +std::string lastKey = std::string(); void __fastcall fileIdWriter_hook_v45(void* This, void* _EDX, int* a2) { // [[ebp+8]+28] @@ -167,10 +173,11 @@ void __fastcall fileIdWriter_hook_v45(void* This, void* _EDX, int* a2) //std::cout << "fileId: " << fileId << std::endl << std::endl; - if (signalled) + if (signalled && lastKey.compare(keyStr) != 0) { //std::cout << "signalled = false" << std::endl; signalled = false; + lastKey = keyStr; std::thread t2(Utils::DownloadSong, std::string(fileId), trackUriStr, keyStr, authToken); t2.detach(); } @@ -182,8 +189,6 @@ int signalEmitterInitCount = 0; const int signalEmitterRequired = 3; void __fastcall signalEmitter_hook_v45(void* This, void* _EDX, int a1, int a2) { - //std::cout << "signalEmitter!!!" << std::endl << std::endl; - // Required in order to guarentee accurate data needed if (signalEmitterInitCount < signalEmitterRequired) signalEmitterInitCount++; @@ -196,6 +201,65 @@ void __fastcall signalEmitter_hook_v45(void* This, void* _EDX, int a1, int a2) 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; + } +} + +__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 + } +} + char* GetKeyFuncAddrV26() { BYTE ref_v19 = 0x55; @@ -282,5 +346,16 @@ void Hooks::Init() 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 /*0x015337CD*/, (char*)keyDecoder_hook_v47, 7 /*6*/); + 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); + break; } } \ No newline at end of file