From 3a8492b6514cb2808124b88c27c859bf91c7bcd1 Mon Sep 17 00:00:00 2001 From: xhacker-zzz <959220793@qq.com> Date: Mon, 21 Nov 2022 01:07:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0WASM=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=BA=90=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/KgmWasm/CMakeLists.txt | 65 +++++++++ src/KgmWasm/KgmWasm.cpp | 20 +++ src/KgmWasm/KgmWasm.h | 18 +++ src/KgmWasm/README.md | 9 ++ src/KgmWasm/build-wasm | 41 ++++++ src/KgmWasm/kgm.hpp | 112 ++++++++++++++ src/QmcWasm/CMakeLists.txt | 65 +++++++++ src/QmcWasm/QmcWasm.cpp | 57 ++++++++ src/QmcWasm/QmcWasm.h | 23 +++ src/QmcWasm/README.md | 9 ++ src/QmcWasm/TencentTea.hpp | 289 ++++++++++++++++++++++++++++++++++++ src/QmcWasm/base64.hpp | 207 ++++++++++++++++++++++++++ src/QmcWasm/build-wasm | 41 ++++++ src/QmcWasm/qmc.hpp | 233 +++++++++++++++++++++++++++++ src/QmcWasm/qmc_cipher.hpp | 290 +++++++++++++++++++++++++++++++++++++ src/QmcWasm/qmc_key.hpp | 217 +++++++++++++++++++++++++++ 16 files changed, 1696 insertions(+) create mode 100644 src/KgmWasm/CMakeLists.txt create mode 100644 src/KgmWasm/KgmWasm.cpp create mode 100644 src/KgmWasm/KgmWasm.h create mode 100644 src/KgmWasm/README.md create mode 100644 src/KgmWasm/build-wasm create mode 100644 src/KgmWasm/kgm.hpp create mode 100644 src/QmcWasm/CMakeLists.txt create mode 100644 src/QmcWasm/QmcWasm.cpp create mode 100644 src/QmcWasm/QmcWasm.h create mode 100644 src/QmcWasm/README.md create mode 100644 src/QmcWasm/TencentTea.hpp create mode 100644 src/QmcWasm/base64.hpp create mode 100644 src/QmcWasm/build-wasm create mode 100644 src/QmcWasm/qmc.hpp create mode 100644 src/QmcWasm/qmc_cipher.hpp create mode 100644 src/QmcWasm/qmc_key.hpp diff --git a/src/KgmWasm/CMakeLists.txt b/src/KgmWasm/CMakeLists.txt new file mode 100644 index 0000000..1014b3b --- /dev/null +++ b/src/KgmWasm/CMakeLists.txt @@ -0,0 +1,65 @@ +# CMakeList.txt : CMake project for KgmWasm, include source and define +# project specific logic here. +# +cmake_minimum_required (VERSION 3.8) + +project ("KgmWasm") + +set(CMAKE_CXX_STANDARD 14) + +include_directories( + $ +) + +# Add source to this project's executable. +set(RUNTIME_METHODS_LIST + getValue + writeArrayToMemory + UTF8ToString +) +list(JOIN RUNTIME_METHODS_LIST "," RUNTIME_METHODS) + +set(EMSCRIPTEN_FLAGS + "--bind" + "-s NO_DYNAMIC_EXECUTION=1" + "-s MODULARIZE=1" + "-s EXPORT_NAME=KgmCryptoModule" + "-s EXPORTED_RUNTIME_METHODS=${RUNTIME_METHODS}" +) +set(EMSCRIPTEN_LEGACY_FLAGS + ${EMSCRIPTEN_FLAGS} + "-s WASM=0" + "--memory-init-file 0" +) +set(EMSCRIPTEN_WASM_BUNDLE_FLAGS + ${EMSCRIPTEN_FLAGS} + "-s SINGLE_FILE=1" +) + +list(JOIN EMSCRIPTEN_FLAGS " " EMSCRIPTEN_FLAGS_STR) +list(JOIN EMSCRIPTEN_LEGACY_FLAGS " " EMSCRIPTEN_LEGACY_FLAGS_STR) +list(JOIN EMSCRIPTEN_WASM_BUNDLE_FLAGS " " EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR) + +# Define projects config +set(WASM_SOURCES + "KgmWasm.cpp" +) + +add_executable(KgmWasm ${WASM_SOURCES}) +set_target_properties( + KgmWasm + PROPERTIES LINK_FLAGS ${EMSCRIPTEN_FLAGS_STR} +) + +add_executable(KgmWasmBundle ${WASM_SOURCES}) +set_target_properties( + KgmWasmBundle + PROPERTIES LINK_FLAGS ${EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR} +) + +add_executable(KgmLegacy ${WASM_SOURCES}) +set_target_properties( + KgmLegacy + PROPERTIES LINK_FLAGS ${EMSCRIPTEN_LEGACY_FLAGS_STR} +) + diff --git a/src/KgmWasm/KgmWasm.cpp b/src/KgmWasm/KgmWasm.cpp new file mode 100644 index 0000000..7901fed --- /dev/null +++ b/src/KgmWasm/KgmWasm.cpp @@ -0,0 +1,20 @@ +// KgmWasm.cpp : Defines the entry point for the application. +// + +#include "KgmWasm.h" + +#include "kgm.hpp" + +#include +#include + +size_t preDec(uintptr_t blob, size_t blobSize, std::string ext) +{ + return PreDec((uint8_t*)blob, blobSize, ext == "vpr"); +} + +void decBlob(uintptr_t blob, size_t blobSize, size_t offset) +{ + Decrypt((uint8_t*)blob, blobSize, offset); + return; +} diff --git a/src/KgmWasm/KgmWasm.h b/src/KgmWasm/KgmWasm.h new file mode 100644 index 0000000..0b1d7eb --- /dev/null +++ b/src/KgmWasm/KgmWasm.h @@ -0,0 +1,18 @@ +// KgmWasm.h : Include file for standard system include files, +// or project specific include files. + +#pragma once + +#include +#include + +namespace em = emscripten; + +size_t preDec(uintptr_t blob, size_t blobSize, std::string ext); +void decBlob(uintptr_t blob, size_t blobSize, size_t offset); + +EMSCRIPTEN_BINDINGS(QmcCrypto) +{ + em::function("preDec", &preDec, em::allow_raw_pointers()); + em::function("decBlob", &decBlob, em::allow_raw_pointers()); +} diff --git a/src/KgmWasm/README.md b/src/KgmWasm/README.md new file mode 100644 index 0000000..0ad5092 --- /dev/null +++ b/src/KgmWasm/README.md @@ -0,0 +1,9 @@ +# KgmWasm + +## 构建 + +在 Linux 环境下执行 `bash build-wasm` 即可构建。 + +## Build + +Linux environment required. Build wasm binary by execute `bash build-wasm`. diff --git a/src/KgmWasm/build-wasm b/src/KgmWasm/build-wasm new file mode 100644 index 0000000..7a9c12d --- /dev/null +++ b/src/KgmWasm/build-wasm @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -e + +pushd "$(realpath "$(dirname "$0")")" + +CURR_DIR="${PWD}" + +BUILD_TYPE="$1" +if [ -z "$BUILD_TYPE" ]; then + BUILD_TYPE=Release +fi + +mkdir -p build/wasm +if [ ! -d build/emsdk ]; then + git clone https://github.com/emscripten-core/emsdk.git build/emsdk +fi + +pushd build/emsdk +#git pull +./emsdk install 3.0.0 +./emsdk activate 3.0.0 +source ./emsdk_env.sh +popd # build/emsdk + +pushd build/wasm +emcmake cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ../.. +make -j +TARGET_FILES=" + KgmLegacy.js + KgmWasm.js + KgmWasm.wasm + KgmWasmBundle.js +" + +#mkdir -p "${CURR_DIR}/npm" +#cp $TARGET_FILES "${CURR_DIR}/npm/" +cp $TARGET_FILES "${CURR_DIR}/" +popd # build/wasm + +popd diff --git a/src/KgmWasm/kgm.hpp b/src/KgmWasm/kgm.hpp new file mode 100644 index 0000000..b3493e5 --- /dev/null +++ b/src/KgmWasm/kgm.hpp @@ -0,0 +1,112 @@ +#include + +std::vector VprHeader = { + 0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43, + 0x91, 0xAA, 0xBD, 0xD0, 0x7A, 0xF5, 0x36, 0x31 }; +std::vector KgmHeader = { + 0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B, + 0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14 }; +std::vector VprMaskDiff = { + 0x25, 0xDF, 0xE8, 0xA6, 0x75, 0x1E, 0x75, 0x0E, + 0x2F, 0x80, 0xF3, 0x2D, 0xB8, 0xB6, 0xE3, 0x11, 0x00 }; + +std::vector MaskV2; + +std::vector table1 = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x21, 0x01, 0x61, 0x01, 0x21, 0x01, 0xe1, 0x01, 0x21, 0x01, 0x61, 0x01, 0x21, 0x01, + 0xd2, 0x23, 0x02, 0x02, 0x42, 0x42, 0x02, 0x02, 0xc2, 0xc2, 0x02, 0x02, 0x42, 0x42, 0x02, 0x02, + 0xd3, 0xd3, 0x02, 0x03, 0x63, 0x43, 0x63, 0x03, 0xe3, 0xc3, 0xe3, 0x03, 0x63, 0x43, 0x63, 0x03, + 0x94, 0xb4, 0x94, 0x65, 0x04, 0x04, 0x04, 0x04, 0x84, 0x84, 0x84, 0x84, 0x04, 0x04, 0x04, 0x04, + 0x95, 0x95, 0x95, 0x95, 0x04, 0x05, 0x25, 0x05, 0xe5, 0x85, 0xa5, 0x85, 0xe5, 0x05, 0x25, 0x05, + 0xd6, 0xb6, 0x96, 0xb6, 0xd6, 0x27, 0x06, 0x06, 0xc6, 0xc6, 0x86, 0x86, 0xc6, 0xc6, 0x06, 0x06, + 0xd7, 0xd7, 0x97, 0x97, 0xd7, 0xd7, 0x06, 0x07, 0xe7, 0xc7, 0xe7, 0x87, 0xe7, 0xc7, 0xe7, 0x07, + 0x18, 0x38, 0x18, 0x78, 0x18, 0x38, 0x18, 0xe9, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x08, 0x09, 0x29, 0x09, 0x69, 0x09, 0x29, 0x09, + 0xda, 0x3a, 0x1a, 0x3a, 0x5a, 0x3a, 0x1a, 0x3a, 0xda, 0x2b, 0x0a, 0x0a, 0x4a, 0x4a, 0x0a, 0x0a, + 0xdb, 0xdb, 0x1b, 0x1b, 0x5b, 0x5b, 0x1b, 0x1b, 0xdb, 0xdb, 0x0a, 0x0b, 0x6b, 0x4b, 0x6b, 0x0b, + 0x9c, 0xbc, 0x9c, 0x7c, 0x1c, 0x3c, 0x1c, 0x7c, 0x9c, 0xbc, 0x9c, 0x6d, 0x0c, 0x0c, 0x0c, 0x0c, + 0x9d, 0x9d, 0x9d, 0x9d, 0x1d, 0x1d, 0x1d, 0x1d, 0x9d, 0x9d, 0x9d, 0x9d, 0x0c, 0x0d, 0x2d, 0x0d, + 0xde, 0xbe, 0x9e, 0xbe, 0xde, 0x3e, 0x1e, 0x3e, 0xde, 0xbe, 0x9e, 0xbe, 0xde, 0x2f, 0x0e, 0x0e, + 0xdf, 0xdf, 0x9f, 0x9f, 0xdf, 0xdf, 0x1f, 0x1f, 0xdf, 0xdf, 0x9f, 0x9f, 0xdf, 0xdf, 0x0e, 0x0f, + 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0xf1 +}; + +std::vector table2 = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x23, 0x01, 0x67, 0x01, 0x23, 0x01, 0xef, 0x01, 0x23, 0x01, 0x67, 0x01, 0x23, 0x01, + 0xdf, 0x21, 0x02, 0x02, 0x46, 0x46, 0x02, 0x02, 0xce, 0xce, 0x02, 0x02, 0x46, 0x46, 0x02, 0x02, + 0xde, 0xde, 0x02, 0x03, 0x65, 0x47, 0x65, 0x03, 0xed, 0xcf, 0xed, 0x03, 0x65, 0x47, 0x65, 0x03, + 0x9d, 0xbf, 0x9d, 0x63, 0x04, 0x04, 0x04, 0x04, 0x8c, 0x8c, 0x8c, 0x8c, 0x04, 0x04, 0x04, 0x04, + 0x9c, 0x9c, 0x9c, 0x9c, 0x04, 0x05, 0x27, 0x05, 0xeb, 0x8d, 0xaf, 0x8d, 0xeb, 0x05, 0x27, 0x05, + 0xdb, 0xbd, 0x9f, 0xbd, 0xdb, 0x25, 0x06, 0x06, 0xca, 0xca, 0x8e, 0x8e, 0xca, 0xca, 0x06, 0x06, + 0xda, 0xda, 0x9e, 0x9e, 0xda, 0xda, 0x06, 0x07, 0xe9, 0xcb, 0xe9, 0x8f, 0xe9, 0xcb, 0xe9, 0x07, + 0x19, 0x3b, 0x19, 0x7f, 0x19, 0x3b, 0x19, 0xe7, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x08, 0x09, 0x2b, 0x09, 0x6f, 0x09, 0x2b, 0x09, + 0xd7, 0x39, 0x1b, 0x39, 0x5f, 0x39, 0x1b, 0x39, 0xd7, 0x29, 0x0a, 0x0a, 0x4e, 0x4e, 0x0a, 0x0a, + 0xd6, 0xd6, 0x1a, 0x1a, 0x5e, 0x5e, 0x1a, 0x1a, 0xd6, 0xd6, 0x0a, 0x0b, 0x6d, 0x4f, 0x6d, 0x0b, + 0x95, 0xb7, 0x95, 0x7b, 0x1d, 0x3f, 0x1d, 0x7b, 0x95, 0xb7, 0x95, 0x6b, 0x0c, 0x0c, 0x0c, 0x0c, + 0x94, 0x94, 0x94, 0x94, 0x1c, 0x1c, 0x1c, 0x1c, 0x94, 0x94, 0x94, 0x94, 0x0c, 0x0d, 0x2f, 0x0d, + 0xd3, 0xb5, 0x97, 0xb5, 0xd3, 0x3d, 0x1f, 0x3d, 0xd3, 0xb5, 0x97, 0xb5, 0xd3, 0x2d, 0x0e, 0x0e, + 0xd2, 0xd2, 0x96, 0x96, 0xd2, 0xd2, 0x1e, 0x1e, 0xd2, 0xd2, 0x96, 0x96, 0xd2, 0xd2, 0x0e, 0x0f, + 0x00, 0x22, 0x00, 0x66, 0x00, 0x22, 0x00, 0xee, 0x00, 0x22, 0x00, 0x66, 0x00, 0x22, 0x00, 0xfe +}; + +std::vector MaskV2PreDef = { + 0xB8, 0xD5, 0x3D, 0xB2, 0xE9, 0xAF, 0x78, 0x8C, 0x83, 0x33, 0x71, 0x51, 0x76, 0xA0, 0xCD, 0x37, + 0x2F, 0x3E, 0x35, 0x8D, 0xA9, 0xBE, 0x98, 0xB7, 0xE7, 0x8C, 0x22, 0xCE, 0x5A, 0x61, 0xDF, 0x68, + 0x69, 0x89, 0xFE, 0xA5, 0xB6, 0xDE, 0xA9, 0x77, 0xFC, 0xC8, 0xBD, 0xBD, 0xE5, 0x6D, 0x3E, 0x5A, + 0x36, 0xEF, 0x69, 0x4E, 0xBE, 0xE1, 0xE9, 0x66, 0x1C, 0xF3, 0xD9, 0x02, 0xB6, 0xF2, 0x12, 0x9B, + 0x44, 0xD0, 0x6F, 0xB9, 0x35, 0x89, 0xB6, 0x46, 0x6D, 0x73, 0x82, 0x06, 0x69, 0xC1, 0xED, 0xD7, + 0x85, 0xC2, 0x30, 0xDF, 0xA2, 0x62, 0xBE, 0x79, 0x2D, 0x62, 0x62, 0x3D, 0x0D, 0x7E, 0xBE, 0x48, + 0x89, 0x23, 0x02, 0xA0, 0xE4, 0xD5, 0x75, 0x51, 0x32, 0x02, 0x53, 0xFD, 0x16, 0x3A, 0x21, 0x3B, + 0x16, 0x0F, 0xC3, 0xB2, 0xBB, 0xB3, 0xE2, 0xBA, 0x3A, 0x3D, 0x13, 0xEC, 0xF6, 0x01, 0x45, 0x84, + 0xA5, 0x70, 0x0F, 0x93, 0x49, 0x0C, 0x64, 0xCD, 0x31, 0xD5, 0xCC, 0x4C, 0x07, 0x01, 0x9E, 0x00, + 0x1A, 0x23, 0x90, 0xBF, 0x88, 0x1E, 0x3B, 0xAB, 0xA6, 0x3E, 0xC4, 0x73, 0x47, 0x10, 0x7E, 0x3B, + 0x5E, 0xBC, 0xE3, 0x00, 0x84, 0xFF, 0x09, 0xD4, 0xE0, 0x89, 0x0F, 0x5B, 0x58, 0x70, 0x4F, 0xFB, + 0x65, 0xD8, 0x5C, 0x53, 0x1B, 0xD3, 0xC8, 0xC6, 0xBF, 0xEF, 0x98, 0xB0, 0x50, 0x4F, 0x0F, 0xEA, + 0xE5, 0x83, 0x58, 0x8C, 0x28, 0x2C, 0x84, 0x67, 0xCD, 0xD0, 0x9E, 0x47, 0xDB, 0x27, 0x50, 0xCA, + 0xF4, 0x63, 0x63, 0xE8, 0x97, 0x7F, 0x1B, 0x4B, 0x0C, 0xC2, 0xC1, 0x21, 0x4C, 0xCC, 0x58, 0xF5, + 0x94, 0x52, 0xA3, 0xF3, 0xD3, 0xE0, 0x68, 0xF4, 0x00, 0x23, 0xF3, 0x5E, 0x0A, 0x7B, 0x93, 0xDD, + 0xAB, 0x12, 0xB2, 0x13, 0xE8, 0x84, 0xD7, 0xA7, 0x9F, 0x0F, 0x32, 0x4C, 0x55, 0x1D, 0x04, 0x36, + 0x52, 0xDC, 0x03, 0xF3, 0xF9, 0x4E, 0x42, 0xE9, 0x3D, 0x61, 0xEF, 0x7C, 0xB6, 0xB3, 0x93, 0x50, +}; + +uint8_t getMask(size_t pos) { + size_t offset = pos >> 4; + uint8_t value = 0; + while (offset >= 0x11) { + value ^= table1[offset % 272]; + offset >>= 4; + value ^= table2[offset % 272]; + offset >>= 4; + } + + return MaskV2PreDef[pos % 272] ^ value; +} + +std::vector key(17); +bool isVpr = false; + +size_t PreDec(uint8_t* fileData, size_t size, bool iV) { + uint32_t headerLen = *(uint32_t*)(fileData + 0x10); + memcpy(key.data(), (fileData + 0x1C), 0x10); + key[16] = 0; + isVpr = iV; + return headerLen; +} + +void Decrypt(uint8_t* fileData, size_t size, size_t offset) { + for (size_t i = 0; i < size; ++i) { + uint8_t med8 = key[(i + offset) % 17] ^ fileData[i]; + med8 ^= (med8 & 0xf) << 4; + + uint8_t msk8 = getMask(i + offset); + msk8 ^= (msk8 & 0xf) << 4; + fileData[i] = med8 ^ msk8; + + if (isVpr) { + fileData[i] ^= VprMaskDiff[(i + offset) % 17]; + } + } +} diff --git a/src/QmcWasm/CMakeLists.txt b/src/QmcWasm/CMakeLists.txt new file mode 100644 index 0000000..066268a --- /dev/null +++ b/src/QmcWasm/CMakeLists.txt @@ -0,0 +1,65 @@ +# CMakeList.txt : CMake project for QmcWasm, include source and define +# project specific logic here. +# +cmake_minimum_required (VERSION 3.8) + +project ("QmcWasm") + +set(CMAKE_CXX_STANDARD 14) + +include_directories( + $ +) + +# Add source to this project's executable. +set(RUNTIME_METHODS_LIST + getValue + writeArrayToMemory + UTF8ToString +) +list(JOIN RUNTIME_METHODS_LIST "," RUNTIME_METHODS) + +set(EMSCRIPTEN_FLAGS + "--bind" + "-s NO_DYNAMIC_EXECUTION=1" + "-s MODULARIZE=1" + "-s EXPORT_NAME=QmcCryptoModule" + "-s EXPORTED_RUNTIME_METHODS=${RUNTIME_METHODS}" +) +set(EMSCRIPTEN_LEGACY_FLAGS + ${EMSCRIPTEN_FLAGS} + "-s WASM=0" + "--memory-init-file 0" +) +set(EMSCRIPTEN_WASM_BUNDLE_FLAGS + ${EMSCRIPTEN_FLAGS} + "-s SINGLE_FILE=1" +) + +list(JOIN EMSCRIPTEN_FLAGS " " EMSCRIPTEN_FLAGS_STR) +list(JOIN EMSCRIPTEN_LEGACY_FLAGS " " EMSCRIPTEN_LEGACY_FLAGS_STR) +list(JOIN EMSCRIPTEN_WASM_BUNDLE_FLAGS " " EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR) + +# Define projects config +set(WASM_SOURCES + "QmcWasm.cpp" +) + +add_executable(QmcWasm ${WASM_SOURCES}) +set_target_properties( + QmcWasm + PROPERTIES LINK_FLAGS ${EMSCRIPTEN_FLAGS_STR} +) + +add_executable(QmcWasmBundle ${WASM_SOURCES}) +set_target_properties( + QmcWasmBundle + PROPERTIES LINK_FLAGS ${EMSCRIPTEN_WASM_BUNDLE_FLAGS_STR} +) + +add_executable(QmcLegacy ${WASM_SOURCES}) +set_target_properties( + QmcLegacy + PROPERTIES LINK_FLAGS ${EMSCRIPTEN_LEGACY_FLAGS_STR} +) + diff --git a/src/QmcWasm/QmcWasm.cpp b/src/QmcWasm/QmcWasm.cpp new file mode 100644 index 0000000..f4fc8c0 --- /dev/null +++ b/src/QmcWasm/QmcWasm.cpp @@ -0,0 +1,57 @@ +// QmcWasm.cpp : Defines the entry point for the application. +// + +#include "QmcWasm.h" + +#include "qmc.hpp" + +#include +#include + +std::string err = ""; +std::string sid = ""; +QmcDecode e; + +int preDec(uintptr_t blob, size_t blobSize, std::string ext) +{ + if (!e.SetBlob((uint8_t*)blob, blobSize)) + { + err = "cannot allocate memory"; + return -1; + } + int tailSize = e.PreDecode(ext); + if (e.error != "") + { + err = e.error; + return -1; + } + sid = e.songId; + return tailSize; +} + +size_t decBlob(uintptr_t blob, size_t blobSize, size_t offset) +{ + if (!e.SetBlob((uint8_t*)blob, blobSize)) + { + err = "cannot allocate memory"; + return 0; + } + std::vector decData = e.Decode(offset); + if (e.error != "") + { + err = e.error; + return 0; + } + memcpy((uint8_t*)blob, decData.data(), decData.size()); + return decData.size(); +} + +std::string getErr() +{ + return err; +} + +std::string getSongId() +{ + return sid; +} diff --git a/src/QmcWasm/QmcWasm.h b/src/QmcWasm/QmcWasm.h new file mode 100644 index 0000000..6fd63bf --- /dev/null +++ b/src/QmcWasm/QmcWasm.h @@ -0,0 +1,23 @@ +// QmcWasm.h : Include file for standard system include files, +// or project specific include files. + +#pragma once + +#include +#include + +namespace em = emscripten; + +int preDec(uintptr_t blob, size_t blobSize, std::string ext); +size_t decBlob(uintptr_t blob, size_t blobSize, size_t offset); +std::string getErr(); +std::string getSongId(); + +EMSCRIPTEN_BINDINGS(QmcCrypto) +{ + em::function("getErr", &getErr); + em::function("getSongId", &getSongId); + + em::function("preDec", &preDec, em::allow_raw_pointers()); + em::function("decBlob", &decBlob, em::allow_raw_pointers()); +} diff --git a/src/QmcWasm/README.md b/src/QmcWasm/README.md new file mode 100644 index 0000000..035fe65 --- /dev/null +++ b/src/QmcWasm/README.md @@ -0,0 +1,9 @@ +# QmcWasm + +## 构建 + +在 Linux 环境下执行 `bash build-wasm` 即可构建。 + +## Build + +Linux environment required. Build wasm binary by execute `bash build-wasm`. diff --git a/src/QmcWasm/TencentTea.hpp b/src/QmcWasm/TencentTea.hpp new file mode 100644 index 0000000..4f635a7 --- /dev/null +++ b/src/QmcWasm/TencentTea.hpp @@ -0,0 +1,289 @@ +#ifndef QQMUSIC_CPP_TENCENTTEA_HPP +#define QQMUSIC_CPP_TENCENTTEA_HPP + +#include +#include +#include +#include +#include +#include + +const uint32_t DELTA = 0x9e3779b9; + +#define ROUNDS 32 +#define SALT_LEN 2 +#define ZERO_LEN 7 + +void TeaDecryptECB(uint8_t* src, uint8_t* dst, std::vector key, size_t rounds = ROUNDS) { + if (key.size() != 16 || (rounds & 1) != 0) + { + return; + } + uint32_t y, z, sum; + uint32_t k[4]; + int i; + + //now encrypted buf is TCP/IP-endian; + //TCP/IP network byte order (which is big-endian). + y = ntohl(*((uint32_t*)src)); + z = ntohl(*((uint32_t*)(src + 4))); + //std::cout << ntohl(0x0a3aea41); + + for (i = 0; i < 4; i++) { + //key is TCP/IP-endian; + k[i] = ntohl(*((uint32_t*)(key.data() + i * 4))); + } + + sum = (DELTA * rounds); + for (i = 0; i < rounds; i++) { + z -= ((y << 4) + k[2]) ^ (y + sum) ^ ((y >> 5) + k[3]); + y -= ((z << 4) + k[0]) ^ (z + sum) ^ ((z >> 5) + k[1]); + sum -= DELTA; + } + + *((uint32_t*)dst) = ntohl(y); + *((uint32_t*)(dst + 4)) = ntohl(z); + + //now plain-text is TCP/IP-endian; +} + +void TeaEncryptECB(uint8_t* src, uint8_t* dst, std::vector key, size_t rounds = ROUNDS) { + if (key.size() != 16 || (rounds & 1) != 0) + { + return; + } + uint32_t y, z, sum; + uint32_t k[4]; + int i; + + //now encrypted buf is TCP/IP-endian; + //TCP/IP network byte order (which is big-endian). + y = ntohl(*((uint32_t*)src)); + z = ntohl(*((uint32_t*)(src + 4))); + //std::cout << ntohl(0x0a3aea41); + + for (i = 0; i < 4; i++) { + //key is TCP/IP-endian; + k[i] = ntohl(*((uint32_t*)(key.data() + i * 4))); + } + + sum = 0; + for (i = 0; i < rounds; i++) { + sum += DELTA; + y += ((z << 4) + k[0]) ^ (z + sum) ^ ((z >> 5) + k[1]); + z += ((y << 4) + k[2]) ^ (y + sum) ^ ((y >> 5) + k[3]); + } + + *((uint32_t*)dst) = ntohl(y); + *((uint32_t*)(dst + 4)) = ntohl(z); + + //now plain-text is TCP/IP-endian; +} + +/*pKeyΪ16byte*/ +/* + :nInBufLenΪܵIJ(Body); + :Ϊܺij(8byteı); +*/ +/*TEA㷨,CBCģʽ*/ +/*ĸʽ:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ +int encryptTencentTeaLen(int nInBufLen) +{ + + int nPadSaltBodyZeroLen/*PadLen(1byte)+Salt+Body+Zeroij*/; + int nPadlen; + + /*BodyȼPadLen,С賤ȱΪ8byte*/ + nPadSaltBodyZeroLen = nInBufLen/*Body*/ + 1 + SALT_LEN + ZERO_LEN/*PadLen(1byte)+Salt(2byte)+Zero(7byte)*/; + if ((nPadlen = nPadSaltBodyZeroLen % 8)) /*len=nSaltBodyZeroLen%8*/ + { + /*ģ80貹0,17,26,...,71*/ + nPadlen = 8 - nPadlen; + } + + return nPadlen; +} + +/*pKeyΪ16byte*/ +/* + :pInBufΪܵIJ(Body),nInBufLenΪpInBuf; + :pOutBufΪĸʽ,pOutBufLenΪpOutBufij8byteı; +*/ +/*TEA㷨,CBCģʽ*/ +/*ĸʽ:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ +bool encryptTencentTea(std::vector inBuf, std::vector key, std::vector &outBuf) +{ + srand(time(0)); + int nPadlen = encryptTencentTeaLen(inBuf.size()); + size_t ivCrypt; + std::vector srcBuf; + srcBuf.resize(8); + std::vector ivPlain; + ivPlain.resize(8); + int tmpIdx, i, j; + + /*ܵһ(8byte),ȡǰ10byte*/ + srcBuf[0] = (((char)rand()) & 0x0f8)/*λPadLen,*/ | (char)nPadlen; + tmpIdx = 1; /*tmpIdxָsrcBufһλ*/ + + while (nPadlen--) srcBuf[tmpIdx++] = (char)rand(); /*Padding*/ + + /*come here, tmpIdx must <= 8*/ + + for (i = 0; i < 8; i++) ivPlain[i] = 0; + ivCrypt = 0;//ivPlain /*make zero iv*/ + + auto outBufPos = 0; /*init outBufPos*/ + +#define cryptBlock {\ + /*tmpIdx==8*/\ + outBuf.resize(outBuf.size() + 8);\ + for (j = 0; j < 8; j++) /*ǰǰ8byte(iv_cryptָ)*/\ + srcBuf[j] ^= outBuf[j + ivCrypt];\ + /*pOutBufferpInBufferΪ8byte, pKeyΪ16byte*/\ + /**/\ + TeaEncryptECB(srcBuf.data(), outBuf.data()+outBufPos, key, 16);\ + for (j = 0; j < 8; j++) /*ܺǰ8byte(iv_plainָ)*/\ + outBuf[j + outBufPos] ^= ivPlain[j];\ + /*浱ǰiv_plain*/\ + for (j = 0; j < 8; j++) ivPlain[j] = srcBuf[j];\ + /*iv_crypt*/\ + tmpIdx = 0;\ + ivCrypt = outBufPos;\ + outBufPos += 8;\ + } + + + for (i = 1; i <= SALT_LEN;) /*Salt(2byte)*/ + { + if (tmpIdx < 8) + { + srcBuf[tmpIdx++] = (char)rand(); + i++; /*i inc in here*/ + } + if (tmpIdx == 8) + { + cryptBlock + } + } + + /*tmpIdxָsrcBufһλ*/ + + auto inBufPos = 0; + while (inBufPos < inBuf.size()) + { + if (tmpIdx < 8) + { + srcBuf[tmpIdx++] = inBuf[inBufPos]; + inBufPos++; + } + if (tmpIdx == 8) + { + cryptBlock + } + } + + /*tmpIdxָsrcBufһλ*/ + + for (i = 1; i <= ZERO_LEN;) + { + if (tmpIdx < 8) + { + srcBuf[tmpIdx++] = 0; + i++; //i inc in here + } + if (tmpIdx == 8) + { + cryptBlock + } + } + return true; +#undef cryptBlock +} + +bool decryptTencentTea(std::vector inBuf, std::vector key, std::vector &out) { + if (inBuf.size() % 8 != 0) { + return false; + //inBuf size not a multiple of the block size + } + if (inBuf.size() < 16) { + return false; + //inBuf size too small + } + + std::vector tmpBuf; + tmpBuf.resize(8); + + TeaDecryptECB(inBuf.data(), tmpBuf.data(), key, 16); + + auto nPadLen = tmpBuf[0] & 0x7; //ֻҪλ + /*ĸʽ:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ + auto outLen = inBuf.size() - 1 /*PadLen*/ - nPadLen - SALT_LEN - ZERO_LEN; + std::vector outBuf; + outBuf.resize(outLen); + + std::vector ivPrev; + ivPrev.resize(8); + std::vector ivCur; + ivCur.resize(8); + for (size_t i = 0; i < 8; i++) + { + ivCur[i] = inBuf[i]; // init iv + } + auto inBufPos = 8; + + // Padding Len Padding + auto tmpIdx = 1 + nPadLen; + + // CBC IV +#define cryptBlock {\ + ivPrev = ivCur;\ + for (size_t k = inBufPos; k < inBufPos + 8; k++)\ + {\ + ivCur[k - inBufPos] = inBuf[k];\ + }\ + for (size_t j = 0; j < 8; j++) {\ + tmpBuf[j] ^= ivCur[j];\ + }\ + TeaDecryptECB(tmpBuf.data(), tmpBuf.data(), key, 16);\ + inBufPos += 8;\ + tmpIdx = 0;\ + } + + // Salt + for (size_t i = 1; i <= SALT_LEN; ) { + if (tmpIdx < 8) { + tmpIdx++; + i++; + } + else { + cryptBlock + } + } + + // ԭ + auto outBufPos = 0; + while (outBufPos < outLen) { + if (tmpIdx < 8) { + outBuf[outBufPos] = tmpBuf[tmpIdx] ^ ivPrev[tmpIdx]; + outBufPos++; + tmpIdx++; + } + else { + cryptBlock + } + } + + // УZero + for (size_t i = 1; i <= ZERO_LEN; i++) { + if (tmpBuf[i] != ivPrev[i]) { + return false; + //zero check failed + } + } + out = outBuf; + return true; +#undef cryptBlock +} + +#endif //QQMUSIC_CPP_TENCENTTEA_HPP diff --git a/src/QmcWasm/base64.hpp b/src/QmcWasm/base64.hpp new file mode 100644 index 0000000..b3b6aca --- /dev/null +++ b/src/QmcWasm/base64.hpp @@ -0,0 +1,207 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +/* + Portions from http://www.adp-gmbh.ch/cpp/common/base64.html + Copyright notice: + + base64.cpp and base64.h + + Copyright (C) 2004-2008 Rene Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + Rene Nyffenegger rene.nyffenegger@adp-gmbh.ch +*/ + +#ifndef BASE64_HPP +#define BASE64_HPP + +#include +#include +#include + +namespace base64 { + + /// Returns max chars needed to encode a base64 string + std::size_t constexpr + encoded_size(std::size_t n) + { + return 4 * ((n + 2) / 3); + } + + /// Returns max bytes needed to decode a base64 string + inline + std::size_t constexpr + decoded_size(std::size_t n) + { + return n / 4 * 3; // requires n&3==0, smaller + } + + char const* + get_alphabet() + { + static char constexpr tab[] = { + "ABCDEFGHIJKLMNOP" + "QRSTUVWXYZabcdef" + "ghijklmnopqrstuv" + "wxyz0123456789+/" + }; + return &tab[0]; + } + + signed char const* + get_inverse() + { + static signed char constexpr tab[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 48-63 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95 + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255 + }; + return &tab[0]; + } + + /** Encode a series of octets as a padded, base64 string. + + The resulting string will not be null terminated. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `encoded_size(len)` bytes. + + @return The number of characters written to `out`. This + will exclude any null termination. + */ + std::size_t + encode(void* dest, void const* src, std::size_t len) + { + char* out = static_cast(dest); + char const* in = static_cast(src); + auto const tab = base64::get_alphabet(); + + for (auto n = len / 3; n--;) + { + *out++ = tab[(in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *out++ = tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)]; + *out++ = tab[in[2] & 0x3f]; + in += 3; + } + + switch (len % 3) + { + case 2: + *out++ = tab[(in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *out++ = tab[(in[1] & 0x0f) << 2]; + *out++ = '='; + break; + + case 1: + *out++ = tab[(in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4)]; + *out++ = '='; + *out++ = '='; + break; + + case 0: + break; + } + + return out - static_cast(dest); + } + + /** Decode a padded base64 string into a series of octets. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `decoded_size(len)` bytes. + + @return The number of octets written to `out`, and + the number of characters read from the input string, + expressed as a pair. + */ + std::pair + decode(void* dest, char const* src, std::size_t len) + { + char* out = static_cast(dest); + auto in = reinterpret_cast(src); + unsigned char c3[3], c4[4]; + int i = 0; + int j = 0; + + auto const inverse = base64::get_inverse(); + + while (len-- && *in != '=') + { + auto const v = inverse[*in]; + if (v == -1) + break; + ++in; + c4[i] = v; + if (++i == 4) + { + c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for (i = 0; i < 3; i++) + *out++ = c3[i]; + i = 0; + } + } + + if (i) + { + c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for (j = 0; j < i - 1; j++) + *out++ = c3[j]; + } + + return { out - static_cast(dest), + in - reinterpret_cast(src) }; + } + +} // base64 + +#endif diff --git a/src/QmcWasm/build-wasm b/src/QmcWasm/build-wasm new file mode 100644 index 0000000..8a9a1f3 --- /dev/null +++ b/src/QmcWasm/build-wasm @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -e + +pushd "$(realpath "$(dirname "$0")")" + +CURR_DIR="${PWD}" + +BUILD_TYPE="$1" +if [ -z "$BUILD_TYPE" ]; then + BUILD_TYPE=Release +fi + +mkdir -p build/wasm +if [ ! -d build/emsdk ]; then + git clone https://github.com/emscripten-core/emsdk.git build/emsdk +fi + +pushd build/emsdk +#git pull +./emsdk install 3.0.0 +./emsdk activate 3.0.0 +source ./emsdk_env.sh +popd # build/emsdk + +pushd build/wasm +emcmake cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ../.. +make -j +TARGET_FILES=" + QmcLegacy.js + QmcWasm.js + QmcWasm.wasm + QmcWasmBundle.js +" + +#mkdir -p "${CURR_DIR}/npm" +#cp $TARGET_FILES "${CURR_DIR}/npm/" +cp $TARGET_FILES "${CURR_DIR}/" +popd # build/wasm + +popd diff --git a/src/QmcWasm/qmc.hpp b/src/QmcWasm/qmc.hpp new file mode 100644 index 0000000..9bfb5e1 --- /dev/null +++ b/src/QmcWasm/qmc.hpp @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include "qmc_key.hpp" +#include "qmc_cipher.hpp" + +class QmcDecode { +private: + std::vector blobData; + + std::vector rawKeyBuf; + std::string cipherType = ""; + + size_t dataOffset = 0; + size_t keySize = 0; + int mediaVer = 0; + + std::string checkType(std::string fn) { + if (fn.find(".qmc") < fn.size() || fn.find(".m") < fn.size()) + { + std::string buf_tag = ""; + for (int i = 4; i > 0; --i) + { + buf_tag += *((char*)blobData.data() + blobData.size() - i); + } + if (buf_tag == "QTag") + { + keySize = ntohl(*(uint32_t*)(blobData.data() + blobData.size() - 8)); + return "QTag"; + } + else if (buf_tag == "STag") + { + return "STag"; + } + else + { + keySize = (*(uint32_t*)(blobData.data() + blobData.size() - 4)); + if (keySize < 0x400) + { + return "Map/RC4"; + } + else + { + keySize = 0; + return "Static"; + } + } + } + else if (fn.find(".cache") < fn.size()) + { + return "cache"; + } + else if (fn.find(".tm") < fn.size()) + { + return "ios"; + } + else + { + return "invalid"; + } + } + + bool parseRawKeyQTag() { + std::string ketStr = ""; + std::string::size_type index = 0; + ketStr.append((char*)rawKeyBuf.data(), rawKeyBuf.size()); + index = ketStr.find(",", 0); + if (index != std::string::npos) + { + rawKeyBuf.resize(index); + } + else + { + return false; + } + ketStr = ketStr.substr(index + 1); + index = ketStr.find(",", 0); + if (index != std::string::npos) + { + this->songId = ketStr.substr(0, index); + } + else + { + return false; + } + ketStr = ketStr.substr(index + 1); + index = ketStr.find(",", 0); + if (index == std::string::npos) + { + this->mediaVer = std::stoi(ketStr); + } + else + { + return false; + } + return true; + } + + bool readRawKey(size_t tailSize) { + // get raw key data length + rawKeyBuf.resize(keySize); + if (rawKeyBuf.size() != keySize) { + return false; + } + for (size_t i = 0; i < keySize; i++) + { + rawKeyBuf[i] = blobData[i + blobData.size() - (tailSize + keySize)]; + } + return true; + } + + void DecodeStatic(); + + void DecodeMapRC4(); + + void DecodeCache(); + + void DecodeTm(); + +public: + bool SetBlob(uint8_t* blob, size_t blobSize) { + blobData.resize(blobSize); + if (blobData.size() != blobSize) { + return false; + } + memcpy(blobData.data(), blob, blobSize); + return true; + } + + int PreDecode(std::string ext) { + cipherType = checkType(ext); + size_t tailSize = 0; + if (cipherType == "QTag") { + tailSize = 8; + } + else if (cipherType == "Map/RC4") { + tailSize = 4; + } + if (keySize > 0) { + if (!readRawKey(tailSize)) { + error = "cannot read embedded key from file"; + return -1; + } + if (tailSize == 8) { + cipherType = "Map/RC4"; + if (!parseRawKeyQTag()) { + error = "cannot parse embedded key"; + return -1; + } + } + std::vector tmp; + if (!QmcDecryptKey(rawKeyBuf, tmp)) { + error = "cannot decrypt embedded key"; + return -1; + } + rawKeyBuf = tmp; + } + if (cipherType == "invalid") { + error = "file is invalid or not supported(Please downgrade your app.)"; + return -1; + } + return keySize + tailSize; + } + + std::vector Decode(size_t offset); + + std::string songId = ""; + std::string error = ""; +}; + +void QmcDecode::DecodeStatic() +{ + QmcStaticCipher sc; + sc.proc(blobData, dataOffset); +} + +void QmcDecode::DecodeMapRC4() { + if (rawKeyBuf.size() > 300) + { + QmcRC4Cipher c(rawKeyBuf, 2); + c.proc(blobData, dataOffset); + } + else + { + QmcMapCipher c(rawKeyBuf, 2); + c.proc(blobData, dataOffset); + } +} + +void QmcDecode::DecodeCache() +{ + for (size_t i = 0; i < blobData.size(); i++) { + blobData[i] ^= 0xf4; + if (blobData[i] <= 0x3f) blobData[i] = blobData[i] * 4; + else if (blobData[i] <= 0x7f) blobData[i] = (blobData[i] - 0x40) * 4 + 1; + else if (blobData[i] <= 0xbf) blobData[i] = (blobData[i] - 0x80) * 4 + 2; + else blobData[i] = (blobData[i] - 0xc0) * 4 + 3; + } +} + +void QmcDecode::DecodeTm() +{ + uint8_t const TM_HEADER[] = { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70 }; + for (size_t cur = dataOffset, i = 0; cur < 8 && i < blobData.size(); ++cur, ++i) { + blobData[i] = TM_HEADER[dataOffset]; + } +} + +std::vector QmcDecode::Decode(size_t offset) +{ + dataOffset = offset; + if (cipherType == "Map/RC4") + { + DecodeMapRC4(); + } + else if (cipherType == "Static") + { + DecodeStatic(); + } + else if (cipherType == "cache") + { + DecodeCache(); + } + else if (cipherType == "ios") + { + DecodeTm(); + } + else { + error = "File is invalid or encryption type is not supported."; + } + return blobData; +} diff --git a/src/QmcWasm/qmc_cipher.hpp b/src/QmcWasm/qmc_cipher.hpp new file mode 100644 index 0000000..8dc2b18 --- /dev/null +++ b/src/QmcWasm/qmc_cipher.hpp @@ -0,0 +1,290 @@ +#include +#include +class QmcStaticCipher { +private: + uint8_t staticCipherBox[256] = { + 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, //0x00 + 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, //0x08 + 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, //0x10 + 0xCE, 0xB8, 0xDC, 0xC3, 0x4A, 0x67, 0x93, 0xD6, //0x18 + 0x28, 0xC2, 0x91, 0x70, 0xCA, 0x8D, 0xA2, 0xA4, //0x20 + 0xF0, 0x08, 0x61, 0x90, 0x7E, 0x6F, 0xA2, 0xE0, //0x28 + 0xEB, 0xAE, 0x3E, 0xB6, 0x67, 0xC7, 0x92, 0xF4, //0x30 + 0x91, 0xB5, 0xF6, 0x6C, 0x5E, 0x84, 0x40, 0xF7, //0x38 + 0xF3, 0x1B, 0x02, 0x7F, 0xD5, 0xAB, 0x41, 0x89, //0x40 + 0x28, 0xF4, 0x25, 0xCC, 0x52, 0x11, 0xAD, 0x43, //0x48 + 0x68, 0xA6, 0x41, 0x8B, 0x84, 0xB5, 0xFF, 0x2C, //0x50 + 0x92, 0x4A, 0x26, 0xD8, 0x47, 0x6A, 0x7C, 0x95, //0x58 + 0x61, 0xCC, 0xE6, 0xCB, 0xBB, 0x3F, 0x47, 0x58, //0x60 + 0x89, 0x75, 0xC3, 0x75, 0xA1, 0xD9, 0xAF, 0xCC, //0x68 + 0x08, 0x73, 0x17, 0xDC, 0xAA, 0x9A, 0xA2, 0x16, //0x70 + 0x41, 0xD8, 0xA2, 0x06, 0xC6, 0x8B, 0xFC, 0x66, //0x78 + 0x34, 0x9F, 0xCF, 0x18, 0x23, 0xA0, 0x0A, 0x74, //0x80 + 0xE7, 0x2B, 0x27, 0x70, 0x92, 0xE9, 0xAF, 0x37, //0x88 + 0xE6, 0x8C, 0xA7, 0xBC, 0x62, 0x65, 0x9C, 0xC2, //0x90 + 0x08, 0xC9, 0x88, 0xB3, 0xF3, 0x43, 0xAC, 0x74, //0x98 + 0x2C, 0x0F, 0xD4, 0xAF, 0xA1, 0xC3, 0x01, 0x64, //0xA0 + 0x95, 0x4E, 0x48, 0x9F, 0xF4, 0x35, 0x78, 0x95, //0xA8 + 0x7A, 0x39, 0xD6, 0x6A, 0xA0, 0x6D, 0x40, 0xE8, //0xB0 + 0x4F, 0xA8, 0xEF, 0x11, 0x1D, 0xF3, 0x1B, 0x3F, //0xB8 + 0x3F, 0x07, 0xDD, 0x6F, 0x5B, 0x19, 0x30, 0x19, //0xC0 + 0xFB, 0xEF, 0x0E, 0x37, 0xF0, 0x0E, 0xCD, 0x16, //0xC8 + 0x49, 0xFE, 0x53, 0x47, 0x13, 0x1A, 0xBD, 0xA4, //0xD0 + 0xF1, 0x40, 0x19, 0x60, 0x0E, 0xED, 0x68, 0x09, //0xD8 + 0x06, 0x5F, 0x4D, 0xCF, 0x3D, 0x1A, 0xFE, 0x20, //0xE0 + 0x77, 0xE4, 0xD9, 0xDA, 0xF9, 0xA4, 0x2B, 0x76, //0xE8 + 0x1C, 0x71, 0xDB, 0x00, 0xBC, 0xFD, 0x0C, 0x6C, //0xF0 + 0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11 //0xF8 + }; + + uint8_t getMask(size_t offset) { + if (offset > 0x7fff) offset %= 0x7fff; + return staticCipherBox[(offset * offset + 27) & 0xff]; + } + +public: + void proc(std::vector& buf, size_t offset) { + for (size_t i = 0; i < buf.size(); i++) { + buf[i] ^= getMask(offset + i); + } + } +}; + +class QmcMapCipher { +private: + std::vector key; + + uint8_t rotate(uint8_t value, size_t bits) { + auto rotate = (bits + 4) % 8; + auto left = value << rotate; + auto right = value >> rotate; + return (left | right) & 0xff; + } + + uint8_t getMask(size_t offset) { + if (offset > 0x7fff) offset %= 0x7fff; + + const auto idx = (offset * offset + 71214) % key.size(); + return rotate(key[idx], idx & 0x7); + } + +public: + QmcMapCipher(std::vector &argKey, short operation) { + if (operation == 2) + { + if (argKey.size() == 0) { + return; + } + } + else if (operation == 1) + { + const char WordList[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + srand(time(0)); + uint32_t number = 0; + while (number > 300 || number == 0) + { + number = rand(); + } + argKey.resize(number); + for (int i = 0; i < argKey.size(); i++) { + number = rand(); + argKey[i] = WordList[number % 62]; + } + } + else + { + return; + } + + key = argKey; + } + + void proc(std::vector& buf, size_t offset) { + for (size_t i = 0; i < buf.size(); i++) { + buf[i] ^= getMask(offset + i); + } + } +}; + +class QmcRC4Cipher { +public: + void proc(std::vector& buf, size_t offset) { + // Macro: common code after each process +#define postProcess(len) \ + { \ + toProcess -= len; \ + processed += len; \ + offset += len; \ + /* no more data */ \ + if (toProcess == 0) { \ + return; \ + } \ + } + + size_t toProcess = buf.size(); + size_t processed = 0; + std::vector tmpbuf; + + // ǰ 128 ֽʹòͬĽܷ + if (offset < FIRST_SEGMENT_SIZE) { + size_t len_segment = std::min(FIRST_SEGMENT_SIZE - offset, buf.size()); + tmpbuf.resize(len_segment); + for (size_t i = 0; i < len_segment; i++) + { + tmpbuf[i] = buf[processed + i]; + } + procFirstSegment(tmpbuf, offset); + for (size_t i = 0; i < len_segment; i++) + { + buf[processed + i] = tmpbuf[i]; + } + postProcess(len_segment); + } + + + // + if (offset % SEGMENT_SIZE != 0) { + size_t len_segment = std::min(SEGMENT_SIZE - (offset % SEGMENT_SIZE), toProcess); + tmpbuf.resize(len_segment); + for (size_t i = 0; i < len_segment; i++) + { + tmpbuf[i] = buf[processed + i]; + } + procASegment(tmpbuf, offset); + for (size_t i = 0; i < len_segment; i++) + { + buf[processed + i] = tmpbuf[i]; + } + postProcess(len_segment); + } + + // ÿһн + while (toProcess > SEGMENT_SIZE) { + tmpbuf.resize(SEGMENT_SIZE); + for (size_t i = 0; i < SEGMENT_SIZE; i++) + { + tmpbuf[i] = buf[processed + i]; + } + procASegment(tmpbuf, offset); + for (size_t i = 0; i < SEGMENT_SIZE; i++) + { + buf[processed + i] = tmpbuf[i]; + } + postProcess(SEGMENT_SIZE); + } + + if (toProcess > 0) { + tmpbuf.resize(toProcess); + for (size_t i = 0; i < toProcess; i++) + { + tmpbuf[i] = buf[processed + i]; + } + procASegment(tmpbuf, offset); + for (size_t i = 0; i < toProcess; i++) + { + buf[processed + i] = tmpbuf[i]; + } + } + +#undef postProcess + } + + QmcRC4Cipher(std::vector& argKey, short operation) { + if (operation == 2) + { + if (argKey.size() == 0) { + return; + } + } + else if (operation == 1) + { + const char WordList[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + srand(time(0)); + uint32_t number = 0; + while (number <= 300 || number >= 512) + { + number = rand(); + } + argKey.resize(number); + for (int i = 0; i < argKey.size(); i++) { + number = rand(); + argKey[i] = WordList[number % 62]; + } + } + else + { + return; + } + + key = argKey; + + // init seed box + S.resize(key.size()); + for (size_t i = 0; i < key.size(); ++i) { + S[i] = i & 0xff; + } + size_t j = 0; + for (size_t i = 0; i < key.size(); ++i) { + j = (S[i] + j + key[i % key.size()]) % key.size(); + std::swap(S[i], S[j]); + } + + // init hash base + hash = 1; + for (size_t i = 0; i < key.size(); i++) { + uint8_t value = key[i]; + + // ignore if key char is '\x00' + if (!value) continue; + + auto next_hash = hash * value; + if (next_hash == 0 || next_hash <= hash) break; + + hash = next_hash; + } + } + +private: + const size_t FIRST_SEGMENT_SIZE = 0x80; + const size_t SEGMENT_SIZE = 5120; + + std::vector S; + std::vector key; + uint32_t hash = 1; + + void procFirstSegment(std::vector& buf, size_t offset) { + for (size_t i = 0; i < buf.size(); i++) { + buf[i] ^= key[getSegmentKey(offset + i)]; + } + } + + void procASegment(std::vector& buf, size_t offset) { + // Initialise a new seed box + std::vector nS; + nS = S; + + // Calculate the number of bytes to skip. + // The initial "key" derived from segment id, plus the current offset. + int64_t skipLen = (offset % SEGMENT_SIZE) + getSegmentKey(int(offset / SEGMENT_SIZE)); + + // decrypt the block + size_t j = 0; + size_t k = 0; + int i = -skipLen; + for (; i < (int)buf.size(); i++) { + j = (j + 1) % key.size(); + k = (nS[j] + k) % key.size(); + std::swap(nS[k], nS[j]); + + if (i >= 0) { + buf[i] ^= nS[(nS[j] + nS[k]) % key.size()]; + } + } + } + + uint64_t getSegmentKey(int id) { + auto seed = key[id % key.size()]; + uint64_t idx = ((double)hash / ((id + 1) * seed)) * 100.0; + return idx % key.size(); + } +}; diff --git a/src/QmcWasm/qmc_key.hpp b/src/QmcWasm/qmc_key.hpp new file mode 100644 index 0000000..f3178cd --- /dev/null +++ b/src/QmcWasm/qmc_key.hpp @@ -0,0 +1,217 @@ +#include"TencentTea.hpp" +#include "base64.hpp" + +void simpleMakeKey(uint8_t salt, int length, std::vector &key_buf) { + for (size_t i = 0; i < length; ++i) { + double tmp = tan((float)salt + (double)i * 0.1); + key_buf[i] = 0xFF & (uint8_t)(fabs(tmp) * 100.0); + } +} + +std::vector v2KeyPrefix = { 0x51, 0x51, 0x4D, 0x75, 0x73, 0x69, 0x63, 0x20, 0x45, 0x6E, 0x63, 0x56, 0x32, 0x2C, 0x4B, 0x65, 0x79, 0x3A }; + +bool decryptV2Key(std::vector key, std::vector& outVec) +{ + if (v2KeyPrefix.size() > key.size()) + { + return true; + } + for (size_t i = 0; i < v2KeyPrefix.size(); i++) + { + if (key[i] != v2KeyPrefix[i]) + { + return true; + } + } + + std::vector mixKey1 = { 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 }; + std::vector mixKey2 = { 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 }; + + std::vector out; + std::vector tmpKey; + tmpKey.resize(key.size() - 18); + for (size_t i = 0; i < tmpKey.size(); i++) + { + tmpKey[i] = key[18 + i]; + } + if (!decryptTencentTea(tmpKey, mixKey1, out)) + { + outVec.resize(0); + //EncV2 key decode failed. + return false; + } + + tmpKey.resize(out.size()); + for (size_t i = 0; i < tmpKey.size(); i++) + { + tmpKey[i] = out[i]; + } + out.resize(0); + if (!decryptTencentTea(tmpKey, mixKey2, out)) + { + outVec.resize(0); + //EncV2 key decode failed. + return false; + } + + outVec.resize(base64::decoded_size(out.size())); + auto n = base64::decode(outVec.data(), (const char*)(out.data()), out.size()).first; + + if (n < 16) + { + outVec.resize(0); + //EncV2 key size is too small. + return false; + } + outVec.resize(n); + + return true; +} + +bool encryptV2Key(std::vector key, std::vector& outVec) +{ + if (key.size() < 16) + { + outVec.resize(0); + //EncV2 key size is too small. + return false; + } + + std::vector in; + in.resize(base64::encoded_size(key.size())); + auto n = base64::encode(in.data(), (const char*)(key.data()), key.size()); + in.resize(n); + + std::vector mixKey1 = { 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 }; + std::vector mixKey2 = { 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 }; + + std::vector tmpKey; + if (!encryptTencentTea(in, mixKey2, tmpKey)) + { + outVec.resize(0); + //EncV2 key decode failed. + return false; + } + in.resize(tmpKey.size()); + for (size_t i = 0; i < tmpKey.size(); i++) + { + in[i] = tmpKey[i]; + } + tmpKey.resize(0); + + if (!encryptTencentTea(in, mixKey1, tmpKey)) + { + outVec.resize(0); + //EncV2 key decode failed. + return false; + } + outVec.resize(tmpKey.size() + 18); + for (size_t i = 0; i < tmpKey.size(); i++) + { + outVec[18 + i] = tmpKey[i]; + } + + for (size_t i = 0; i < v2KeyPrefix.size(); i++) + { + outVec[i] = v2KeyPrefix[i]; + } + + return true; +} + +bool QmcDecryptKey(std::vector raw, std::vector &outVec) { + std::vector rawDec; + rawDec.resize(base64::decoded_size(raw.size())); + auto n = base64::decode(rawDec.data(), (const char*)(raw.data()), raw.size()).first; + if (n < 16) { + return false; + //key length is too short + } + rawDec.resize(n); + + std::vector tmpIn = rawDec; + if (!decryptV2Key(tmpIn, rawDec)) + { + //decrypt EncV2 failed. + return false; + } + + std::vector simpleKey; + simpleKey.resize(8); + simpleMakeKey(106, 8, simpleKey); + std::vector teaKey; + teaKey.resize(16); + for (size_t i = 0; i < 8; i++) { + teaKey[i << 1] = simpleKey[i]; + teaKey[(i << 1) + 1] = rawDec[i]; + } + std::vector out; + std::vector tmpRaw; + tmpRaw.resize(rawDec.size() - 8); + for (size_t i = 0; i < tmpRaw.size(); i++) + { + tmpRaw[i] = rawDec[8 + i]; + } + if (decryptTencentTea(tmpRaw, teaKey, out)) + { + rawDec.resize(8 + out.size()); + for (size_t i = 0; i < out.size(); i++) + { + rawDec[8 + i] = out[i]; + } + outVec = rawDec; + return true; + } + else + { + return false; + } +} + +bool QmcEncryptKey(std::vector raw, std::vector& outVec, bool useEncV2 = true) { + std::vector simpleKey; + simpleKey.resize(8); + simpleMakeKey(106, 8, simpleKey); + std::vector teaKey; + teaKey.resize(16); + for (size_t i = 0; i < 8; i++) { + teaKey[i << 1] = simpleKey[i]; + teaKey[(i << 1) + 1] = raw[i]; + } + std::vector out; + out.resize(raw.size() - 8); + for (size_t i = 0; i < out.size(); i++) + { + out[i] = raw[8 + i]; + } + std::vector tmpRaw; + if (encryptTencentTea(out, teaKey, tmpRaw)) + { + raw.resize(tmpRaw.size() + 8); + for (size_t i = 0; i < tmpRaw.size(); i++) + { + raw[i + 8] = tmpRaw[i]; + } + + if (useEncV2) + { + std::vector tmpIn = raw; + if (!encryptV2Key(tmpIn, raw)) + { + //encrypt EncV2 failed. + return false; + } + } + + std::vector rawEnc; + rawEnc.resize(base64::encoded_size(raw.size())); + auto n = base64::encode(rawEnc.data(), (const char*)(raw.data()), raw.size()); + rawEnc.resize(n); + outVec = rawEnc; + return true; + } + else + { + return false; + } +}