Compare commits

9 Commits

Author SHA1 Message Date
a47024f029 fix: fix build.
Some checks failed
Build / build (debug, map[arch:arm64 os:linux runner:ubuntu-24.04-arm toolchain:gcc-14]) (push) Has been cancelled
Build / build (debug, map[arch:arm64 os:macosx runner:macos-latest toolchain:xcode]) (push) Has been cancelled
Build / build (debug, map[arch:x64 os:windows runner:windows-latest toolchain:clang]) (push) Has been cancelled
Build / build (debug, map[arch:x86_64 os:linux runner:ubuntu-latest toolchain:gcc-14]) (push) Has been cancelled
Build / build (release, map[arch:arm64 os:linux runner:ubuntu-24.04-arm toolchain:gcc-14]) (push) Has been cancelled
Build / build (release, map[arch:arm64 os:macosx runner:macos-latest toolchain:xcode]) (push) Has been cancelled
Build / build (release, map[arch:x64 os:windows runner:windows-latest toolchain:clang]) (push) Has been cancelled
Build / build (release, map[arch:x86_64 os:linux runner:ubuntu-latest toolchain:gcc-14]) (push) Has been cancelled
2025-08-22 02:10:17 +08:00
4697e8487a chore: add some notes. 2025-08-22 01:48:39 +08:00
729cd72330 feat: complete runtimedata decryption. 2025-08-20 14:44:56 +08:00
ddecaeee6b feat: add openssl aes-ecb wrapper. 2025-08-20 14:44:38 +08:00
15df2fab99 refactor: subdividing IOException. 2025-08-20 14:43:52 +08:00
93c79d850a refactor: use magic_enum instead of switch. 2025-08-20 14:43:33 +08:00
3254c24f0a feat: impl XTEA-LL algorithm. 2025-08-19 21:54:15 +08:00
fc42ade509 refactor: rewrite streamed_io. 2025-08-18 18:35:05 +08:00
989ed8e60c feat: start to add preloader v1.13.0 support. 2025-08-17 23:58:43 +08:00
18 changed files with 827 additions and 92 deletions

View File

