Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a47024f029 | |||
| 4697e8487a | |||
| 729cd72330 | |||
| ddecaeee6b | |||
| 15df2fab99 | |||
| 93c79d850a | |||
| 3254c24f0a | |||
| fc42ade509 | |||
| 989ed8e60c |
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
193
src/data_format/_pl/v1_13_0/blob_impl.cpp
Normal file
193
src/data_format/_pl/v1_13_0/blob_impl.cpp
Normal 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
|
||||
22
src/data_format/_pl/v1_13_0/blob_impl.h
Normal file
22
src/data_format/_pl/v1_13_0/blob_impl.h
Normal 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
|
||||
145
src/data_format/_pl/v1_13_0/xtea_ll.hpp
Normal file
145
src/data_format/_pl/v1_13_0/xtea_ll.hpp
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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));
|
||||
|
||||
// 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() {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
164
src/third-party/chacha20.hpp
vendored
Normal 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
84
src/util/aes.cpp
Normal 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
31
src/util/aes.h
Normal 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
|
||||
23
xmake.lua
23
xmake.lua
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user