diff --git a/README.md b/README.md index 58d5cd2..5eb64d5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ They are used to develop [librespot](https://github.com/plietar/librespot), an o The `spotify-dump` tool intercepts packets by the Spotify desktop client before they are encrypted or after they are decrypted. It works by patching the `shn_encrypt` and `shn_decrypt` routines of the client at run time. -This currently only works on 64 bit OS X/Linux. +##### MacOS/Linux 64 bit To use, quit the Spotify application, and execute the `dump.sh` script with the path the spotify binary. @@ -14,6 +14,13 @@ To use, quit the Spotify application, and execute the `dump.sh` script with the ~/spotify-analyze/dump> ./dump.sh /Applications/Spotify.app/Contents/MacOS/Spotify ``` +###### Windows 64 bit + +Build the solution and make sure `injector.exe` and `core.dll` are located in the same folder as `Spotify.exe`. + +To use, quit the Spotify application, execute the `injector.exe` and then Spotify app. + + This will produce a `dump.pcap` in the current directory, which can be analyzed by the `spotify-dissect` tool. ## spotify-dump-http diff --git a/dump-win/core/core.vcxproj b/dump-win/core/core.vcxproj new file mode 100644 index 0000000..329d571 --- /dev/null +++ b/dump-win/core/core.vcxproj @@ -0,0 +1,164 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {1fb90cce-1dd3-452b-88fa-e29f616b6a24} + core + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;CORE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;CORE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;CORE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;CORE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp20 + + + Windows + true + true + true + false + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + \ No newline at end of file diff --git a/dump-win/core/core.vcxproj.filters b/dump-win/core/core.vcxproj.filters new file mode 100644 index 0000000..01cf461 --- /dev/null +++ b/dump-win/core/core.vcxproj.filters @@ -0,0 +1,51 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/dump-win/core/core.vcxproj.user b/dump-win/core/core.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/dump-win/core/core.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dump-win/core/dllmain.cpp b/dump-win/core/dllmain.cpp new file mode 100644 index 0000000..11ac0bc --- /dev/null +++ b/dump-win/core/dllmain.cpp @@ -0,0 +1,133 @@ +#include "pch.h" + +#include "pcap.h" +#include "shn.h" +#include "utils.h" + +// 48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 +// EC 20 8B ?? CC 00 00 00 +static const char *pattern = + "\x48\x89\x5c\x24\x08\x48\x89\x6c\x24\x10\x48\x89\x74\x24\x18\x57\x41\x54" + "\x41\x55\x41\x56\x41\x57\x48\x83\xec\x20\x8b\x00\xcc\x00\x00\x00"; + +static const char *mask = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx?xxxx"; + +#define DIRECTION_SEND 0 +#define DIRECTION_RECV 1 + +static HANDLE dump_fd; + +typedef void(__cdecl *shn_decrypt_t)(shn_ctx *, UCHAR *, int); +shn_decrypt_t shn_decrypt_stub = nullptr; + +typedef void(__cdecl *shn_encrypt_t)(shn_ctx *, UCHAR *, int); +shn_encrypt_t shn_encrypt_stub = nullptr; + +int gettimeofday(struct timeval *tp, struct timezone *tzp) { + // note: some broken versions only have 8 trailing zero's, the correct epoch + // has 9 trailing zero's this magic number is the number of 100 nanosecond + // intervals since january 1, 1601 (utc) until 00:00:00 january 1, 1970 + static const uint64_t epoch = ((uint64_t)116444736000000000ull); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + time = ((uint64_t)file_time.dwLowDateTime); + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long)((time - epoch) / 10000000l); + tp->tv_usec = (long)(system_time.wMilliseconds * 1000); + return 0; +} + +void __cdecl shn_decrypt_proxy(shn_ctx *c, UCHAR *buf, int nBytes) { + shn_decrypt(c, buf, nBytes); + +#pragma pack(push, 1) + static struct { + uint8_t cmd; + uint16_t length; + } header = {0, 0}; +#pragma pack(pop) + + if (header.cmd == 0) { + if (nBytes == 3) + memcpy(&header, buf, 3); + } else { + if (nBytes == ntohs(header.length)) { + struct timeval tv; + gettimeofday(&tv, NULL); + pcap_write_packet_header(dump_fd, &tv, 4 + nBytes); + + uint8_t direction = DIRECTION_RECV; + WriteFile(dump_fd, &direction, 1, NULL, NULL); + WriteFile(dump_fd, &header, 3, NULL, NULL); + WriteFile(dump_fd, buf, nBytes, NULL, NULL); + } + + header.cmd = 0; + } +} + +void __cdecl shn_encrypt_proxy(shn_ctx *c, UCHAR *buf, int nBytes) { + struct timeval tv; + gettimeofday(&tv, NULL); + pcap_write_packet_header(dump_fd, &tv, 1 + nBytes); + + uint8_t direction = DIRECTION_SEND; + WriteFile(dump_fd, &direction, 1, NULL, NULL); + WriteFile(dump_fd, buf, nBytes, NULL, NULL); + + shn_encrypt(c, buf, nBytes); +} + +DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter) { + MODULEINFO mInfo = utils::GetModuleInfo(NULL); + + auto shn_decrypt = utils::FindPattern( + pattern, mask, (char *)mInfo.lpBaseOfDll, mInfo.SizeOfImage); + + if (shn_decrypt == nullptr) { + MessageBoxA(NULL, "shn_decrypt is nullptr", "error", MB_ICONERROR); + return 1; + } + + char *newPos = (char *)(shn_decrypt + strlen(mask)); + + uintptr_t newSize = mInfo.SizeOfImage - + ((uintptr_t)shn_decrypt - (uintptr_t)mInfo.lpBaseOfDll) - + strlen(mask); + + auto shn_encrypt = utils::FindPattern(pattern, mask, newPos, newSize); + + if (shn_decrypt == nullptr) { + MessageBoxA(NULL, "shn_encrypt is nullptr", "error", MB_ICONERROR); + return 1; + } + + dump_fd = CreateFileA("dump.pcap", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + + pcap_write_header(dump_fd, PCAP_DLT_USER0); + + shn_encrypt_stub = (shn_encrypt_t)utils::InstallHook( + (void *)shn_encrypt, (void *)shn_encrypt_proxy); + + shn_decrypt_stub = (shn_decrypt_t)utils::InstallHook( + (void *)shn_decrypt, (void *)shn_decrypt_proxy); + + return 0; +}; + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, + LPVOID lpReserved) { + + if (ul_reason_for_call == DLL_PROCESS_ATTACH) { + CreateThread(NULL, 0, &ThreadProc, NULL, 0, NULL); + } + + return TRUE; +} diff --git a/dump-win/core/framework.h b/dump-win/core/framework.h new file mode 100644 index 0000000..5a3602b --- /dev/null +++ b/dump-win/core/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include diff --git a/dump-win/core/pcap.cpp b/dump-win/core/pcap.cpp new file mode 100644 index 0000000..8f74089 --- /dev/null +++ b/dump-win/core/pcap.cpp @@ -0,0 +1,34 @@ +#include "pch.h" + +#include "pcap.h" + +void pcap_write_header(HANDLE hFile, uint32_t network) { + pcap_hdr_t hdr = { + .magic_number = PCAP_MAGIC, + .version_major = PCAP_MAJOR, + .version_minor = PCAP_MINOR, + .thiszone = 0, + .sigfigs = 0, + .snaplen = 65535, + .network = 12, + }; + + WriteFile(hFile, &hdr, sizeof(hdr), NULL, NULL); +} + +void pcap_write_packet_header(HANDLE hFile, const struct timeval *tv, + uint32_t length) { + pcaprec_hdr_t hdr = { + .ts_sec = 0, + .ts_usec = 0, + .incl_len = length, + .orig_len = length, + }; + + if (tv != NULL) { + hdr.ts_sec = tv->tv_sec; + hdr.ts_usec = tv->tv_usec; + } + + WriteFile(hFile, &hdr, sizeof(hdr), NULL, NULL); +} diff --git a/dump-win/core/pcap.h b/dump-win/core/pcap.h new file mode 100644 index 0000000..c767735 --- /dev/null +++ b/dump-win/core/pcap.h @@ -0,0 +1,33 @@ +#ifndef PCAP_H +#define PCAP_H + +#include "pch.h" + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_MAJOR 2 +#define PCAP_MINOR 4 + +#define PCAP_DLT_USER0 147 + +typedef struct { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +} pcap_hdr_t; + +typedef struct { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +} pcaprec_hdr_t; + +void pcap_write_header(HANDLE hFile, uint32_t network); +void pcap_write_packet_header(HANDLE hFile, const struct timeval *tv, + uint32_t length); + +#endif diff --git a/dump-win/core/pch.cpp b/dump-win/core/pch.cpp new file mode 100644 index 0000000..00df68a --- /dev/null +++ b/dump-win/core/pch.cpp @@ -0,0 +1,6 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for +// compilation to succeed. diff --git a/dump-win/core/pch.h b/dump-win/core/pch.h new file mode 100644 index 0000000..0a34cc1 --- /dev/null +++ b/dump-win/core/pch.h @@ -0,0 +1,28 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for +// future builds. This also affects IntelliSense performance, including code +// completion and many code browsing features. However, files listed here are +// ALL re-compiled if any one of them is updated between builds. Do not add +// files here that you will be updating frequently as this negates the +// performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#pragma comment(lib, "Ws2_32.lib") + +#endif // PCH_H diff --git a/dump-win/core/shn.cpp b/dump-win/core/shn.cpp new file mode 100644 index 0000000..4ffe3d5 --- /dev/null +++ b/dump-win/core/shn.cpp @@ -0,0 +1,467 @@ +#include "pch.h" + +/* $Id: shn.c 182 2009-03-12 08:21:53Z zagor $ */ +/* Shannon: Shannon stream cipher and MAC -- reference implementation */ + +/* +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND AGAINST +INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* interface */ +#include +#include +#include "shn.h" + +/* + * FOLD is how many register cycles need to be performed after combining the + * last byte of key and non-linear feedback, before every byte depends on every + * byte of the key. This depends on the feedback and nonlinear functions, and + * on where they are combined into the register. Making it same as the + * register length is a safe and conservative choice. + */ +#define FOLD N /* how many iterations of folding to do */ +#define INITKONST 0x6996c53a /* value of KONST to use during key loading */ +#define KEYP 13 /* where to insert key/MAC/counter words */ + +/* some useful macros -- machine independent little-endian */ +#define Byte(x,i) ((UCHAR)(((x) >> (8*(i))) & 0xFF)) +#define BYTE2WORD(b) ( \ + (((WORD)(b)[3] & 0xFF)<<24) | \ + (((WORD)(b)[2] & 0xFF)<<16) | \ + (((WORD)(b)[1] & 0xFF)<<8) | \ + (((WORD)(b)[0] & 0xFF)) \ +) +#define WORD2BYTE(w, b) { \ + (b)[3] = Byte(w,3); \ + (b)[2] = Byte(w,2); \ + (b)[1] = Byte(w,1); \ + (b)[0] = Byte(w,0); \ +} +#define XORWORD(w, b) { \ + (b)[3] ^= Byte(w,3); \ + (b)[2] ^= Byte(w,2); \ + (b)[1] ^= Byte(w,1); \ + (b)[0] ^= Byte(w,0); \ +} + +/* Nonlinear transform (sbox) of a word. + * There are two slightly different combinations. + */ +static WORD sbox1 (WORD w) +{ + w ^= ROTL (w, 5) | ROTL (w, 7); + w ^= ROTL (w, 19) | ROTL (w, 22); + return w; +} + +static WORD sbox2 (WORD w) +{ + w ^= ROTL (w, 7) | ROTL (w, 22); + w ^= ROTL (w, 5) | ROTL (w, 19); + return w; +} + +/* cycle the contents of the register and calculate output word in c->sbuf. + */ +static void cycle (shn_ctx * c) +{ + WORD t; + int i; + + /* nonlinear feedback function */ + t = c->R[12] ^ c->R[13] ^ c->konst; + t = sbox1 (t) ^ ROTL (c->R[0], 1); + /* shift register */ + for (i = 1; i < N; ++i) + c->R[i - 1] = c->R[i]; + c->R[N - 1] = t; + t = sbox2 (c->R[2] ^ c->R[15]); + c->R[0] ^= t; + c->sbuf = t ^ c->R[8] ^ c->R[12]; +} + +/* The Shannon MAC function is modelled after the concepts of Phelix and SHA. + * Basically, words to be accumulated in the MAC are incorporated in two + * different ways: + * 1. They are incorporated into the stream cipher register at a place + * where they will immediately have a nonlinear effect on the state + * 2. They are incorporated into bit-parallel CRC-16 registers; the + * contents of these registers will be used in MAC finalization. + */ + +/* Accumulate a CRC of input words, later to be fed into MAC. + * This is actually 32 parallel CRC-16s, using the IBM CRC-16 + * polynomial x^16 + x^15 + x^2 + 1. + */ +static void crcfunc (shn_ctx * c, WORD i) +{ + WORD t; + int j; + + /* Accumulate CRC of input */ + t = c->CRC[0] ^ c->CRC[2] ^ c->CRC[15] ^ i; + for (j = 1; j < N; ++j) + c->CRC[j - 1] = c->CRC[j]; + c->CRC[N - 1] = t; +} + +/* Normal MAC word processing: do both stream register and CRC. + */ +static void macfunc (shn_ctx * c, WORD i) +{ + crcfunc (c, i); + c->R[KEYP] ^= i; +} + +/* initialise to known state + */ +static void shn_initstate (shn_ctx * c) +{ + int i; + + /* Register initialised to Fibonacci numbers; Counter zeroed. */ + c->R[0] = 1; + c->R[1] = 1; + for (i = 2; i < N; ++i) + c->R[i] = c->R[i - 1] + c->R[i - 2]; + c->konst = INITKONST; +} + +/* Save the current register state + */ +static void shn_savestate (shn_ctx * c) +{ + int i; + + for (i = 0; i < N; ++i) + c->initR[i] = c->R[i]; +} + +/* initialise to previously saved register state + */ +static void shn_reloadstate (shn_ctx * c) +{ + int i; + + for (i = 0; i < N; ++i) + c->R[i] = c->initR[i]; +} + +/* Initialise "konst" + */ +static void shn_genkonst (shn_ctx * c) +{ + c->konst = c->R[0]; +} + +/* Load key material into the register + */ +#define ADDKEY(k) \ + c->R[KEYP] ^= (k); + +/* extra nonlinear diffusion of register for key and MAC */ +static void shn_diffuse (shn_ctx * c) +{ + int i; + + for (i = 0; i < FOLD; ++i) + cycle (c); +} + +/* Common actions for loading key material + * Allow non-word-multiple key and nonce material. + * Note also initializes the CRC register as a side effect. + */ +static void shn_loadkey (shn_ctx * c, const UCHAR *key, int keylen) +{ + int i, j; + WORD k; + UCHAR xtra[4]; + + /* start folding in key */ + for (i = 0; i < (keylen & ~0x3); i += 4) { + k = BYTE2WORD (&key[i]); + ADDKEY (k); + cycle (c); + } + + /* if there were any extra key bytes, zero pad to a word */ + if (i < keylen) { + for (j = 0 /* i unchanged */ ; i < keylen; ++i) + xtra[j++] = key[i]; + for ( /* j unchanged */ ; j < 4; ++j) + xtra[j] = 0; + k = BYTE2WORD (xtra); + ADDKEY (k); + cycle (c); + } + + /* also fold in the length of the key */ + ADDKEY (keylen); + cycle (c); + + /* save a copy of the register */ + for (i = 0; i < N; ++i) + c->CRC[i] = c->R[i]; + + /* now diffuse */ + shn_diffuse (c); + + /* now xor the copy back -- makes key loading irreversible */ + for (i = 0; i < N; ++i) + c->R[i] ^= c->CRC[i]; +} + +/* Published "key" interface + */ +void shn_key (shn_ctx * c, const UCHAR *key, int keylen) +{ + shn_initstate (c); + shn_loadkey (c, key, keylen); + shn_genkonst (c); /* in case we proceed to stream generation */ + shn_savestate (c); + c->nbuf = 0; +} + +/* Published "IV" interface + */ +void shn_nonce (shn_ctx * c, const UCHAR *nonce, int noncelen) +{ + shn_reloadstate (c); + c->konst = INITKONST; + shn_loadkey (c, nonce, noncelen); + shn_genkonst (c); + c->nbuf = 0; +} + +/* XOR pseudo-random bytes into buffer + * Note: doesn't play well with MAC functions. + */ +void shn_stream (shn_ctx * c, UCHAR * buf, int nbytes) +{ + UCHAR *endbuf; + + /* handle any previously buffered bytes */ + while (c->nbuf != 0 && nbytes != 0) { + *buf++ ^= c->sbuf & 0xFF; + c->sbuf >>= 8; + c->nbuf -= 8; + --nbytes; + } + + /* handle whole words */ + endbuf = &buf[nbytes & ~((WORD) 0x03)]; + while (buf < endbuf) { + cycle (c); + XORWORD (c->sbuf, buf); + buf += 4; + } + + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + cycle (c); + c->nbuf = 32; + while (c->nbuf != 0 && nbytes != 0) { + *buf++ ^= c->sbuf & 0xFF; + c->sbuf >>= 8; + c->nbuf -= 8; + --nbytes; + } + } +} + +/* accumulate words into MAC without encryption + * Note that plaintext is accumulated for MAC. + */ +void shn_maconly (shn_ctx * c, const UCHAR * buf, int nbytes) +{ + UCHAR *endbuf; + + /* handle any previously buffered bytes */ + if (c->nbuf != 0) { + while (c->nbuf != 0 && nbytes != 0) { + c->mbuf ^= (*buf++) << (32 - c->nbuf); + c->nbuf -= 8; + --nbytes; + } + if (c->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + macfunc (c, c->mbuf); + } + + /* handle whole words */ + endbuf = (unsigned char*)&buf[nbytes & ~((WORD) 0x03)]; + while (buf < endbuf) { + cycle (c); + macfunc (c, BYTE2WORD (buf)); + buf += 4; + } + + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + cycle (c); + c->mbuf = 0; + c->nbuf = 32; + while (c->nbuf != 0 && nbytes != 0) { + c->mbuf ^= (*buf++) << (32 - c->nbuf); + c->nbuf -= 8; + --nbytes; + } + } +} + +/* Combined MAC and encryption. + * Note that plaintext is accumulated for MAC. + */ +void shn_encrypt (shn_ctx * c, UCHAR * buf, int nbytes) +{ + UCHAR *endbuf; + WORD t = 0; + + /* handle any previously buffered bytes */ + if (c->nbuf != 0) { + while (c->nbuf != 0 && nbytes != 0) { + c->mbuf ^= *buf << (32 - c->nbuf); + *buf ^= (c->sbuf >> (32 - c->nbuf)) & 0xFF; + ++buf; + c->nbuf -= 8; + --nbytes; + } + if (c->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + macfunc (c, c->mbuf); + } + + /* handle whole words */ + endbuf = &buf[nbytes & ~((WORD) 0x03)]; + while (buf < endbuf) { + cycle (c); + t = BYTE2WORD (buf); + macfunc (c, t); + t ^= c->sbuf; + WORD2BYTE (t, buf); + buf += 4; + } + + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + cycle (c); + c->mbuf = 0; + c->nbuf = 32; + while (c->nbuf != 0 && nbytes != 0) { + c->mbuf ^= *buf << (32 - c->nbuf); + *buf ^= (c->sbuf >> (32 - c->nbuf)) & 0xFF; + ++buf; + c->nbuf -= 8; + --nbytes; + } + } +} + +/* Combined MAC and decryption. + * Note that plaintext is accumulated for MAC. + */ +void shn_decrypt (shn_ctx * c, UCHAR * buf, int nbytes) +{ + UCHAR *endbuf; + WORD t = 0; + + /* handle any previously buffered bytes */ + if (c->nbuf != 0) { + while (c->nbuf != 0 && nbytes != 0) { + *buf ^= (c->sbuf >> (32 - c->nbuf)) & 0xFF; + c->mbuf ^= *buf << (32 - c->nbuf); + ++buf; + c->nbuf -= 8; + --nbytes; + } + if (c->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + macfunc (c, c->mbuf); + } + + /* handle whole words */ + endbuf = &buf[nbytes & ~((WORD) 0x03)]; + while (buf < endbuf) { + cycle (c); + t = BYTE2WORD (buf) ^ c->sbuf; + macfunc (c, t); + WORD2BYTE (t, buf); + buf += 4; + } + + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + cycle (c); + c->mbuf = 0; + c->nbuf = 32; + while (c->nbuf != 0 && nbytes != 0) { + *buf ^= (c->sbuf >> (32 - c->nbuf)) & 0xFF; + c->mbuf ^= *buf << (32 - c->nbuf); + ++buf; + c->nbuf -= 8; + --nbytes; + } + } +} + +/* Having accumulated a MAC, finish processing and return it. + * Note that any unprocessed bytes are treated as if + * they were encrypted zero bytes, so plaintext (zero) is accumulated. + */ +void shn_finish (shn_ctx * c, UCHAR * buf, int nbytes) +{ + int i; + + /* handle any previously buffered bytes */ + if (c->nbuf != 0) { + /* LFSR already cycled */ + macfunc (c, c->mbuf); + } + + /* perturb the MAC to mark end of input. + * Note that only the stream register is updated, not the CRC. This is an + * action that can't be duplicated by passing in plaintext, hence + * defeating any kind of extension attack. + */ + cycle (c); + ADDKEY (INITKONST ^ (c->nbuf << 3)); + c->nbuf = 0; + + /* now add the CRC to the stream register and diffuse it */ + for (i = 0; i < N; ++i) + c->R[i] ^= c->CRC[i]; + shn_diffuse (c); + + /* produce output from the stream buffer */ + while (nbytes > 0) { + cycle (c); + if (nbytes >= 4) { + WORD2BYTE (c->sbuf, buf); + nbytes -= 4; + buf += 4; + } + else { + for (i = 0; i < nbytes; ++i) + buf[i] = Byte (c->sbuf, i); + break; + } + } +} diff --git a/dump-win/core/shn.h b/dump-win/core/shn.h new file mode 100644 index 0000000..1233634 --- /dev/null +++ b/dump-win/core/shn.h @@ -0,0 +1,69 @@ +/* $Id: shn.h 182 2009-03-12 08:21:53Z zagor $ */ +/* Shannon: Shannon stream cipher and MAC header files */ + +/* +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND AGAINST +INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _SHN_DEFINED +#define _SHN_DEFINED 1 + +#include + +#if __STDC_VERSION__ >= 199901 +#include +#endif + +#define N 16 +#define WORDSIZE 32 +#define UCHAR unsigned char + +#if __STDC_VERSION__ >= 199901 +#define WORD uint32_t +#define WORD_MAX UINT32_MAX +#elif UINT_MAX >= 0xffffffff +#define WORD unsigned int +#define WORD_MAX UINT_MAX +#else +#define WORD unsigned long +#define WORD_MAX ULONG_MAX +#endif + +#if WORD_MAX == 0xffffffff +#define ROTL(w,x) (((w) << (x))|((w) >> (32 - (x)))) +#define ROTR(w,x) (((w) >> (x))|((w) << (32 - (x)))) +#else +#define ROTL(w,x) (((w) << (x))|(((w) & 0xffffffff) >> (32 - (x)))) +#define ROTR(w,x) ((((w) & 0xffffffff) >> (x))|((w) << (32 - (x)))) +#endif + +typedef struct +{ + WORD R[N]; /* Working storage for the shift register */ + WORD CRC[N]; /* Working storage for CRC accumulation */ + WORD initR[N]; /* saved register contents */ + WORD konst; /* key dependent semi-constant */ + WORD sbuf; /* encryption buffer */ + WORD mbuf; /* partial word MAC buffer */ + int nbuf; /* number of part-word stream bits buffered */ +} shn_ctx; + +/* interface definitions */ +void shn_key (shn_ctx * c, const UCHAR *key, int keylen); /* set key */ +void shn_nonce (shn_ctx * c, const UCHAR *nonce, int nlen); /* set Init Vector */ +void shn_stream (shn_ctx * c, UCHAR * buf, int nbytes); /* stream cipher */ +void shn_maconly (shn_ctx * c, const UCHAR * buf, int nbytes); /* accumulate MAC */ +void shn_encrypt (shn_ctx * c, UCHAR * buf, int nbytes); /* encrypt + MAC */ +void shn_decrypt (shn_ctx * c, UCHAR * buf, int nbytes); /* decrypt + MAC */ +void shn_finish (shn_ctx * c, UCHAR * buf, int nbytes); /* finalise MAC */ +#endif /* _SHN_DEFINED */ diff --git a/dump-win/core/utils.cpp b/dump-win/core/utils.cpp new file mode 100644 index 0000000..a2e9a71 --- /dev/null +++ b/dump-win/core/utils.cpp @@ -0,0 +1,138 @@ +#include "pch.h" + +#include "utils.h" + +void *utils::AllocatePageNearAddress(void *targetAddr) { + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + const uint64_t PAGE_SIZE = sysInfo.dwPageSize; + + uint64_t startAddr = + (uint64_t(targetAddr) & + ~(PAGE_SIZE - 1)); // round down to nearest page boundary + uint64_t minAddr = min(startAddr - 0x7FFFFF00, + (uint64_t)sysInfo.lpMinimumApplicationAddress); + uint64_t maxAddr = max(startAddr + 0x7FFFFF00, + (uint64_t)sysInfo.lpMaximumApplicationAddress); + + uint64_t startPage = (startAddr - (startAddr % PAGE_SIZE)); + + uint64_t pageOffset = 1; + while (1) { + uint64_t byteOffset = pageOffset * PAGE_SIZE; + uint64_t highAddr = startPage + byteOffset; + uint64_t lowAddr = (startPage > byteOffset) ? startPage - byteOffset : 0; + + bool needsExit = highAddr > maxAddr && lowAddr < minAddr; + + if (highAddr < maxAddr) { + void *outAddr = + VirtualAlloc((void *)highAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE); + if (outAddr) + return outAddr; + } + + if (lowAddr > minAddr) { + void *outAddr = + VirtualAlloc((void *)lowAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE); + if (outAddr != nullptr) + return outAddr; + } + + pageOffset++; + + if (needsExit) { + break; + } + } + + return nullptr; +} + +void utils::WriteAbsoluteJump64(void *absJumpMemory, void *addrToJumpTo) { + uint8_t absJumpInstructions[] = { + 0x49, 0xBA, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr + 0x41, 0xFF, 0xE2 // jmp r10 + }; + + uint64_t addrToJumpTo64 = (uint64_t)addrToJumpTo; + memcpy(&absJumpInstructions[2], &addrToJumpTo64, sizeof(addrToJumpTo64)); + memcpy(absJumpMemory, absJumpInstructions, sizeof(absJumpInstructions)); +} + +void *utils::InstallHook(void *func2hook, void *payloadFunction) { + // Store the original function's bytes + uint8_t originalBytes[5]; + memcpy(originalBytes, func2hook, 5); + + // Store the original function pointer + void *originalFunction = func2hook; + + void *relayFuncMemory = AllocatePageNearAddress(func2hook); + WriteAbsoluteJump64(relayFuncMemory, + payloadFunction); // write relay func instructions + + // now that the relay function is built, we need to install the E9 jump into + // the target func, this will jump to the relay function + DWORD oldProtect; + VirtualProtect(func2hook, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); + + // 32 bit relative jump opcode is E9, takes 1 32 bit operand for jump offset + uint8_t jmpInstruction[5] = {0xE9, 0x0, 0x0, 0x0, 0x0}; + + // to fill out the last 4 bytes of jmpInstruction, we need the offset between + // the relay function and the instruction immediately AFTER the jmp + // instruction + const uint64_t relAddr = (uint64_t)relayFuncMemory - + ((uint64_t)func2hook + sizeof(jmpInstruction)); + memcpy(jmpInstruction + 1, &relAddr, 4); + + // install the hook + memcpy(func2hook, jmpInstruction, sizeof(jmpInstruction)); + + // Return the original function pointer + return originalFunction; +} + +const char *utils::FindPattern(const char *pattern, const char *mask, + const char *begin, size_t size) { + if (!pattern || !mask || !begin) { + return nullptr; + } + + size_t patternLen = strlen(mask); + if (patternLen > size) { + return nullptr; + } + + for (size_t i = 0; i <= size - patternLen; i++) { + bool found = true; + + for (size_t j = 0; j < patternLen; j++) { + if (mask[j] != '?' && pattern[j] != *(begin + i + j)) { + found = false; + break; + } + } + + if (found) { + return begin + i; + } + } + + return nullptr; +} + +MODULEINFO utils::GetModuleInfo(char *szModule) { + MODULEINFO mInfo = {0}; + HMODULE hModule = GetModuleHandleA(szModule); + if (hModule != 0) { + GetModuleInformation(GetCurrentProcess(), hModule, &mInfo, + sizeof(MODULEINFO)); + } + + return mInfo; +} diff --git a/dump-win/core/utils.h b/dump-win/core/utils.h new file mode 100644 index 0000000..b81b284 --- /dev/null +++ b/dump-win/core/utils.h @@ -0,0 +1,13 @@ +#pragma once +#include "pch.h" + +class utils { +public: + static void *AllocatePageNearAddress(void *targetAddr); + static void WriteAbsoluteJump64(void *absJumpMemory, void *addrToJumpTo); + static void *InstallHook(void *func2hook, void *payloadFunction); + + static MODULEINFO GetModuleInfo(char *szModule); + static const char *FindPattern(const char *pattern, const char *mask, + const char *begin, size_t size); +}; diff --git a/dump-win/dump-win.sln b/dump-win/dump-win.sln new file mode 100644 index 0000000..e8e8c25 --- /dev/null +++ b/dump-win/dump-win.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32505.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "core\core.vcxproj", "{1FB90CCE-1DD3-452B-88FA-E29F616B6A24}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "injector", "injector\injector.vcxproj", "{991E683A-E927-4426-90F7-8C599A96F6CC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1FB90CCE-1DD3-452B-88FA-E29F616B6A24}.Debug|x64.ActiveCfg = Debug|x64 + {1FB90CCE-1DD3-452B-88FA-E29F616B6A24}.Debug|x64.Build.0 = Debug|x64 + {1FB90CCE-1DD3-452B-88FA-E29F616B6A24}.Debug|x86.ActiveCfg = Debug|Win32 + {1FB90CCE-1DD3-452B-88FA-E29F616B6A24}.Debug|x86.Build.0 = Debug|Win32 + {1FB90CCE-1DD3-452B-88FA-E29F616B6A24}.Release|x64.ActiveCfg = Release|x64 + {1FB90CCE-1DD3-452B-88FA-E29F616B6A24}.Release|x64.Build.0 = Release|x64 + {1FB90CCE-1DD3-452B-88FA-E29F616B6A24}.Release|x86.ActiveCfg = Release|Win32 + {1FB90CCE-1DD3-452B-88FA-E29F616B6A24}.Release|x86.Build.0 = Release|Win32 + {991E683A-E927-4426-90F7-8C599A96F6CC}.Debug|x64.ActiveCfg = Debug|x64 + {991E683A-E927-4426-90F7-8C599A96F6CC}.Debug|x64.Build.0 = Debug|x64 + {991E683A-E927-4426-90F7-8C599A96F6CC}.Debug|x86.ActiveCfg = Debug|Win32 + {991E683A-E927-4426-90F7-8C599A96F6CC}.Debug|x86.Build.0 = Debug|Win32 + {991E683A-E927-4426-90F7-8C599A96F6CC}.Release|x64.ActiveCfg = Release|x64 + {991E683A-E927-4426-90F7-8C599A96F6CC}.Release|x64.Build.0 = Release|x64 + {991E683A-E927-4426-90F7-8C599A96F6CC}.Release|x86.ActiveCfg = Release|Win32 + {991E683A-E927-4426-90F7-8C599A96F6CC}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0A1C2237-B2F3-4ABF-853F-AAA156A3CC4D} + EndGlobalSection +EndGlobal diff --git a/dump-win/injector/injector.cpp b/dump-win/injector/injector.cpp new file mode 100644 index 0000000..2efdc86 --- /dev/null +++ b/dump-win/injector/injector.cpp @@ -0,0 +1,67 @@ +#include + +#include + +#include + +static const char *DLL_FILE = "core.dll"; +static const wchar_t *TARGET = L"Spotify.exe"; + +DWORD GetProcId(const wchar_t *procName) { + DWORD procId = 0; + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + + if (hSnap != INVALID_HANDLE_VALUE) { + PROCESSENTRY32 procEntry; + procEntry.dwSize = sizeof(procEntry); + + if (Process32First(hSnap, &procEntry)) { + do { + if (!wcscmp(procEntry.szExeFile, procName)) { + procId = procEntry.th32ProcessID; + break; + } + } while (Process32Next(hSnap, &procEntry)); + } + } + + CloseHandle(hSnap); + return procId; +} + +int main() { + std::wcout << "Attempting to inject \"" << DLL_FILE << "\" into \"" + << std::wstring(TARGET) << "\"..." << std::endl; + + DWORD procId = 0; + while (!procId) { + procId = GetProcId(TARGET); + Sleep(50); + } + + // Possibly injecting too fast into Spotify.exe resulting in an error, so + // delay? + Sleep(50); + + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, procId); + + if (hProcess && hProcess != INVALID_HANDLE_VALUE) { + void *loc = VirtualAllocEx(hProcess, 0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); + + WriteProcessMemory(hProcess, loc, DLL_FILE, strlen(DLL_FILE) + 1, 0); + + HANDLE hThread = CreateRemoteThread( + hProcess, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, loc, 0, 0); + + if (hThread) { + CloseHandle(hThread); + } + } + + if (hProcess) { + CloseHandle(hProcess); + } + + std::cout << "Done" << std::endl; +} diff --git a/dump-win/injector/injector.vcxproj b/dump-win/injector/injector.vcxproj new file mode 100644 index 0000000..cd4b42a --- /dev/null +++ b/dump-win/injector/injector.vcxproj @@ -0,0 +1,135 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {991e683a-e927-4426-90f7-8c599a96f6cc} + injector + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/dump-win/injector/injector.vcxproj.filters b/dump-win/injector/injector.vcxproj.filters new file mode 100644 index 0000000..9ce493a --- /dev/null +++ b/dump-win/injector/injector.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/dump-win/injector/injector.vcxproj.user b/dump-win/injector/injector.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/dump-win/injector/injector.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file