@@ -39,15 +39,15 @@ namespace di::data_format::_pl::v1_12_0 {
void MagicBlobImpl::read(const fs::path& path) {
StreamedIO::read(path);
m_stored_seed = eat<uint64_t>();
m_stored_seed = get<uint64_t>();
m_query_seed = hash_qseed(m_stored_seed);
rva_t n_rva{};
while (next() != EOF) {
auto flags = eat_varint<uint64_t>();
auto rva = eat_varint<rva_t>();
auto hash = eat<hash_t>();
while (!is_eof()) {
auto flags = get_varint<uint64_t>();
auto rva = get_varint<rva_t>();
auto hash = get<hash_t>();
// What is stored in the original format is not the RVA itself, but the
// difference with the previous entry (in MagicBlob, RVA is sorted from

View File

@@ -6,8 +6,7 @@ namespace di::data_format::_pl::v1_12_0 {
class MagicBlobImpl : public MagicBlob {
public:
using for_each_callback_t =
std::function<void(hash_t, shared_entry_t const&)>;
MagicBlobImpl() = default;
void read(const fs::path& path) override;

View File

@@ -0,0 +1,193 @@
#include "blob_impl.h"
#include "xtea_ll.hpp"
#include "third-party/chacha20.hpp"
#include "util/aes.h"
namespace {
// clang-format off
constexpr auto BLOB_MAGIC = 0x534442666F0B0E0BuLL;
constexpr auto BLOB_HEADER_SIZE = 0x14;
constexpr std::array<uint8_t, 16> CHACHA20_MAGIC = { // lldev's ninjutsu.
0xE5, 0x93, 0x8E, 0xE6,
0x88, 0x91, 0xE8, 0x8D,
0x89, 0x52, 0x42, 0x4E,
0xE6, 0x80, 0x8E, 0xE4,
};
constexpr std::array<uint8_t, 32> CHACHA20_KEY = {
0xE5, 0x90, 0x93, 0xE6,
0x88, 0x91, 0xE4, 0xB8,
0x80, 0xE8, 0xB7, 0xB3,
0xE9, 0x87, 0x8A, 0xE6,
0x94, 0xBE, 0xE5, 0xBF,
0x8D, 0xE6, 0x9C, 0xAF,
0x61, 0x77, 0x63, 0x7A,
0x72, 0x62, 0x6E, 0x00,
};
constexpr std::array<uint32_t, 4> DATAKEY_PART1_PRECURSOR = {
0xE79E8CE7,
0xADE5818C,
0x9E9BE590,
0x0082ADE9,
};
constexpr std::array<uint8_t, 32> DATAKEY_PART2_PRECURSOR_1 = {
0x9C, 0x99, 0x18, 0xB8, 0x74, 0xF1, 0xF8, 0x6B,
0x25, 0xAE, 0x87, 0xCE, 0x61, 0x32, 0xA0, 0x0C,
0x01, 0xF3, 0x3A, 0x81, 0x5A, 0x16, 0x06, 0x4E,
0x93, 0x16, 0xBE, 0xE8, 0xE3, 0xCA, 0x6F, 0xDF,
};
constexpr std::array<uint8_t, 32> DATAKEY_PART2_PRECURSOR_2 = {
0x45, 0xB6, 0x4F, 0xDC, 0xAA, 0x0B, 0x4C, 0x67,
0x11, 0x91, 0x3B, 0x75, 0x91, 0x83, 0x98, 0x05,
0x77, 0x51, 0x5F, 0xD2, 0x09, 0x37, 0x1B, 0x36,
0x0A, 0x77, 0x9B, 0x5E, 0xD6, 0xD9, 0xEE, 0x42,
};
// clang-format on
} // namespace
/*
Magic Blob File Format
v1.13.0
--------------------------------------- 0x0
| (u64) Magic Number |
--------------------------------------- 0x8
| (u32) UNK |
--------------------------------------- 0xC
| (u64) ChaCha20 Nonce |
--------------------------------------- 0x14
| ***** ChaCha20 Encrypted Data ***** |
--------------------------------------- EOF
ChaCha20 Decrypted Data
--------------------------------------- 0x14 (before file header)
| (u64) UNK |
--------------------------------------- 0x1c
| (u64) AES Encrypted Data Part 1 Len |
--------------------------------------- 0x24
| (b16) Stored Key Lower 16 Bytes |
--------------------------------------- 0x34
| **** AES Encrypted Data Part 1 **** |
--------------------------------------- 0x34 + enc_len_p1
| (b16) Stored Key Higher 16 Bytes |
--------------------------------------- 0x34 + enc_len_p1 + 0x10
| (u64) AES Encrypted Data Part 2 Len |
--------------------------------------- 0x34 + enc_len_p1 + 0x18
| **** AES Encrypted Data Part 2 **** |
--------------------------------------- EOF
AES Decrypted Data (Part 1)
--------------------------------------- 0x0
| (u64) UNK_DATA |
--------------------------------------- 0x8
| ........ (repeat N times) ........ |
--------------------------------------- EOF
AES Decrypted Data (Part 2)
--------------------------------------- 0x0
| (u64) UNK_DATA |
| (u64) Flag |
--------------------------------------- 0xF
| ........ (repeat N times) ........ |
--------------------------------------- EOF
*/
namespace di::data_format::_pl::v1_13_0 {
void MagicBlobImpl::read(const fs::path& path) {
StreamedIO::read(path);
// header
auto magic = get<uint64_t>();
auto unk_1 = get<uint32_t>();
auto chacha20_nonce = get<uint64_t>();
if (magic != BLOB_MAGIC) {
std::println("error!");
return;
}
if (unk_1 != 1) {
std::println("error!");
return;
}
// perform chacha20 decryption
Chacha20 chacha20(
CHACHA20_MAGIC.data(),
CHACHA20_KEY.data(),
(uint8_t*)&chacha20_nonce,
0
);
chacha20.decrypt(
reinterpret_cast<uint8_t*>(underlying().data()) + BLOB_HEADER_SIZE,
size() - BLOB_HEADER_SIZE
);
// chacha20 decrypted
XTeaLL::Block data_key;
auto unk_2 = get<uint64_t>(); // may be signed, L241
auto enc_data_part1_len = get<uint64_t>();
data_key.parts[0] = get<uint32_t>(); // L231
data_key.parts[1] = get<uint32_t>();
data_key.parts[2] = get<uint32_t>();
data_key.parts[3] = get<uint32_t>();
seek(position() + enc_data_part1_len);
data_key.parts[4] = get<uint32_t>(); // L244
data_key.parts[5] = get<uint32_t>();
data_key.parts[6] = get<uint32_t>();
data_key.parts[7] = get<uint32_t>();
auto enc_data_part2_len = get<uint64_t>();
// perform aes decryption
auto enc_data_part1_begin = underlying().data() + 0x34;
auto enc_data_part2_begin =
underlying().data() + 0x34 + enc_data_part1_len + 0x18;
XTeaLL({
DATAKEY_PART1_PRECURSOR[0],
DATAKEY_PART1_PRECURSOR[1],
DATAKEY_PART1_PRECURSOR[2],
DATAKEY_PART1_PRECURSOR[3],
})
.decrypt(data_key);
std::array<uint8_t, 32> aes_key_part1;
std::memcpy(aes_key_part1.data(), (char*)&data_key.parts, 32);
std::array<uint8_t, 32> aes_key_part2;
for (int i = 0; i < 32; i++) {
aes_key_part2[i] = DATAKEY_PART2_PRECURSOR_1[i]
^ DATAKEY_PART2_PRECURSOR_2[i] ^ aes_key_part1[i];
}
auto data_part1 = AES(aes_key_part1)
.decrypt(
{reinterpret_cast<uint8_t*>(enc_data_part1_begin),
enc_data_part1_len}
);
auto data_part2 = AES(aes_key_part2)
.decrypt(
{reinterpret_cast<uint8_t*>(enc_data_part2_begin),
enc_data_part2_len}
);
}
} // namespace di::data_format::_pl::v1_13_0

View File

@@ -0,0 +1,22 @@
#pragma once
#include "data_format/magic_blob.h"
namespace di::data_format::_pl::v1_13_0 {
class MagicBlobImpl : public MagicBlob {
public:
MagicBlobImpl() = default;
void read(const fs::path& path) override;
shared_entry_t query(std::string_view symbol) const override {
return std::make_shared<MagicEntry>(0, 0);
}
void for_each(const for_each_callback_t& callback) const override {}
size_t count() const override { return 0; }
};
} // namespace di::data_format::_pl::v1_13_0

View File

@@ -0,0 +1,145 @@
#pragma once
// An xtea variant algorithm.
// IDX = 11, L247 ~ L400
namespace di::data_format::_pl::v1_13_0 {
class XTeaLL {
public:
struct Block {
std::array<uint32_t, 8> parts;
};
static_assert(sizeof(Block) == 32);
explicit XTeaLL(const Block& sample) : m_sample(sample) {};
void decrypt(Block& data) {
transform_block_a_v(data.parts[0], data.parts[1]);
transform_block_b_v(data.parts[2], data.parts[3]);
transform_block_b_i(data.parts[4], data.parts[5]);
transform_block_a_i(data.parts[6], data.parts[7]);
}
private:
using u32 = uint32_t;
using u16 = uint16_t;
using u8 = uint8_t;
// ----------------------------------------
const static u32 delta = 0x9E3779B9;
const static u32 mask_1 = 0x0000BEEF;
const static u32 mask_2 = 0xFFFF4110;
const static u32 mask_3 = 0x001FFFFC;
const static u32 mask_4 = 0xFFFFFFFC;
// ----------------------------------------
Block const& m_sample;
// ----------------------------------------
constexpr auto mix32(u32 x) { return x + ((x >> 5) ^ (x << 4)); };
constexpr auto beef(u32 a, u32 b, u32 c, u32 sum) {
return (((u16)a + (u16)b - (u16)c) & mask_1) | (sum & mask_2);
};
constexpr auto idx_a(u32 sum1) {
u32 t = (sum1 >> 11);
return t & (t ^ mask_3);
};
template <bool Inverse>
constexpr auto idx_b(u32 k, u32 s) {
if (!Inverse) return ~((u8)k + (u8)s) & 3u;
u32 t = k + s;
return t & (t ^ mask_4);
};
template <bool Inverse>
constexpr auto idx_c(u32 sum1) {
if constexpr (!Inverse) return ((sum1 >> 9) & 0xC) / 4;
return idx_a(sum1);
};
template <bool Inverse>
constexpr auto idx_d(u32 vsum2, u32 vrsum2) {
if constexpr (!Inverse) return ~(u8)vrsum2 & 3u;
return vsum2 & (vsum2 ^ mask_4);
};
// ----------------------------------------
template <bool Inverse>
constexpr void transform_block_a(u32& v1, u32& v2) {
u32 sum1 = delta * 64;
u32 sum2 = delta * 63;
u32 rsum1 = ~sum1;
u32 rsum2 = ~sum2;
for (int i = 0; i < 64; i++) {
u32 s1 = m_sample.parts.at(idx_c<Inverse>(sum1));
u32 s2 = m_sample.parts.at(idx_d<Inverse>(sum2, rsum2));
if constexpr (!Inverse) {
v2 -= mix32(v1) ^ beef(0, rsum1, s1, sum1 + s1) ^ mask_1;
v1 -= (sum2 + s2) ^ (mix32(v2) & ((sum2 + s2) ^ (rsum2 - s2)));
} else {
v2 -= (sum1 + s1) ^ (mix32(v1) & ((sum1 + s1) ^ (rsum1 - s1)));
v1 -= mix32(v2) ^ beef(0, rsum2, s2, sum2 + s2) ^ mask_1;
}
sum1 -= delta;
sum2 -= delta;
rsum1 += delta;
rsum2 += delta;
}
};
template <bool Inverse>
constexpr void transform_block_b(u32& v1, u32& v2) {
const u32 md1 = delta * 64;
const u32 md2 = delta * 63;
const u32 rmd1 = ~md1;
const u32 rmd2 = ~md2;
u32 sum1 = 0;
u32 sum2 = 0;
for (int i = 0; i < 64; i++) {
u32 c1 = md1 + sum2;
u32 s1 = m_sample.parts.at(idx_a(c1));
v2 -= mix32(v1) ^ beef(sum1, rmd1, s1, s1 + c1) ^ mask_1;
if constexpr (!Inverse) {
u32 s2 = m_sample.parts.at(idx_b<Inverse>(rmd2, sum1));
v1 -=
mix32(v2) ^ beef(rmd2, sum1, s2, s2 + sum2 + md2) ^ mask_1;
} else {
u32 s2 = m_sample.parts.at(idx_b<Inverse>(md2, sum2));
u32 c2 = s2 + md2 + sum2;
v1 -= c2 ^ (mix32(v2) & (c2 ^ (sum1 + rmd2 - s2)));
}
sum1 += delta;
sum2 -= delta;
}
};
constexpr void transform_block_a_v(u32& v1, u32& v2) {
return transform_block_a<false>(v1, v2);
}
constexpr void transform_block_a_i(u32& v1, u32& v2) {
return transform_block_a<true>(v1, v2);
}
constexpr void transform_block_b_v(u32& v1, u32& v2) {
return transform_block_b<false>(v1, v2);
}
constexpr void transform_block_b_i(u32& v1, u32& v2) {
return transform_block_b<true>(v1, v2);
}
};
} // namespace di::data_format::_pl::v1_13_0

View File

@@ -1,6 +1,7 @@
#include "data_format/magic_blob.h"
#include "data_format/_pl/v1_12_0/blob_impl.h"
#include "data_format/_pl/v1_13_0/blob_impl.h"
namespace di::data_format {
@@ -8,6 +9,8 @@ MagicBlob::blob_t MagicBlob::create(FormatVersion version) {
switch (version) {
case V1_12_0:
return std::make_unique<_pl::v1_12_0::MagicBlobImpl>();
case V1_13_0:
return std::make_unique<_pl::v1_13_0::MagicBlobImpl>();
}
return nullptr;
}

View File

@@ -9,6 +9,7 @@ class MagicBlob : public io::StreamedIO {
public:
enum FormatVersion {
V1_12_0,
V1_13_0,
};
using blob_t = std::unique_ptr<MagicBlob>;

View File

@@ -1,6 +1,6 @@
#pragma once
#include "util/string.h"
#include <magic_enum.hpp>
namespace di {
@@ -26,33 +26,11 @@ public:
constexpr DeclType(Enum value) : m_data(value) {}
constexpr explicit DeclType(std::string_view str) {
using namespace util::string;
// clang-format off
switch (H(str)) {
#define HSTR(x) \
case H(#x): \
m_data = x; \
break
HSTR(Function);
HSTR(CXXDeductionGuide);
HSTR(CXXMethod);
HSTR(CXXConstructor);
HSTR(CXXConversion);
HSTR(CXXDestructor);
HSTR(Var);
HSTR(Decomposition);
HSTR(ImplicitParam);
HSTR(OMPCapturedExpr);
HSTR(ParamVar);
HSTR(VarTemplateSpecialization);
#undef HSTR
default:
throw ConvertEnumException(str, TypeOnly<Enum>{});
if (auto value = magic_enum::enum_cast<Enum>(str)) {
m_data = *value;
} else {
throw EnumCastException(str, TypeOnly<Enum>{});
}
// clang-format on
}
constexpr bool is_function() const {
@@ -65,32 +43,12 @@ public:
constexpr Enum data() const { return m_data; }
constexpr std::string string() const {
using namespace util::string;
// clang-format off
switch (m_data) {
#define HSTR(x) \
case x: \
return #x;
HSTR(Function);
HSTR(CXXDeductionGuide);
HSTR(CXXMethod);
HSTR(CXXConstructor);
HSTR(CXXConversion);
HSTR(CXXDestructor);
HSTR(Var);
HSTR(Decomposition);
HSTR(ImplicitParam);
HSTR(OMPCapturedExpr);
HSTR(ParamVar);
HSTR(VarTemplateSpecialization);
#undef HSTR
default:
throw ConvertEnumException(m_data);
auto value = magic_enum::enum_name(m_data);
if (value.empty()) {
throw EnumCastException(m_data);
}
// clang-format on
return std::string(value);
}
constexpr bool operator==(const DeclType& other) const {

View File

@@ -107,13 +107,12 @@ public:
constexpr std::string category() const { return "exception.unix"; }
};
class ConvertEnumException : public RuntimeException<ConvertEnumException> {
class EnumCastException : public RuntimeException<EnumCastException> {
public:
// TODO: compile-time reflection.
// TODO: remove helper.
template <Enumerate T>
explicit ConvertEnumException(T enum_val)
explicit EnumCastException(T enum_val)
: RuntimeException("Unable to convert string to enumeration value because "
"input value is bad.") {
add_context("enum_type", typeid(T).name());
@@ -121,14 +120,14 @@ public:
}
template <typename T>
explicit ConvertEnumException(std::string_view enum_str, TypeOnly<T>)
explicit EnumCastException(std::string_view enum_str, TypeOnly<T>)
: RuntimeException("Unable to convert enumeration value to string because "
"input value is bad.") {
add_context("enum_type", typeid(T).name());
add_context("string", enum_str);
}
constexpr std::string category() const { return "exception.enumconvert"; }
constexpr std::string category() const { return "exception.magicenum"; }
};
} // namespace di

View File

@@ -17,6 +17,30 @@ public:
constexpr std::string category() const { return "exception.io"; }
};
class OutOfBoundException : public IOException {
public:
explicit OutOfBoundException(size_t op, size_t buffer_size)
: IOException("An attempt was made to move the read pointer outside the "
"buffer bounds.") {
add_context_v_hex("op", op);
add_context_v_hex("buffer_size", buffer_size);
}
constexpr std::string category() const { return "exception.io.outofbound"; }
};
class CorruptedVarIntException : public IOException {
public:
explicit CorruptedVarIntException(std::string_view msg, size_t position)
: IOException("{}", msg) {
add_context_v_hex("position", position);
}
constexpr std::string category() const {
return "exception.io.corruptedvarint";
}
};
class UnableToOpenException : public UnixException {
public:
explicit UnableToOpenException(const fs::path& path) {

View File

@@ -3,13 +3,39 @@
namespace di::io {
void StreamedIO::read(const fs::path& path) {
m_file_stream.open(path, std::ios::binary);
if (!m_file_stream) throw UnableToOpenException(path);
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
throw UnableToOpenException(path);
}
auto size = static_cast<size_t>(file.tellg());
file.seekg(0, std::ios::beg);
m_buffer.resize(size);
if (!file.read(m_buffer.data(), size)) {
throw IOException(
"Failed to read {} bytes from {}.",
size,
path.string()
);
}
m_position = 0; // reset
}
void StreamedIO::write(const fs::path& path) const {
std::fstream ofs(path, std::ios::binary);
ofs << m_file_stream.rdbuf();
std::ofstream file(path, std::ios::binary | std::ios::trunc);
if (!file) {
throw UnableToOpenException(path);
}
if (!file.write(m_buffer.data(), m_buffer.size())) {
throw IOException(
"Failed to write {} bytes to {}.",
m_buffer.size(),
path.string()
);
}
}
} // namespace di::io

View File

@@ -6,38 +6,109 @@ namespace di::io {
class StreamedIO : public io::IOBase {
public:
explicit StreamedIO(std::string data) : m_buffer(std::move(data)) {}
explicit StreamedIO(const fs::path& path) { read(path); }
StreamedIO() = default;
void read(const fs::path& path) override;
void write(const fs::path& path) const override;
constexpr auto next() { return m_file_stream.peek(); }
// Avoid confusion with the function that opens a file, so use eat
// instead of read. Maybe come up with another name for write as well.
//
// TODO: write<T>() method.
template <typename T>
constexpr T eat() {
template <TriviallyCopyable T>
constexpr T get() {
if (m_position + sizeof(T) > m_buffer.size()) {
throw OutOfBoundException(sizeof(T), m_buffer.size());
}
T value;
m_file_stream.read((char*)&value, sizeof(T));
return value;
// To avoid violating the strict aliasing rule, so we use memcpy.
std::memcpy(&value, m_buffer.data() + m_position, sizeof(T));
m_position += sizeof(T);
if constexpr (std::endian::native != std::endian::little) {
return std::byteswap(value);
} else {
return value;
}
}
template <std::unsigned_integral T>
constexpr T eat_varint() {
T res = 0;
int shift = 0;
constexpr T get_varint() {
T res = 0;
int shift = 0;
const int max_shift = sizeof(T) * 8;
while (true) {
auto byte = m_file_stream.get();
res |= static_cast<T>(byte & 0x7F) << shift;
if ((byte & 0x80) == 0) break;
if (m_position >= m_buffer.size()) {
throw CorruptedVarIntException(
"Incomplete VarInt at end of buffer.",
m_position
);
}
if (shift >= max_shift) {
throw CorruptedVarIntException(
"VarInt is too long for the requested type.",
m_position
);
}
auto byte = static_cast<std::byte>(m_buffer[m_position++]);
res |= static_cast<T>(byte & std::byte{0x7F}) << shift;
if ((byte & std::byte{0x80}) == std::byte{0}) {
break;
}
shift += 7;
}
return res;
}
template <TriviallyCopyable T>
constexpr void put(T value) {
if constexpr (std::endian::native != std::endian::little) {
value = std::byteswap(value);
}
auto data = reinterpret_cast<const char*>(&value);
if (m_position + sizeof(T) > m_buffer.size()) {
m_buffer.append(data, sizeof(T));
} else {
std::memcpy(m_buffer.data() + m_position, data, sizeof(T));
}
m_position += sizeof(T);
}
template <std::unsigned_integral T>
constexpr void put_varint(T value) {
while (value >= 0x80) {
std::byte to_write = static_cast<std::byte>((value & 0x7F) | 0x80);
put(static_cast<char>(to_write));
value >>= 7;
}
put(static_cast<char>(value));
}
constexpr void seek(size_t pos) {
if (pos > m_buffer.size()) {
throw OutOfBoundException(pos, m_buffer.size());
}
m_position = pos;
}
size_t size() const { return m_buffer.size(); }
bool is_eof() const { return m_position >= m_buffer.size(); }
constexpr size_t position() const { return m_position; }
std::string& underlying() { return m_buffer; }
private:
std::ifstream m_file_stream;
std::string m_buffer;
size_t m_position;
};
} // namespace di::io

View File

@@ -13,6 +13,9 @@ constexpr auto underlying_value(T v) {
return static_cast<std::underlying_type_t<decltype(v)>>(v);
}
template <typename T>
concept TriviallyCopyable = std::is_trivially_copyable_v<T>;
#if __cpp_constexpr >= 202211L // complete c++23 constexpr
#define DI_CONSTEXPR constexpr
#else

View File

@@ -16,7 +16,10 @@ using namespace llvm;
#include <functional>
#include <memory>
#include <bit>
#include <cstring>
#include <span>
#include <string>
#include <string_view>

164
src/third-party/chacha20.hpp vendored Normal file
View File

@@ -0,0 +1,164 @@
#pragma once
// --------------------------------------------------
// From: https://github.com/983/ChaCha20 (Unlicense)
// Modified to support custom magic constants.
// --------------------------------------------------
// This is high quality software because the includes are sorted alphabetically.
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
struct Chacha20Block {
// This is basically a random number generator seeded with key and nonce.
// Generates 64 random bytes every time count is incremented.
uint32_t state[16];
static uint32_t rotl32(uint32_t x, int n) {
return (x << n) | (x >> (32 - n));
}
static uint32_t pack4(const uint8_t* a) {
return uint32_t(a[0] << 0 * 8) | uint32_t(a[1] << 1 * 8)
| uint32_t(a[2] << 2 * 8) | uint32_t(a[3] << 3 * 8);
}
static void unpack4(uint32_t src, uint8_t* dst) {
dst[0] = (src >> 0 * 8) & 0xff;
dst[1] = (src >> 1 * 8) & 0xff;
dst[2] = (src >> 2 * 8) & 0xff;
dst[3] = (src >> 3 * 8) & 0xff;
}
Chacha20Block(const uint8_t key[32], const uint8_t nonce[8])
: Chacha20Block((uint8_t*)"expand 32-byte k", key, nonce) {}
Chacha20Block(
const uint8_t magic[16],
const uint8_t key[32],
const uint8_t nonce[8]
) {
state[0] = pack4(magic + 0 * 4);
state[1] = pack4(magic + 1 * 4);
state[2] = pack4(magic + 2 * 4);
state[3] = pack4(magic + 3 * 4);
state[4] = pack4(key + 0 * 4);
state[5] = pack4(key + 1 * 4);
state[6] = pack4(key + 2 * 4);
state[7] = pack4(key + 3 * 4);
state[8] = pack4(key + 4 * 4);
state[9] = pack4(key + 5 * 4);
state[10] = pack4(key + 6 * 4);
state[11] = pack4(key + 7 * 4);
// 64 bit counter initialized to zero by default.
state[12] = 0;
state[13] = 0;
state[14] = pack4(nonce + 0 * 4);
state[15] = pack4(nonce + 1 * 4);
}
void set_counter(uint64_t counter) {
// Want to process many blocks in parallel?
// No problem! Just set the counter to the block you want to process.
state[12] = uint32_t(counter);
state[13] = counter >> 32;
}
void next(uint32_t result[16]) {
// This is where the crazy voodoo magic happens.
// Mix the bytes a lot and hope that nobody finds out how to undo it.
for (int i = 0; i < 16; i++) result[i] = state[i];
#define CHACHA20_QUARTERROUND(x, a, b, c, d) \
x[a] += x[b]; \
x[d] = rotl32(x[d] ^ x[a], 16); \
x[c] += x[d]; \
x[b] = rotl32(x[b] ^ x[c], 12); \
x[a] += x[b]; \
x[d] = rotl32(x[d] ^ x[a], 8); \
x[c] += x[d]; \
x[b] = rotl32(x[b] ^ x[c], 7);
for (int i = 0; i < 10; i++) {
CHACHA20_QUARTERROUND(result, 0, 4, 8, 12)
CHACHA20_QUARTERROUND(result, 1, 5, 9, 13)
CHACHA20_QUARTERROUND(result, 2, 6, 10, 14)
CHACHA20_QUARTERROUND(result, 3, 7, 11, 15)
CHACHA20_QUARTERROUND(result, 0, 5, 10, 15)
CHACHA20_QUARTERROUND(result, 1, 6, 11, 12)
CHACHA20_QUARTERROUND(result, 2, 7, 8, 13)
CHACHA20_QUARTERROUND(result, 3, 4, 9, 14)
}
#undef CHACHA20_QUARTERROUND
for (int i = 0; i < 16; i++) result[i] += state[i];
uint32_t* counter = state + 12;
// increment counter
counter[0]++;
if (0 == counter[0]) {
// wrap around occured, increment higher 32 bits of counter
counter[1]++;
// Limited to 2^64 blocks of 64 bytes each.
// If you want to process more than 1180591620717411303424 bytes
// you have other problems.
// We could keep counting with counter[2] and counter[3] (nonce),
// but then we risk reusing the nonce which is very bad.
assert(0 != counter[1]);
}
}
void next(uint8_t result8[64]) {
uint32_t temp32[16];
next(temp32);
for (size_t i = 0; i < 16; i++) unpack4(temp32[i], result8 + i * 4);
}
};
struct Chacha20 {
// XORs plaintext/encrypted bytes with whatever Chacha20Block generates.
// Encryption and decryption are the same operation.
// Chacha20Blocks can be skipped, so this can be done in parallel.
// If keys are reused, messages can be decrypted.
// Known encrypted text with known position can be tampered with.
// See https://en.wikipedia.org/wiki/Stream_cipher_attack
Chacha20Block block;
uint8_t keystream8[64];
size_t position;
Chacha20(
const uint8_t magic[16],
const uint8_t key[32],
const uint8_t nonce[8],
uint64_t counter
)
: block(magic, key, nonce),
position(64) {
block.set_counter(counter);
}
Chacha20(const uint8_t key[32], const uint8_t nonce[8], uint64_t counter)
: block(key, nonce),
position(64) {
block.set_counter(counter);
}
void crypt(uint8_t* bytes, size_t n_bytes) {
for (size_t i = 0; i < n_bytes; i++) {
if (position >= 64) {
block.next(keystream8);
position = 0;
}
bytes[i] ^= keystream8[position];
position++;
}
}
// Just an alias.
void decrypt(uint8_t* bytes, size_t n_bytes) { crypt(bytes, n_bytes); }
};

84
src/util/aes.cpp Normal file
View File

@@ -0,0 +1,84 @@
#include "aes.h"
#include <openssl/err.h>
#include <openssl/evp.h>
namespace di {
void handle_openssl_errors() {
std::string str;
unsigned long code;
while ((code = ERR_get_error()) != 0) {
char buffer[256];
ERR_error_string_n(code, buffer, sizeof(buffer));
str += std::string(buffer) + "\n";
}
if (str.empty()) str = "Unknown error.";
throw OpenSSLException("{}", str);
}
AES::AES(const std::span<uint8_t>& key) : m_ctx(nullptr) {
m_ctx = EVP_CIPHER_CTX_new();
if (!m_ctx) {
handle_openssl_errors();
}
const EVP_CIPHER* cipher_type = nullptr;
switch (key.size()) {
case 16: // AES-128
cipher_type = EVP_aes_128_ecb();
break;
case 24: // AES-192
cipher_type = EVP_aes_192_ecb();
break;
case 32: // AES-256
cipher_type = EVP_aes_256_ecb();
break;
default:
EVP_CIPHER_CTX_free(m_ctx);
throw OpenSSLException("Invalid key size. Must be 16/24/32 bytes.");
}
if (EVP_DecryptInit_ex(m_ctx, cipher_type, nullptr, key.data(), nullptr)
!= 1) {
EVP_CIPHER_CTX_free(m_ctx);
handle_openssl_errors();
}
}
AES::~AES() {
if (m_ctx) {
EVP_CIPHER_CTX_free(m_ctx);
}
}
std::vector<uint8_t> AES::decrypt(const std::span<uint8_t>& ciphertext) {
if (ciphertext.empty()) {
return {};
}
std::vector<uint8_t> plaintext(ciphertext.size());
int len = 0;
if (EVP_DecryptUpdate(
m_ctx,
plaintext.data(),
&len,
ciphertext.data(),
ciphertext.size()
)
!= 1) {
handle_openssl_errors();
}
int plaintext_len = len;
if (EVP_DecryptFinal_ex(m_ctx, plaintext.data() + len, &len) != 1) {
handle_openssl_errors();
}
plaintext_len += len;
plaintext.resize(plaintext_len);
return plaintext;
}
} // namespace di

31
src/util/aes.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <openssl/types.h>
namespace di {
class AES {
public:
explicit AES(const std::span<uint8_t>& key);
~AES();
AES(const AES&) = delete;
AES(AES&&) = delete;
AES& operator=(const AES&) = delete;
AES& operator=(AES&&) = delete;
std::vector<uint8_t> decrypt(const std::span<uint8_t>& ciphertext);
private:
EVP_CIPHER_CTX* m_ctx;
};
class OpenSSLException : public RuntimeException<OpenSSLException> {
public:
using RuntimeException::RuntimeException;
constexpr std::string category() const { return "exception.ossl"; }
};
} // namespace di

View File

@@ -5,6 +5,7 @@ add_requires('nlohmann_json 3.12.0')
add_requires('xxhash 0.8.3')
add_requires('libllvm 19.1.7')
add_requires('magic_enum 0.9.7')
add_requires('openssl3 3.5.1')
add_requires('boost 1.88.0', {
system = false,
configs = {
@@ -26,11 +27,21 @@ option('symbol-resolver')
if option:value() == 'native' and not is_plat('windows') then
raise('the native symbol resolver does not support this platform.')
end
-- TODO: remove it.
-- The native symbol resolver is completely unavailable,
-- v1.12.0 Problem: RVA not subtracted from ImageBase.
-- v1.13.0 Problem: pl_resolve_symbol does not return the real function address, and needs to handle thunk.
if option:value() == 'native' then
raise('sorry use builtin symbol resolver please.')
end
end)
option_end()
if is_config('symbol-resolver', 'native') then
add_repositories("liteldev-free-repo https://github.com/liteldev-free/xmake-repo.git")
add_repositories('liteldev-free-repo https://github.com/liteldev-free/xmake-repo.git')
-- TODO: need to add an option to select the PreLoader version.
add_requires('preloader 1.13.0')
end
@@ -62,6 +73,8 @@ target('libdi')
'libllvm',
'boost',
'nlohmann_json',
'openssl3',
'magic_enum',
{public = true}
)
@@ -98,10 +111,7 @@ target('askrva')
set_pcxxheader('src/pch.h')
add_deps('libdi')
add_packages(
'argparse',
'magic_enum'
)
add_packages('argparse')
if is_config('symbol-resolver', 'native') then
add_packages('preloader')
@@ -116,8 +126,7 @@ target('blob-extractor')
add_deps('libdi')
add_packages(
'argparse',
'nlohmann_json',
'magic_enum'
'nlohmann_json'
)
target('dumpsym')