From 3b461cd845b929bfabea7a7e58fe46f1f0ad5e87 Mon Sep 17 00:00:00 2001 From: neru Date: Sun, 12 Apr 2026 20:56:25 -0300 Subject: [PATCH] feat: add dbdcrypt --- src/unlocker/dbdcrypt.cpp | 369 ++++++++++++++++++++++++++++++++++++++ src/unlocker/dbdcrypt.h | 49 +++++ 2 files changed, 418 insertions(+) create mode 100644 src/unlocker/dbdcrypt.cpp create mode 100644 src/unlocker/dbdcrypt.h diff --git a/src/unlocker/dbdcrypt.cpp b/src/unlocker/dbdcrypt.cpp new file mode 100644 index 0000000..69726f1 --- /dev/null +++ b/src/unlocker/dbdcrypt.cpp @@ -0,0 +1,369 @@ +#include "dbdcrypt.h" + +#include + +#include + +#include + +#include +#include +#include +#include + +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 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 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 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 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 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 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 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 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& cipherText, const std::vector& 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& plainText, const std::vector& 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 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 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& 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& 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 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 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 DBDCrypt::transformCDNKey(const std::string& b64CDNKey) +{ + auto encryptedKey = b64Dec(b64CDNKey); + std::vector 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 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; +} diff --git a/src/unlocker/dbdcrypt.h b/src/unlocker/dbdcrypt.h new file mode 100644 index 0000000..f4a64e8 --- /dev/null +++ b/src/unlocker/dbdcrypt.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +// 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& cipherText, const std::vector& key); + static std::string aesECBEncrypt(const std::vector& plainText, const std::vector& key); + + static std::vector b64Dec(const std::string& input); + static std::string b64Enc(const std::vector& input); + + static std::string zlibDecompress(const std::vector& compressed); + static std::vector zLibCompress(const std::string& data); + + static std::vector transformCDNKey(const std::string& b64CDNKey); + static std::string shiftKeyID(const std::string& id, int shift); +};