Skip to content

Commit fa71365

Browse files
feat!(memory): new memory api
1 parent dd51b45 commit fa71365

File tree

10 files changed

+481
-138
lines changed

10 files changed

+481
-138
lines changed

include/blook/disassembly.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ template <typename Range> class DisassembleRange {
146146
goto retry;
147147
} else {
148148
const auto size = r->getLength();
149+
149150
current_value = InstructionCtx{r.value(), address};
150151
ptr += size;
151152
address += size;

include/blook/memo.h

Lines changed: 160 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
#include <memory>
1111
#include <optional>
1212
#include <span>
13+
#include <string>
1314
#include <string_view>
1415
#include <system_error>
16+
#include <type_traits>
1517
#include <vector>
1618

19+
1720
namespace blook {
1821
class Process;
1922

@@ -38,6 +41,7 @@ class Pointer {
3841

3942
public:
4043
std::shared_ptr<Process> proc = nullptr;
44+
bool is_self() const;
4145
static void *malloc_rwx(size_t size);
4246

4347
static void protect_rwx(void *p, size_t size);
@@ -64,47 +68,120 @@ class Pointer {
6468
Pointer malloc(size_t size,
6569
MemoryProtection protection = MemoryProtection::rw);
6670

67-
std::vector<uint8_t> read(void *ptr, size_t size) const;
71+
std::optional<Thread> create_thread(bool suspended = false);
6872

69-
std::span<uint8_t> read_leaked(void *ptr, size_t size);
73+
// Read operations
74+
/**
75+
* @brief Try to read a byte array from the pointer.
76+
* This function will check if the memory is readable before reading in try
77+
* mode.
78+
*/
79+
std::expected<std::vector<uint8_t>, std::string>
80+
try_read_bytearray(size_t size) const;
81+
82+
std::expected<Pointer, std::string> try_read_pointer() const;
83+
84+
template <typename T>
85+
requires std::is_standard_layout_v<T> && std::is_trivial_v<T>
86+
std::expected<T, std::string> try_read_struct() const {
87+
T val;
88+
auto res = try_read_into(&val, sizeof(T));
89+
if (!res)
90+
return std::unexpected(res.error());
91+
return val;
92+
}
7093

71-
std::expected<void, std::string> write(void *addr, std::span<uint8_t>) const;
94+
std::expected<std::string, std::string>
95+
try_read_utf8_string(size_t length = -1) const;
7296

73-
std::optional<Thread> create_thread(bool suspended = false);
97+
std::expected<std::string, std::string>
98+
try_read_utf16_string(size_t length = -1) const;
7499

75-
template <typename Struct>
76-
inline std::optional<Struct *> read_leaked(void *ptr = nullptr) {
77-
const auto val = read_leaked(ptr, sizeof(Struct));
78-
return reinterpret_cast<Struct *>(val.data());
79-
}
100+
// Write operations
101+
std::expected<void, std::string>
102+
try_write_bytearray(std::span<const uint8_t> data) const;
80103

81-
template <typename Struct>
82-
[[nodiscard]] inline Struct read(size_t ptr = 0) const {
83-
auto data = try_read((void *)ptr, sizeof(Struct));
84-
if (!data.has_value()) {
85-
throw std::runtime_error("Failed to read memory");
86-
}
87-
return *reinterpret_cast<Struct *>(data.value().data());
88-
}
104+
std::expected<void, std::string> try_write_pointer(Pointer ptr) const;
89105

90-
template <typename Struct>
91-
[[nodiscard]] inline auto write(Struct data, size_t ptr = 0) {
92-
return write((void *)ptr, std::span((uint8_t *)&data, sizeof(Struct)));
106+
template <typename T>
107+
requires std::is_standard_layout_v<T> && std::is_trivial_v<T>
108+
std::expected<void, std::string> try_write_struct(const T &value) const {
109+
return try_write_bytearray(
110+
std::span<const uint8_t>((const uint8_t *)&value, sizeof(T)));
93111
}
94112

95-
template <typename Struct>
96-
[[nodiscard]] inline std::optional<Struct> try_read(size_t ptr = 0) const {
97-
auto data = try_read((void *)ptr, sizeof(Struct));
98-
if (!data.has_value()) {
99-
return {};
100-
}
101-
return *reinterpret_cast<Struct *>(data.value().data());
113+
// Raw read into buffer, avoids extra vector allocation
114+
std::expected<void, std::string> try_read_into(void *dest, size_t size) const;
115+
void read_into(void *dest, size_t size) const;
116+
117+
// Throwing wrappers
118+
/**
119+
* @brief Read a byte array from the pointer.
120+
* Note: This function does NOT perform extra safety checks like
121+
* try_read_bytearray. It is intended for high-performance use when memory is
122+
* known to be valid.
123+
*/
124+
std::vector<uint8_t> read_bytearray(size_t size) const;
125+
Pointer read_pointer() const;
126+
127+
template <typename T>
128+
requires std::is_standard_layout_v<T> && std::is_trivial_v<T>
129+
T read_struct() const {
130+
T val;
131+
read_into(&val, sizeof(T));
132+
return val;
102133
}
103134

104-
std::optional<std::vector<uint8_t>> try_read(void *ptr, size_t size) const;
135+
std::string read_utf8_string(size_t length = -1) const;
136+
std::string read_utf16_string(size_t length = -1) const;
137+
138+
void write_bytearray(std::span<const uint8_t> data) const;
139+
void write_pointer(Pointer ptr) const;
140+
141+
template <typename T> void write_struct(const T &value) const {
142+
auto res = try_write_struct<T>(value);
143+
if (!res)
144+
throw std::runtime_error(res.error());
145+
}
105146

106-
// read to
107-
void *read(std::span<uint8_t> dest) const;
147+
#define BLOOK_READ_WRITE_HELPER(type, name) \
148+
inline std::expected<type, std::string> try_read_##name() const { \
149+
type val; \
150+
auto res = try_read_into(&val, sizeof(type)); \
151+
if (!res) \
152+
return std::unexpected(res.error()); \
153+
return val; \
154+
} \
155+
inline type read_##name() const { \
156+
type val; \
157+
read_into(&val, sizeof(type)); \
158+
return val; \
159+
} \
160+
inline std::expected<void, std::string> try_write_##name(type val) const { \
161+
return try_write_struct<type>(val); \
162+
} \
163+
inline void write_##name(type val) const { write_struct<type>(val); }
164+
165+
BLOOK_READ_WRITE_HELPER(int8_t, s8)
166+
BLOOK_READ_WRITE_HELPER(int16_t, s16)
167+
BLOOK_READ_WRITE_HELPER(int32_t, s32)
168+
BLOOK_READ_WRITE_HELPER(int64_t, s64)
169+
BLOOK_READ_WRITE_HELPER(uint8_t, u8)
170+
BLOOK_READ_WRITE_HELPER(uint16_t, u16)
171+
BLOOK_READ_WRITE_HELPER(uint32_t, u32)
172+
BLOOK_READ_WRITE_HELPER(uint64_t, u64)
173+
BLOOK_READ_WRITE_HELPER(short, short)
174+
BLOOK_READ_WRITE_HELPER(unsigned short, ushort)
175+
BLOOK_READ_WRITE_HELPER(int, int)
176+
BLOOK_READ_WRITE_HELPER(unsigned int, uint)
177+
BLOOK_READ_WRITE_HELPER(float, float)
178+
BLOOK_READ_WRITE_HELPER(double, double)
179+
#undef BLOOK_READ_WRITE_HELPER
180+
181+
template <typename T> T read_volatile() const {
182+
// For now, same as read_struct.
183+
return read_struct<T>();
184+
}
108185

109186
explicit Pointer(std::shared_ptr<Process> proc);
110187

@@ -175,6 +252,11 @@ struct ScopedSetMemoryRWX {
175252
void *old_protect;
176253

177254
ScopedSetMemoryRWX(void *ptr, size_t size);
255+
ScopedSetMemoryRWX(const Pointer &p, size_t size)
256+
: ScopedSetMemoryRWX(p.data(), size) {}
257+
ScopedSetMemoryRWX(const ScopedSetMemoryRWX &) = delete;
258+
ScopedSetMemoryRWX &operator=(const ScopedSetMemoryRWX &) = delete;
259+
ScopedSetMemoryRWX(const MemoryRange &r);
178260

179261
~ScopedSetMemoryRWX();
180262
};
@@ -229,25 +311,40 @@ class MemoryRange : public Pointer {
229311

230312
MemoryIteratorBuffered &operator=(MemoryIteratorBuffered &&) = default;
231313

232-
MemoryIteratorBuffered(Pointer ptr, size_t size) : ptr(ptr), size(size) {}
314+
MemoryIteratorBuffered(Pointer ptr, size_t size) : ptr(ptr), size(size) {
315+
this->cache = std::make_shared<CacheBuffer>();
316+
}
233317
MemoryIteratorBuffered(Pointer ptr, size_t size,
234318
std::shared_ptr<CacheBuffer> cache)
235-
: ptr(ptr), size(size), cache(cache) {}
319+
: ptr(ptr), size(size), cache(cache) {
320+
if (!this->cache)
321+
this->cache = std::make_shared<CacheBuffer>();
322+
}
236323

237324
inline MemoryIteratorBuffered &operator+=(size_t t) {
238-
if (t * step > size) {
239-
this->ptr = ptr + size;
325+
size_t sub = t * step;
326+
if (sub > size) {
327+
ptr += size;
240328
size = 0;
241329
} else {
242-
ptr += t * step;
243-
size -= t * step;
330+
ptr += sub;
331+
size -= sub;
244332
}
245-
246333
return *this;
247334
}
248335

249-
inline MemoryIteratorBuffered operator+(size_t t) {
250-
return {ptr + t * step, std::max(size - t * step, (size_t)0), cache};
336+
inline MemoryIteratorBuffered operator+(size_t t) const {
337+
size_t sub = t * step;
338+
size_t new_size = size;
339+
Pointer new_ptr = ptr;
340+
if (sub > size) {
341+
new_ptr += size;
342+
new_size = 0;
343+
} else {
344+
new_ptr += sub;
345+
new_size -= sub;
346+
}
347+
return {new_ptr, new_size, cache};
251348
}
252349

253350
inline MemoryIteratorBuffered &operator++() {
@@ -273,20 +370,36 @@ class MemoryRange : public Pointer {
273370
inline bool is_readable() const { return try_read().has_value(); }
274371

275372
inline std::optional<uint8_t> try_read() const {
373+
if (size == 0)
374+
return {};
375+
276376
if (cache->buffer
277377
.empty() || /* !(cache->offset ∈ [ptr, ptr+cache->size]) */
278-
cache->offset > ptr.offset() ||
279-
cache->offset + cache->buffer.size() <= ptr.offset()) {
280-
cache->buffer.resize(std::min(bufSize, size));
281-
if (!ptr.read(std::span(cache->buffer.data(), cache->buffer.size())))
378+
ptr.offset() < cache->offset ||
379+
ptr.offset() >= cache->offset + cache->buffer.size()) {
380+
auto read_size = std::min(bufSize, size);
381+
cache->buffer.resize(read_size);
382+
auto res = ptr.try_read_into(cache->buffer.data(), read_size);
383+
if (!res) {
384+
cache->buffer.clear();
282385
return {};
386+
}
283387
cache->offset = ptr.offset();
284388
}
285389

286-
return cache->buffer[ptr.offset() - cache->offset];
390+
const size_t internal_offset = ptr.offset() - cache->offset;
391+
if (internal_offset >= cache->buffer.size())
392+
return {};
393+
394+
return cache->buffer[internal_offset];
287395
}
288396

289-
inline uint8_t operator*() { return try_read().value(); }
397+
inline uint8_t operator*() {
398+
auto val = try_read();
399+
if (!val)
400+
throw std::runtime_error("Failed to read memory through iterator");
401+
return *val;
402+
}
290403

291404
using value_type = uint8_t;
292405
using difference_type = std::ptrdiff_t;
@@ -313,7 +426,8 @@ class MemoryRange : public Pointer {
313426
template <class Scanner = memory_scanner::mb_kmp>
314427
inline std::optional<Pointer>
315428
find_one(const std::vector<uint8_t> pattern) const {
316-
std::optional<size_t> res = Scanner::searchOne((uint8_t*)_offset, _size, pattern);
429+
std::optional<size_t> res =
430+
Scanner::searchOne((uint8_t *)_offset, _size, pattern);
317431
return res.and_then([this](const auto val) {
318432
return std::optional<Pointer>(Pointer(this->proc, this->_offset + val));
319433
});

src/hook.cpp

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <zasm/formatter/formatter.hpp>
77

88
#include "./platform/windows/hwbplib.hpp"
9+
#include "blook/memo.h"
910
#include "zasm/x86/memory.hpp"
1011

1112
namespace blook {
@@ -41,9 +42,8 @@ void InlineHook::install(bool try_trampoline) {
4142
const auto hookSize = serializer.getCodeSize();
4243

4344
trampoline = Trampoline::make(target, hookSize, try_trampoline);
44-
4545
patch = MemoryPatch{
46-
Pointer{Process::self(), target},
46+
Pointer(target),
4747
std::vector<uint8_t>(serializer.getCode(),
4848
serializer.getCode() + serializer.getCodeSize())};
4949
if (!patch->patch())
@@ -87,9 +87,6 @@ Trampoline Trampoline::make(Pointer pCode, size_t minByteSize,
8787
occupiedBytes += instr->getLength();
8888
}
8989

90-
if (occupiedBytes < minByteSize) {
91-
throw std::runtime_error(std::format("Failed to calculate occupied bytes"));
92-
}
9390

9491
auto addr_to_jump_back =
9592
(size_t)pCode + occupiedBytes; // Address to jump back to after trampoline
@@ -111,12 +108,12 @@ Trampoline Trampoline::make(Pointer pCode, size_t minByteSize,
111108
}
112109

113110
serializer.serialize(program, (size_t)pCodePage);
114-
if (auto res = pCodePage.write(
115-
nullptr, std::span<uint8_t>((uint8_t *)serializer.getCode(),
116-
serializer.getCodeSize()));
117-
!res) {
111+
try {
112+
pCodePage.write_bytearray(std::span<const uint8_t>(
113+
(const uint8_t *)serializer.getCode(), serializer.getCodeSize()));
114+
} catch (const std::exception &e) {
118115
throw std::runtime_error(
119-
std::format("Failed to write trampoline code: {}", res.error()));
116+
std::format("Failed to write trampoline code: {}", e.what()));
120117
}
121118

122119
t.pTrampoline = pCodePage;
@@ -168,6 +165,8 @@ static LONG blook_VectoredExceptionHandler(_EXCEPTION_POINTERS *ExceptionInfo) {
168165
VEHHookManager::VEHHookContext ctx(ExceptionInfo);
169166
bp.callback(ctx);
170167
if (ExceptionInfo->ContextRecord->Rip == origRip) {
168+
std::println("Redirecting to trampoline at {:p}",
169+
bp.trampoline.pTrampoline.data());
171170
ExceptionInfo->ContextRecord->Rip =
172171
(size_t)bp.trampoline.pTrampoline.data();
173172
}
@@ -179,7 +178,7 @@ static LONG blook_VectoredExceptionHandler(_EXCEPTION_POINTERS *ExceptionInfo) {
179178
} else if (code == EXCEPTION_BREAKPOINT) {
180179
if (manager.sw_breakpoints.contains(address)) {
181180
auto &bp = manager.sw_breakpoints.at(address);
182-
Pointer(address).write(nullptr, bp.original_bytes);
181+
Pointer(address).write_bytearray(bp.original_bytes);
183182
ExceptionInfo->ContextRecord->Rip = (DWORD_PTR)address;
184183

185184
if (bp.callback) {
@@ -260,7 +259,7 @@ VEHHookManager::add_breakpoint(SoftwareBreakpoint bp,
260259
info.bp = bp;
261260
info.callback = callback;
262261

263-
auto original_byte = Pointer(bp.address).try_read<uint8_t>();
262+
auto original_byte = Pointer(bp.address).try_read_u8();
264263
if (!original_byte) {
265264
throw std::runtime_error(
266265
"Failed to read original byte for software breakpoint.");
@@ -269,7 +268,10 @@ VEHHookManager::add_breakpoint(SoftwareBreakpoint bp,
269268
info.trampoline = Trampoline::make(bp.address, 1, true);
270269

271270
uint8_t int3 = 0xCC;
272-
if (!Pointer(bp.address).write(nullptr, std::span(&int3, 1))) {
271+
try {
272+
ScopedSetMemoryRWX protector(Pointer(bp.address), 1);
273+
Pointer(bp.address).write_bytearray(std::span(&int3, 1));
274+
} catch (...) {
273275
throw std::runtime_error("Failed to write INT3 for software breakpoint.");
274276
}
275277

@@ -317,7 +319,7 @@ void VEHHookManager::remove_breakpoint(const VEHHookHandler &handler) {
317319
return;
318320
}
319321
auto &bp_info = sw_breakpoints.at(_swbp->address);
320-
Pointer(_swbp->address).write(nullptr, bp_info.original_bytes);
322+
Pointer(_swbp->address).write_bytearray(bp_info.original_bytes);
321323
sw_breakpoints.erase(_swbp->address);
322324
} else if (auto _pfbp = std::get_if<PagefaultBreakpointHandler>(&handler)) {
323325
if (!pf_breakpoints.contains(_pfbp->address)) {

src/mb_kmp.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ namespace blook {
4343
while (i < size) {
4444
// test if the page is accessible when switched page
4545
if ((size_t)(data + i) % 0x1000 == 0 || i == 0) {
46-
if(!blook::Pointer(data+i).try_read<int>(0)) {
46+
if(!blook::Pointer(data+i).try_read_u8()) {
4747
i += 0x1000 - ((size_t)(data + i) % 0x1000);
4848
continue;
4949
}

0 commit comments

Comments
 (0)