Compare commits
3 Commits
84585531d4
...
74ba5156df
Author | SHA1 | Date | |
---|---|---|---|
74ba5156df | |||
2908525c5c | |||
edbe9ae708 |
@ -4,4 +4,4 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
std::vector<uint8_t> b64_decode(const uint8_t *input, size_t len);
|
||||
std::vector<uint8_t> b64_decode(const uint8_t* input, size_t len);
|
||||
|
@ -59,6 +59,11 @@ class KggTask {
|
||||
}
|
||||
|
||||
auto qmc2 = QMC2::Create(ekey);
|
||||
if (!qmc2) {
|
||||
error(L"create qmc2 instance failed (ekey decode error?)");
|
||||
fprintf(stderr, "%s\n", ekey.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::string magic(4, 0);
|
||||
kgg_stream_in.seekg(offset_to_audio, std::ios::beg);
|
||||
|
@ -3,67 +3,59 @@
|
||||
#include "tc_tea.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
const static std::string kEKeyV2Prefix = "UVFNdXNpYyBFbmNWMixLZXk6";
|
||||
const static std::array<uint8_t, 16> kEKeyV2Key1{
|
||||
0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40,
|
||||
0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28,
|
||||
0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28,
|
||||
};
|
||||
const static std::array<uint8_t, 16> kEKeyV2Key2{
|
||||
0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25,
|
||||
0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54,
|
||||
0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54,
|
||||
};
|
||||
|
||||
template <typename T> std::span<T> ss2span(std::string_view sv) {
|
||||
auto *data = reinterpret_cast<const T *>(sv.data());
|
||||
return std::span<T>(const_cast<T *>(data), sv.size());
|
||||
template <typename T>
|
||||
std::span<T> ss2span(std::string_view sv) {
|
||||
auto* data = reinterpret_cast<const T*>(sv.data());
|
||||
return std::span<T>(const_cast<T*>(data), sv.size());
|
||||
}
|
||||
|
||||
template <typename T> std::string_view span2ss(std::span<T> span) {
|
||||
return std::string_view(reinterpret_cast<char *>(span.data()), span.size());
|
||||
}
|
||||
|
||||
void remove_trailing_zeros(std::vector<uint8_t> &vec) {
|
||||
auto it = std::find_if(vec.rbegin(), vec.rend(),
|
||||
[](uint8_t value) { return value != 0; });
|
||||
vec.erase(it.base(), vec.end());
|
||||
template <typename T>
|
||||
std::string_view span2ss(std::span<T> span) {
|
||||
return std::string_view(reinterpret_cast<char*>(span.data()), span.size());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> decrypt_ekey_v1(std::string_view ekey) {
|
||||
std::vector<uint8_t> result =
|
||||
b64_decode(reinterpret_cast<const uint8_t *>(ekey.data()), ekey.size());
|
||||
remove_trailing_zeros(result);
|
||||
std::vector<uint8_t> result = b64_decode(reinterpret_cast<const uint8_t*>(ekey.data()), ekey.size());
|
||||
|
||||
uint32_t tea_key[4] = {
|
||||
0x69005600 | static_cast<uint32_t>(result[0] << 16) | (result[1]),
|
||||
0x46003800 | static_cast<uint32_t>(result[2] << 16) | (result[3]),
|
||||
0x2b002000 | static_cast<uint32_t>(result[4] << 16) | (result[5]),
|
||||
0x15000b00 | static_cast<uint32_t>(result[6] << 16) | (result[7]),
|
||||
};
|
||||
auto decrypted = tc_tea_cbc_decrypt(std::span(result).subspan(8), tea_key);
|
||||
if (decrypted.empty()) {
|
||||
return {};
|
||||
}
|
||||
result.resize(8);
|
||||
result.insert(result.end(), decrypted.begin(), decrypted.end());
|
||||
return result;
|
||||
uint32_t tea_key[4] = {
|
||||
0x69005600 | static_cast<uint32_t>(result[0] << 16) | (result[1]),
|
||||
0x46003800 | static_cast<uint32_t>(result[2] << 16) | (result[3]),
|
||||
0x2b002000 | static_cast<uint32_t>(result[4] << 16) | (result[5]),
|
||||
0x15000b00 | static_cast<uint32_t>(result[6] << 16) | (result[7]),
|
||||
};
|
||||
auto decrypted = tc_tea_cbc_decrypt(std::span(result).subspan(8), tea_key);
|
||||
if (decrypted.empty()) {
|
||||
return {};
|
||||
}
|
||||
result.resize(8);
|
||||
result.insert(result.end(), decrypted.begin(), decrypted.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> decrypt_ekey_v2(std::string_view ekey) {
|
||||
std::vector<uint8_t> result;
|
||||
result = tc_tea_cbc_decrypt(ss2span<uint8_t>(ekey), kEKeyV2Key1.data());
|
||||
result = tc_tea_cbc_decrypt(std::span(result), kEKeyV2Key2.data());
|
||||
return decrypt_ekey_v1(span2ss(std::span(result)));
|
||||
std::vector<uint8_t> result;
|
||||
result = tc_tea_cbc_decrypt(ss2span<uint8_t>(ekey), kEKeyV2Key1.data());
|
||||
result = tc_tea_cbc_decrypt(std::span(result), kEKeyV2Key2.data());
|
||||
return decrypt_ekey_v1(span2ss(std::span(result)));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> decrypt_ekey(std::string_view ekey) {
|
||||
if (ekey.starts_with(kEKeyV2Prefix)) {
|
||||
ekey.remove_prefix(kEKeyV2Prefix.size());
|
||||
return decrypt_ekey_v2(ekey);
|
||||
}
|
||||
if (ekey.starts_with(kEKeyV2Prefix)) {
|
||||
ekey.remove_prefix(kEKeyV2Prefix.size());
|
||||
return decrypt_ekey_v2(ekey);
|
||||
}
|
||||
|
||||
return decrypt_ekey_v1(ekey);
|
||||
return decrypt_ekey_v1(ekey);
|
||||
}
|
||||
|
@ -4,17 +4,17 @@
|
||||
namespace QMC2 {
|
||||
|
||||
std::unique_ptr<QMC2_Base> Create(std::string_view ekey) {
|
||||
auto key = decrypt_ekey(ekey);
|
||||
auto key_len = key.size();
|
||||
if (key_len == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
auto key = decrypt_ekey(ekey);
|
||||
auto key_len = key.size();
|
||||
if (key_len == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (key_len < 300) {
|
||||
return std::make_unique<QMC2_MAP>(std::span(key));
|
||||
} else {
|
||||
return std::make_unique<QMC2_RC4>(std::span(key));
|
||||
}
|
||||
if (key_len < 300) {
|
||||
return std::make_unique<QMC2_MAP>(std::span(key));
|
||||
} else {
|
||||
return std::make_unique<QMC2_RC4>(std::span(key));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QMC2
|
||||
} // namespace QMC2
|
||||
|
@ -10,61 +10,62 @@ constexpr size_t kTeaBlockSize = 8;
|
||||
constexpr size_t kFixedSaltLen = 2;
|
||||
constexpr size_t kZeroPadLen = 7;
|
||||
|
||||
inline void decrypt_round(uint8_t *p_plain, const uint8_t *p_cipher,
|
||||
uint64_t *iv1, uint64_t *iv2, const uint32_t *key) {
|
||||
uint64_t iv1_next = Endian::be_u64_read(p_cipher);
|
||||
uint64_t iv2_next = tc_tea_ecb_decrypt(iv1_next ^ *iv2, key);
|
||||
uint64_t plain = iv2_next ^ *iv1;
|
||||
*iv1 = iv1_next;
|
||||
*iv2 = iv2_next;
|
||||
Endian::be_u64_write(p_plain, plain);
|
||||
inline void decrypt_round(uint8_t* p_plain,
|
||||
const uint8_t* p_cipher,
|
||||
uint64_t* iv1,
|
||||
uint64_t* iv2,
|
||||
const uint32_t* key) {
|
||||
uint64_t iv1_next = Endian::be_u64_read(p_cipher);
|
||||
uint64_t iv2_next = tc_tea_ecb_decrypt(iv1_next ^ *iv2, key);
|
||||
uint64_t plain = iv2_next ^ *iv1;
|
||||
*iv1 = iv1_next;
|
||||
*iv2 = iv2_next;
|
||||
Endian::be_u64_write(p_plain, plain);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> tc_tea_cbc_decrypt(std::span<uint8_t> cipher,
|
||||
const uint32_t *key) {
|
||||
// It needs to have at least 2 blocks long, due to the nature of the padding
|
||||
// scheme used.
|
||||
if (cipher.size() % kTeaBlockSize != 0 || cipher.size() < kTeaBlockSize * 2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint64_t iv1 = 0;
|
||||
uint64_t iv2 = 0;
|
||||
uint8_t header[kTeaBlockSize * 2];
|
||||
const uint8_t *in_cipher = cipher.data();
|
||||
decrypt_round(header, in_cipher, &iv1, &iv2, key);
|
||||
in_cipher += kTeaBlockSize;
|
||||
decrypt_round(header + kTeaBlockSize, in_cipher, &iv1, &iv2, key);
|
||||
in_cipher += kTeaBlockSize;
|
||||
|
||||
size_t hdr_skip_len = 1 + (header[0] & 7) + kFixedSaltLen;
|
||||
size_t real_plain_len = cipher.size() - hdr_skip_len - kZeroPadLen;
|
||||
std::vector<uint8_t> result(real_plain_len);
|
||||
|
||||
auto p_output = result.data();
|
||||
|
||||
// copy first block of plain text
|
||||
size_t copy_len = std::min(sizeof(header) - hdr_skip_len, real_plain_len);
|
||||
std::copy_n(header + hdr_skip_len, real_plain_len, p_output);
|
||||
p_output += copy_len;
|
||||
|
||||
if (real_plain_len != copy_len) {
|
||||
// Decrypt the rest of the blocks
|
||||
for (size_t i = cipher.size() - kTeaBlockSize * 3; i != 0;
|
||||
i -= kTeaBlockSize) {
|
||||
decrypt_round(p_output, in_cipher, &iv1, &iv2, key);
|
||||
in_cipher += kTeaBlockSize;
|
||||
p_output += kTeaBlockSize;
|
||||
std::vector<uint8_t> tc_tea_cbc_decrypt(std::span<uint8_t> cipher, const uint32_t* key) {
|
||||
// It needs to have at least 2 blocks long, due to the nature of the padding
|
||||
// scheme used.
|
||||
if (cipher.size() % kTeaBlockSize != 0 || cipher.size() < kTeaBlockSize * 2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint64_t iv1 = 0;
|
||||
uint64_t iv2 = 0;
|
||||
uint8_t header[kTeaBlockSize * 2];
|
||||
const uint8_t* in_cipher = cipher.data();
|
||||
decrypt_round(header, in_cipher, &iv1, &iv2, key);
|
||||
in_cipher += kTeaBlockSize;
|
||||
decrypt_round(header + kTeaBlockSize, in_cipher, &iv1, &iv2, key);
|
||||
p_output[0] = header[kTeaBlockSize];
|
||||
}
|
||||
// Validate zero padding
|
||||
auto verify = Endian::be_u64_read(header + kTeaBlockSize) << 8;
|
||||
if (verify != 0) {
|
||||
result.resize(0);
|
||||
}
|
||||
in_cipher += kTeaBlockSize;
|
||||
|
||||
return result;
|
||||
size_t hdr_skip_len = 1 + (header[0] & 7) + kFixedSaltLen;
|
||||
size_t real_plain_len = cipher.size() - hdr_skip_len - kZeroPadLen;
|
||||
std::vector<uint8_t> result(real_plain_len);
|
||||
|
||||
auto p_output = result.data();
|
||||
|
||||
// copy first block of plain text
|
||||
size_t copy_len = std::min(sizeof(header) - hdr_skip_len, real_plain_len);
|
||||
std::copy_n(header + hdr_skip_len, real_plain_len, p_output);
|
||||
p_output += copy_len;
|
||||
|
||||
if (real_plain_len != copy_len) {
|
||||
// Decrypt the rest of the blocks
|
||||
for (size_t i = cipher.size() - kTeaBlockSize * 3; i != 0; i -= kTeaBlockSize) {
|
||||
decrypt_round(p_output, in_cipher, &iv1, &iv2, key);
|
||||
in_cipher += kTeaBlockSize;
|
||||
p_output += kTeaBlockSize;
|
||||
}
|
||||
|
||||
decrypt_round(header + kTeaBlockSize, in_cipher, &iv1, &iv2, key);
|
||||
p_output[0] = header[kTeaBlockSize];
|
||||
}
|
||||
// Validate zero padding
|
||||
auto verify = Endian::be_u64_read(header + kTeaBlockSize) << 8;
|
||||
if (verify != 0) {
|
||||
result.resize(0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -6,14 +6,12 @@
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
std::vector<uint8_t> tc_tea_cbc_decrypt(std::span<uint8_t> cipher,
|
||||
const uint32_t *key);
|
||||
std::vector<uint8_t> tc_tea_cbc_decrypt(std::span<uint8_t> cipher, const uint32_t* key);
|
||||
|
||||
inline std::vector<uint8_t> tc_tea_cbc_decrypt(std::span<uint8_t> cipher,
|
||||
const uint8_t *key) {
|
||||
uint32_t key_u32[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
key_u32[i] = Endian::be_u32_read(key + i * 4);
|
||||
}
|
||||
return tc_tea_cbc_decrypt(cipher, key_u32);
|
||||
inline std::vector<uint8_t> tc_tea_cbc_decrypt(std::span<uint8_t> cipher, const uint8_t* key) {
|
||||
uint32_t key_u32[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
key_u32[i] = Endian::be_u32_read(key + i * 4);
|
||||
}
|
||||
return tc_tea_cbc_decrypt(cipher, key_u32);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user