feat: add dbdcrypt
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
#include "dbdcrypt.h"
|
||||
|
||||
#include <nerutils/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <openssl/types.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
|
||||
std::string DBDCrypt::decrypt(const std::string& data, const std::string& accessKey, PayloadType* outType)
|
||||
{
|
||||
if (outType) *outType = NONE;
|
||||
|
||||
if (data.starts_with("DbdDAQEB"))
|
||||
{
|
||||
if (outType && *outType == NONE) *outType = TYPE_1;
|
||||
return decType1(data, accessKey, outType);
|
||||
}
|
||||
|
||||
if (data.starts_with("DbdDAgAC"))
|
||||
{
|
||||
if (outType && *outType == NONE) *outType = TYPE_2;
|
||||
return decType2(data, accessKey, outType);
|
||||
}
|
||||
|
||||
if (data.starts_with("DbdDAwAC"))
|
||||
{
|
||||
if (outType && *outType == NONE) *outType = TYPE_3;
|
||||
return decType3(data, accessKey, outType);
|
||||
}
|
||||
|
||||
Log::warning("Attempted to decrypt non encrypted string");
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string DBDCrypt::encrypt(const std::string& data, const std::string& accessKey, PayloadType type,
|
||||
std::string keyId)
|
||||
{
|
||||
if (type == TYPE_1)
|
||||
{
|
||||
auto compressed = zLibCompress(data);
|
||||
if (compressed.empty()) return "";
|
||||
|
||||
std::vector<uint8_t> fullPayload(8, 0);
|
||||
fullPayload.insert(fullPayload.end(), compressed.begin(), compressed.end());
|
||||
return "DbdDAQEB" + b64Enc(fullPayload);
|
||||
}
|
||||
|
||||
if (type == TYPE_3)
|
||||
{
|
||||
auto decodedKey = b64Dec(accessKey);
|
||||
if (decodedKey.empty()) return "";
|
||||
|
||||
std::string shiftedData = data;
|
||||
for (char& c : shiftedData)
|
||||
c = (char)((unsigned char)c - 1);
|
||||
|
||||
std::vector<uint8_t> padded(shiftedData.begin(), shiftedData.end());
|
||||
int padLen = 16 - (padded.size() % 16);
|
||||
if (padLen < 16) padded.insert(padded.end(), padLen, 0);
|
||||
|
||||
auto encryptedBody = aesECBEncrypt(padded, decodedKey);
|
||||
|
||||
if (encryptedBody.empty()) return "";
|
||||
|
||||
std::string shiftedId = shiftKeyID(keyId, -1);
|
||||
std::vector<uint8_t> fullData(shiftedId.begin(), shiftedId.end());
|
||||
fullData.push_back(0); // Null terminator
|
||||
fullData.insert(fullData.end(), (uint8_t*)encryptedBody.data(),
|
||||
(uint8_t*)encryptedBody.data() + encryptedBody.size());
|
||||
|
||||
return "DbdDAwAC" + b64Enc(fullData);
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string DBDCrypt::decType1(const std::string& data, const std::string& key, PayloadType* outType)
|
||||
{
|
||||
if (data.length() < 8) return data;
|
||||
auto decoded = b64Dec(data.substr(8));
|
||||
if (decoded.size() < 4)
|
||||
{
|
||||
Log::error("Type 1 base64 too short ({})", decoded.size());
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<uint8_t> body(decoded.begin() + 4, decoded.end());
|
||||
std::string decompressed = zlibDecompress(body);
|
||||
|
||||
if (decompressed.starts_with("DbdD")) return decrypt(decompressed, key, outType);
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
std::string DBDCrypt::decType2(const std::string& data, const std::string& key, PayloadType* outType)
|
||||
{
|
||||
if (data.length() < 8) return data;
|
||||
|
||||
auto decoded = b64Dec(data.substr(8));
|
||||
if (decoded.empty()) return "";
|
||||
|
||||
std::vector<uint8_t> body = decoded;
|
||||
|
||||
auto transformedKey = transformCDNKey(CDN_KEY_BASE64);
|
||||
|
||||
std::string decrypted = aesECBDecrypt(body, transformedKey);
|
||||
if (decrypted.empty()) return "";
|
||||
|
||||
for (char& c : decrypted)
|
||||
c = (char)((unsigned char)c + 1);
|
||||
|
||||
while (!decrypted.empty() && (unsigned char)decrypted.back() == 1)
|
||||
decrypted.pop_back();
|
||||
|
||||
for (size_t offset : {0ULL, 4ULL})
|
||||
{
|
||||
if (offset + 1 < decrypted.size() && (unsigned char)decrypted[offset] == 0x78)
|
||||
{
|
||||
std::vector<uint8_t> zlibPart((uint8_t*)decrypted.data() + offset,
|
||||
(uint8_t*)decrypted.data() + decrypted.size());
|
||||
std::string decompressed = zlibDecompress(zlibPart);
|
||||
if (!decompressed.empty()) return decompressed;
|
||||
}
|
||||
}
|
||||
|
||||
if (decrypted.starts_with("DbdD")) return decrypt(decrypted, key, outType);
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
std::string DBDCrypt::decType3(const std::string& data, const std::string& key, PayloadType* outType)
|
||||
{
|
||||
if (data.length() < 8) return data;
|
||||
|
||||
auto rawKey = b64Dec(key);
|
||||
auto decoded = b64Dec(data.substr(8));
|
||||
if (decoded.empty()) return "";
|
||||
|
||||
auto it = std::find(decoded.begin(), decoded.end(), 0);
|
||||
if (it == decoded.end()) return "";
|
||||
|
||||
std::vector<uint8_t> body(it + 1, decoded.end());
|
||||
|
||||
std::string decrypted = aesECBDecrypt(body, rawKey);
|
||||
if (decrypted.empty())
|
||||
{
|
||||
Log::error("AES decryption failed (body size: {})", body.size());
|
||||
return "";
|
||||
}
|
||||
|
||||
for (char& c : decrypted)
|
||||
c = (char)((unsigned char)c + 1);
|
||||
|
||||
while (!decrypted.empty() && (unsigned char)decrypted.back() == 1)
|
||||
decrypted.pop_back();
|
||||
|
||||
for (size_t offset : {0ULL, 4ULL})
|
||||
{
|
||||
if (offset + 1 < decrypted.size() && (unsigned char)decrypted[offset] == 0x78)
|
||||
{
|
||||
//Log::verbose("nested zlib at offset {}", offset);
|
||||
std::vector<uint8_t> zlibPart((uint8_t*)decrypted.data() + offset,
|
||||
(uint8_t*)decrypted.data() + decrypted.size());
|
||||
std::string decompressed = zlibDecompress(zlibPart);
|
||||
if (!decompressed.empty())
|
||||
{
|
||||
//Log::verbose("nested zlib decompressed, size: {}", decompressed.length());
|
||||
return decompressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (decrypted.starts_with("DbdD")) return decrypt(decrypted, key, outType);
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
std::string DBDCrypt::aesECBDecrypt(const std::vector<uint8_t>& cipherText, const std::vector<uint8_t>& key)
|
||||
{
|
||||
if (key.size() < 32) return "";
|
||||
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
EVP_DecryptInit_ex(ctx, EVP_aes_256_ecb(), NULL, key.data(), NULL);
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
||||
|
||||
std::string plaintext;
|
||||
plaintext.resize(cipherText.size());
|
||||
|
||||
int len = 0;
|
||||
if (EVP_DecryptUpdate(ctx, (unsigned char*)plaintext.data(), &len, cipherText.data(), (int)cipherText.size()) != 1)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
int outLen = len;
|
||||
|
||||
int finalLen = 0;
|
||||
if (EVP_DecryptFinal_ex(ctx, (unsigned char*)plaintext.data() + len, &finalLen) != 1)
|
||||
{
|
||||
// ignore?
|
||||
}
|
||||
outLen += finalLen;
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
plaintext.resize(outLen);
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
std::string DBDCrypt::aesECBEncrypt(const std::vector<uint8_t>& plainText, const std::vector<uint8_t>& key)
|
||||
{
|
||||
if (key.size() < 32) return "";
|
||||
|
||||
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||
EVP_EncryptInit_ex(ctx, EVP_aes_256_ecb(), NULL, key.data(), NULL);
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
||||
|
||||
std::string ciphertext;
|
||||
ciphertext.resize(plainText.size() + 16);
|
||||
|
||||
int len = 0;
|
||||
if (EVP_EncryptUpdate(ctx, (unsigned char*)ciphertext.data(), &len, plainText.data(), (int)plainText.size()) != 1)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
int outLen = len;
|
||||
|
||||
int finalLen = 0;
|
||||
if (EVP_EncryptFinal_ex(ctx, (unsigned char*)ciphertext.data() + outLen, &finalLen) == 1)
|
||||
{
|
||||
outLen += finalLen;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
ciphertext.resize(outLen);
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DBDCrypt::b64Dec(const std::string& input)
|
||||
{
|
||||
std::string in = input;
|
||||
std::replace(in.begin(), in.end(), '-', '+');
|
||||
std::replace(in.begin(), in.end(), '_', '/');
|
||||
|
||||
BIO *bio, *b64;
|
||||
int decodeLen = (int)in.length();
|
||||
std::vector<uint8_t> buffer(decodeLen);
|
||||
|
||||
bio = BIO_new_mem_buf(in.data(), decodeLen);
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
bio = BIO_push(b64, bio);
|
||||
|
||||
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
|
||||
int len = BIO_read(bio, buffer.data(), decodeLen);
|
||||
BIO_free_all(bio);
|
||||
|
||||
if (len < 0) return {};
|
||||
buffer.resize(len);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string DBDCrypt::b64Enc(const std::vector<uint8_t>& input)
|
||||
{
|
||||
if (input.empty()) return "";
|
||||
|
||||
BIO *bio, *b64;
|
||||
BUF_MEM* bufferPtr;
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
bio = BIO_push(b64, bio);
|
||||
|
||||
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
|
||||
BIO_write(bio, input.data(), (int)input.size());
|
||||
BIO_flush(bio);
|
||||
BIO_get_mem_ptr(bio, &bufferPtr);
|
||||
|
||||
std::string result(bufferPtr->data, bufferPtr->length);
|
||||
BIO_free_all(bio);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string DBDCrypt::zlibDecompress(const std::vector<uint8_t>& compressed)
|
||||
{
|
||||
if (compressed.empty()) return "";
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = (uInt)compressed.size();
|
||||
strm.next_in = (Bytef*)compressed.data();
|
||||
|
||||
if (inflateInit(&strm) != Z_OK) return "";
|
||||
|
||||
std::string result;
|
||||
char buffer[32768];
|
||||
|
||||
do
|
||||
{
|
||||
strm.avail_out = sizeof(buffer);
|
||||
strm.next_out = (Bytef*)buffer;
|
||||
int ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_STREAM_END)
|
||||
{
|
||||
inflateEnd(&strm);
|
||||
return "";
|
||||
}
|
||||
result.append(buffer, sizeof(buffer) - strm.avail_out);
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
inflateEnd(&strm);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DBDCrypt::zLibCompress(const std::string& data)
|
||||
{
|
||||
if (data.empty()) return {};
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
|
||||
if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) return {};
|
||||
|
||||
strm.avail_in = (uInt)data.size();
|
||||
strm.next_in = (Bytef*)data.data();
|
||||
|
||||
std::vector<uint8_t> result;
|
||||
uint8_t buffer[32768];
|
||||
|
||||
do
|
||||
{
|
||||
strm.avail_out = sizeof(buffer);
|
||||
strm.next_out = (Bytef*)buffer;
|
||||
deflate(&strm, Z_FINISH);
|
||||
result.insert(result.end(), buffer, buffer + (sizeof(buffer) - strm.avail_out));
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
deflateEnd(&strm);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DBDCrypt::transformCDNKey(const std::string& b64CDNKey)
|
||||
{
|
||||
auto encryptedKey = b64Dec(b64CDNKey);
|
||||
std::vector<uint8_t> uuidKey(32, 0);
|
||||
std::memcpy(uuidKey.data(), CDN_UUID, std::min((size_t)32, strlen(CDN_UUID)));
|
||||
|
||||
std::string decrypted = aesECBDecrypt(encryptedKey, uuidKey);
|
||||
std::vector<uint8_t> finalKey(decrypted.begin(), decrypted.end());
|
||||
if (finalKey.size() > 32)
|
||||
finalKey.resize(32);
|
||||
else if (finalKey.size() < 32)
|
||||
finalKey.resize(32, 0);
|
||||
return finalKey;
|
||||
}
|
||||
|
||||
std::string DBDCrypt::shiftKeyID(const std::string& id, int shift)
|
||||
{
|
||||
std::string res = id;
|
||||
for (char& c : res)
|
||||
c = (char)((unsigned char)c + shift);
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 9.5.2_live
|
||||
#define ACCESS_KEY "BGz7nwlRX8QP__fzvqrgpNRVqrlEyuY54vuGVAqDO_g="
|
||||
#define KEY_ID "9.5.2_live"
|
||||
|
||||
/*
|
||||
hardcoded variables (they have been the same since like 2017)
|
||||
*/
|
||||
#define CDN_KEY_BASE64 "lEQWeCt51ET+MIuxdTs7Ig/gzVZP2vdkVZA1BDfz+L0="
|
||||
#define CDN_UUID "6EF35759-454D-4EBC-8041-9A94CB99FD5D"
|
||||
|
||||
class DBDCrypt
|
||||
{
|
||||
public:
|
||||
enum PayloadType
|
||||
{
|
||||
NONE = 0,
|
||||
TYPE_1, // compressed
|
||||
TYPE_2, // CDN
|
||||
TYPE_3 // dyn / accesskey
|
||||
};
|
||||
|
||||
static std::string decrypt(const std::string& data, const std::string& accessKey,
|
||||
PayloadType* outType = nullptr);
|
||||
|
||||
static std::string encrypt(const std::string& data, const std::string& accessKey, PayloadType type,
|
||||
std::string keyId);
|
||||
|
||||
private:
|
||||
static std::string decType1(const std::string& data, const std::string& key, PayloadType* outType);
|
||||
static std::string decType2(const std::string& data, const std::string& key, PayloadType* outType);
|
||||
static std::string decType3(const std::string& data, const std::string& key, PayloadType* outType);
|
||||
|
||||
static std::string aesECBDecrypt(const std::vector<uint8_t>& cipherText, const std::vector<uint8_t>& key);
|
||||
static std::string aesECBEncrypt(const std::vector<uint8_t>& plainText, const std::vector<uint8_t>& key);
|
||||
|
||||
static std::vector<uint8_t> b64Dec(const std::string& input);
|
||||
static std::string b64Enc(const std::vector<uint8_t>& input);
|
||||
|
||||
static std::string zlibDecompress(const std::vector<uint8_t>& compressed);
|
||||
static std::vector<uint8_t> zLibCompress(const std::string& data);
|
||||
|
||||
static std::vector<uint8_t> transformCDNKey(const std::string& b64CDNKey);
|
||||
static std::string shiftKeyID(const std::string& id, int shift);
|
||||
};
|
||||
Reference in New Issue
Block a user