mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-18 03:13:55 +00:00
Implement some syscalls
This commit is contained in:
@@ -1 +1,2 @@
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(emulator)
|
||||
16
src/common/CMakeLists.txt
Normal file
16
src/common/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
*.hpp
|
||||
)
|
||||
|
||||
add_library(common ${SRC_FILES})
|
||||
|
||||
momo_assign_source_group(${SRC_FILES})
|
||||
|
||||
target_include_directories(common INTERFACE "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(common PUBLIC
|
||||
Threads::Threads
|
||||
)
|
||||
0
src/common/empty.cpp
Normal file
0
src/common/empty.cpp
Normal file
191
src/common/utils/byte_buffer.hpp
Normal file
191
src/common/utils/byte_buffer.hpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
class buffer_deserializer
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
buffer_deserializer(const std::basic_string_view<T>& buffer)
|
||||
: buffer_(reinterpret_cast<const std::byte*>(buffer.data()), buffer.size() * sizeof(T))
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
buffer_deserializer(const std::basic_string<T>& buffer)
|
||||
: buffer_deserializer(std::basic_string_view<T>(buffer.data(), buffer.size()))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
buffer_deserializer(const std::vector<T>& buffer)
|
||||
: buffer_deserializer(std::basic_string_view<T>(buffer.data(), buffer.size()))
|
||||
{
|
||||
}
|
||||
|
||||
void read(void* data, const size_t length)
|
||||
{
|
||||
if (this->offset_ + length > this->buffer_.size())
|
||||
{
|
||||
throw std::runtime_error("Out of bounds read from byte buffer");
|
||||
}
|
||||
|
||||
memcpy(data, this->buffer_.data() + this->offset_, length);
|
||||
this->offset_ += length;
|
||||
}
|
||||
|
||||
std::string read_data(const size_t length)
|
||||
{
|
||||
std::string result{};
|
||||
result.resize(length);
|
||||
|
||||
this->read(result.data(), result.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T read()
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
|
||||
T object{};
|
||||
this->read(&object, sizeof(object));
|
||||
return object;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> read_vector()
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
|
||||
std::vector<T> result{};
|
||||
const auto size = this->read<uint32_t>();
|
||||
const auto totalSize = size * sizeof(T);
|
||||
|
||||
if (this->offset_ + totalSize > this->buffer_.size())
|
||||
{
|
||||
throw std::runtime_error("Out of bounds read from byte buffer");
|
||||
}
|
||||
|
||||
result.resize(size);
|
||||
this->read(result.data(), totalSize);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string read_string()
|
||||
{
|
||||
std::string result{};
|
||||
const auto size = this->read<uint32_t>();
|
||||
|
||||
if (this->offset_ + size > this->buffer_.size())
|
||||
{
|
||||
throw std::runtime_error("Out of bounds read from byte buffer");
|
||||
}
|
||||
|
||||
result.resize(size);
|
||||
this->read(result.data(), size);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t get_remaining_size() const
|
||||
{
|
||||
return this->buffer_.size() - offset_;
|
||||
}
|
||||
|
||||
std::string get_remaining_data()
|
||||
{
|
||||
return this->read_data(this->get_remaining_size());
|
||||
}
|
||||
|
||||
size_t get_offset() const
|
||||
{
|
||||
return this->offset_;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t offset_{0};
|
||||
std::basic_string_view<std::byte> buffer_{};
|
||||
};
|
||||
|
||||
class buffer_serializer
|
||||
{
|
||||
public:
|
||||
buffer_serializer() = default;
|
||||
|
||||
void write(const void* buffer, const size_t length)
|
||||
{
|
||||
this->buffer_.append(static_cast<const char*>(buffer), length);
|
||||
}
|
||||
|
||||
void write(const char* text)
|
||||
{
|
||||
this->write(text, strlen(text));
|
||||
}
|
||||
|
||||
void write_string(const char* str, const size_t length)
|
||||
{
|
||||
this->write<uint32_t>(static_cast<uint32_t>(length));
|
||||
this->write(str, length);
|
||||
}
|
||||
|
||||
void write_string(const std::string& str)
|
||||
{
|
||||
this->write_string(str.data(), str.size());
|
||||
}
|
||||
|
||||
void write_string(const char* str)
|
||||
{
|
||||
this->write_string(str, strlen(str));
|
||||
}
|
||||
|
||||
void write(const buffer_serializer& object)
|
||||
{
|
||||
const auto& buffer = object.get_buffer();
|
||||
this->write(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(const T& object)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
this->write(&object, sizeof(object));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(const std::vector<T>& vec)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
this->write(vec.data(), vec.size() * sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write_vector(const std::vector<T>& vec)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
this->write(static_cast<uint32_t>(vec.size()));
|
||||
this->write(vec);
|
||||
}
|
||||
|
||||
const std::string& get_buffer() const
|
||||
{
|
||||
return this->buffer_;
|
||||
}
|
||||
|
||||
std::string move_buffer()
|
||||
{
|
||||
return std::move(this->buffer_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string buffer_{};
|
||||
};
|
||||
}
|
||||
57
src/common/utils/concurrency.hpp
Normal file
57
src/common/utils/concurrency.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace utils::concurrency
|
||||
{
|
||||
template <typename T, typename MutexType = std::mutex>
|
||||
class container
|
||||
{
|
||||
public:
|
||||
template <typename R = void, typename F>
|
||||
R access(F&& accessor) const
|
||||
{
|
||||
std::lock_guard<MutexType> _{mutex_};
|
||||
return accessor(object_);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F>
|
||||
R access(F&& accessor)
|
||||
{
|
||||
std::lock_guard<MutexType> _{mutex_};
|
||||
return accessor(object_);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F>
|
||||
R access_with_lock(F&& accessor) const
|
||||
{
|
||||
std::unique_lock<MutexType> lock{mutex_};
|
||||
return accessor(object_, lock);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F>
|
||||
R access_with_lock(F&& accessor)
|
||||
{
|
||||
std::unique_lock<MutexType> lock{mutex_};
|
||||
return accessor(object_, lock);
|
||||
}
|
||||
|
||||
T& get_raw() { return object_; }
|
||||
const T& get_raw() const { return object_; }
|
||||
|
||||
T copy() const
|
||||
{
|
||||
std::unique_lock<MutexType> lock{mutex_};
|
||||
return object_;
|
||||
}
|
||||
|
||||
std::unique_lock<MutexType> acquire_lock()
|
||||
{
|
||||
return std::unique_lock<MutexType>{mutex_};
|
||||
}
|
||||
|
||||
private:
|
||||
mutable MutexType mutex_{};
|
||||
T object_{};
|
||||
};
|
||||
}
|
||||
55
src/common/utils/finally.hpp
Normal file
55
src/common/utils/finally.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
/*
|
||||
* Copied from here: https://github.com/microsoft/GSL/blob/e0880931ae5885eb988d1a8a57acf8bc2b8dacda/include/gsl/util#L57
|
||||
*/
|
||||
|
||||
template <class F>
|
||||
class final_action
|
||||
{
|
||||
public:
|
||||
static_assert(!std::is_reference<F>::value && !std::is_const<F>::value &&
|
||||
!std::is_volatile<F>::value,
|
||||
"Final_action should store its callable by value");
|
||||
|
||||
explicit final_action(F f) noexcept : f_(std::move(f))
|
||||
{
|
||||
}
|
||||
|
||||
final_action(final_action&& other) noexcept
|
||||
: f_(std::move(other.f_)), invoke_(std::exchange(other.invoke_, false))
|
||||
{
|
||||
}
|
||||
|
||||
final_action(const final_action&) = delete;
|
||||
final_action& operator=(const final_action&) = delete;
|
||||
final_action& operator=(final_action&&) = delete;
|
||||
|
||||
~final_action() noexcept
|
||||
{
|
||||
if (invoke_) f_();
|
||||
}
|
||||
|
||||
// Added by momo5502
|
||||
void cancel()
|
||||
{
|
||||
invoke_ = false;
|
||||
}
|
||||
|
||||
private:
|
||||
F f_;
|
||||
bool invoke_{true};
|
||||
};
|
||||
|
||||
template <class F>
|
||||
final_action<typename std::remove_cv<typename std::remove_reference<F>::type>::type>
|
||||
finally(F&& f) noexcept
|
||||
{
|
||||
return final_action<typename std::remove_cv<typename std::remove_reference<F>::type>::type>(
|
||||
std::forward<F>(f));
|
||||
}
|
||||
}
|
||||
26
src/common/utils/timer.hpp
Normal file
26
src/common/utils/timer.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
template <typename Clock = std::chrono::high_resolution_clock>
|
||||
class timer
|
||||
{
|
||||
public:
|
||||
void update()
|
||||
{
|
||||
this->point_ = Clock::now();
|
||||
}
|
||||
|
||||
bool has_elapsed(typename Clock::duration duration) const
|
||||
{
|
||||
const auto now = Clock::now();
|
||||
const auto diff = now - this->point_;
|
||||
return diff > duration;
|
||||
}
|
||||
|
||||
private:
|
||||
typename Clock::time_point point_{ Clock::now() };
|
||||
};
|
||||
}
|
||||
@@ -12,7 +12,11 @@ momo_assign_source_group(${SRC_FILES})
|
||||
|
||||
target_precompile_headers(emulator PRIVATE std_include.hpp)
|
||||
|
||||
target_link_libraries(emulator PRIVATE unicorn phnt::phnt)
|
||||
target_link_libraries(emulator PRIVATE
|
||||
common
|
||||
unicorn
|
||||
phnt::phnt
|
||||
)
|
||||
|
||||
set_property(GLOBAL PROPERTY VS_STARTUP_PROJECT emulator)
|
||||
|
||||
|
||||
@@ -11,9 +11,20 @@
|
||||
#define KUSD_ADDRESS 0x7ffe0000
|
||||
|
||||
#include "unicorn.hpp"
|
||||
#include <utils/finally.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool is_within_start_and_end(const uint64_t value, const uint64_t start, const uint64_t end)
|
||||
{
|
||||
return value >= start && value < end;
|
||||
}
|
||||
|
||||
bool is_within_start_and_length(const uint64_t value, const uint64_t start, const uint64_t length)
|
||||
{
|
||||
return is_within_start_and_end(value, start, start + length);
|
||||
}
|
||||
|
||||
uint64_t align_down(const uint64_t value, const uint64_t alignment)
|
||||
{
|
||||
return value & ~(alignment - 1);
|
||||
@@ -24,6 +35,11 @@ namespace
|
||||
return align_down(value + (alignment - 1), alignment);
|
||||
}
|
||||
|
||||
uint64_t page_align_down(const uint64_t value)
|
||||
{
|
||||
return align_down(value, 0x1000);
|
||||
}
|
||||
|
||||
uint64_t page_align_up(const uint64_t value)
|
||||
{
|
||||
return align_up(value, 0x1000);
|
||||
@@ -51,16 +67,34 @@ namespace
|
||||
return reinterpret_cast<T*>(this->address_);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void access(const F& accessor) const
|
||||
operator bool() const
|
||||
{
|
||||
return this->address_ != 0;
|
||||
}
|
||||
|
||||
T read() const
|
||||
{
|
||||
T obj{};
|
||||
|
||||
e(uc_mem_read(*this->uc_, this->address_, &obj, sizeof(obj)));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void write(const T& value) const
|
||||
{
|
||||
e(uc_mem_write(*this->uc_, this->address_, &value, sizeof(value)));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void access(const F& accessor) const
|
||||
{
|
||||
T obj{};
|
||||
e(uc_mem_read(*this->uc_, this->address_, &obj, sizeof(obj)));
|
||||
|
||||
accessor(obj);
|
||||
|
||||
e(uc_mem_write(*this->uc_, this->address_, &obj, sizeof(obj)));
|
||||
this->write(obj);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -107,19 +141,36 @@ namespace
|
||||
class unicorn_hook
|
||||
{
|
||||
public:
|
||||
using function = std::function<void(uint64_t address, uint32_t size)>;
|
||||
using function = std::function<void(const unicorn& uc, uint64_t address, uint32_t size)>;
|
||||
|
||||
unicorn_hook(const unicorn& uc, const int type, const uint64_t begin, const uint64_t end, function callback)
|
||||
template <typename... Args>
|
||||
unicorn_hook(const unicorn& uc, const int type, const uint64_t begin, const uint64_t end, function callback,
|
||||
Args... args)
|
||||
: uc_(&uc)
|
||||
, function_(std::make_unique<function>(std::move(callback)))
|
||||
{
|
||||
auto* handler = +[](uc_engine*, const uint64_t address, const uint32_t size,
|
||||
this->function_ = std::make_unique<internal_function>(
|
||||
[c = std::move(callback), &uc](const uint64_t address, const uint32_t size)
|
||||
{
|
||||
c(uc, address, size);
|
||||
});
|
||||
|
||||
void* handler = +[](uc_engine*, const uint64_t address, const uint32_t size,
|
||||
void* user_data)
|
||||
{
|
||||
(*static_cast<function*>(user_data))(address, size);
|
||||
(*static_cast<internal_function*>(user_data))(address, size);
|
||||
};
|
||||
|
||||
e(uc_hook_add(*this->uc_, &this->hook_, type, handler, this->function_.get(), begin, end));
|
||||
if (type == UC_HOOK_INSN)
|
||||
{
|
||||
handler = +[](uc_engine* uc, void* user_data)
|
||||
{
|
||||
uint64_t rip{};
|
||||
uc_reg_read(uc, UC_X86_REG_RIP, &rip);
|
||||
(*static_cast<internal_function*>(user_data))(rip, 0);
|
||||
};
|
||||
}
|
||||
|
||||
e(uc_hook_add(*this->uc_, &this->hook_, type, handler, this->function_.get(), begin, end, args...));
|
||||
}
|
||||
|
||||
unicorn_hook(const unicorn_hook&) = delete;
|
||||
@@ -163,9 +214,11 @@ namespace
|
||||
}
|
||||
|
||||
private:
|
||||
using internal_function = std::function<void(uint64_t address, uint32_t size)>;
|
||||
|
||||
const unicorn* uc_{};
|
||||
uc_hook hook_{};
|
||||
std::unique_ptr<function> function_{};
|
||||
std::unique_ptr<internal_function> function_{};
|
||||
};
|
||||
|
||||
void setup_stack(const unicorn& uc, uint64_t stack_base, size_t stack_size)
|
||||
@@ -209,43 +262,53 @@ namespace
|
||||
});
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, uint64_t> map_module(const unicorn& uc, const std::vector<uint8_t>& module_data,
|
||||
const std::string& name)
|
||||
struct mapped_binary
|
||||
{
|
||||
uint64_t image_base{};
|
||||
uint64_t size_of_image{};
|
||||
std::unordered_map<std::string, uint64_t> exports{};
|
||||
};
|
||||
|
||||
mapped_binary map_module(const unicorn& uc, const std::vector<uint8_t>& module_data,
|
||||
const std::string& name)
|
||||
{
|
||||
mapped_binary binary{};
|
||||
|
||||
// TODO: Range checks
|
||||
auto* ptr = module_data.data();
|
||||
auto* dos_header = reinterpret_cast<const IMAGE_DOS_HEADER*>(ptr);
|
||||
auto* nt_headers = reinterpret_cast<const IMAGE_NT_HEADERS*>(ptr + dos_header->e_lfanew);
|
||||
auto& optional_header = nt_headers->OptionalHeader;
|
||||
|
||||
auto prefered_base = optional_header.ImageBase;
|
||||
binary.image_base = optional_header.ImageBase;
|
||||
binary.size_of_image = optional_header.SizeOfImage;
|
||||
|
||||
while (true)
|
||||
{
|
||||
const auto res = uc_mem_map(uc, prefered_base, optional_header.SizeOfImage, UC_PROT_READ);
|
||||
const auto res = uc_mem_map(uc, binary.image_base, binary.size_of_image, UC_PROT_READ);
|
||||
if (res == UC_ERR_OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
prefered_base += 0x10000;
|
||||
binary.image_base += 0x10000;
|
||||
|
||||
if (prefered_base < optional_header.ImageBase || (optional_header.DllCharacteristics &
|
||||
if (binary.image_base < optional_header.ImageBase || (optional_header.DllCharacteristics &
|
||||
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) == 0)
|
||||
{
|
||||
throw std::runtime_error("Failed to map range");
|
||||
}
|
||||
}
|
||||
|
||||
printf("Mapping %s at %llX\n", name.c_str(), prefered_base);
|
||||
printf("Mapping %s at %llX\n", name.c_str(), binary.image_base);
|
||||
|
||||
e(uc_mem_write(uc, prefered_base, ptr, optional_header.SizeOfHeaders));
|
||||
e(uc_mem_write(uc, binary.image_base, ptr, optional_header.SizeOfHeaders));
|
||||
|
||||
const std::span sections(IMAGE_FIRST_SECTION(nt_headers), nt_headers->FileHeader.NumberOfSections);
|
||||
|
||||
for (const auto& section : sections)
|
||||
{
|
||||
const auto target_ptr = prefered_base + section.VirtualAddress;
|
||||
const auto target_ptr = binary.image_base + section.VirtualAddress;
|
||||
|
||||
if (section.SizeOfRawData > 0)
|
||||
{
|
||||
@@ -292,54 +355,88 @@ namespace
|
||||
const auto* ordinals = reinterpret_cast<const WORD*>(ptr + export_directory->AddressOfNameOrdinals);
|
||||
const auto* functions = reinterpret_cast<const DWORD*>(ptr + export_directory->AddressOfFunctions);
|
||||
|
||||
std::unordered_map<std::string, uint64_t> exports{};
|
||||
|
||||
for (DWORD i = 0; i < names_count; i++)
|
||||
{
|
||||
const auto* function_name = reinterpret_cast<const char*>(ptr + names[i]);
|
||||
const auto function_rva = functions[ordinals[i]];
|
||||
const auto function_address = prefered_base + function_rva;
|
||||
const auto function_address = binary.image_base + function_rva;
|
||||
|
||||
exports[function_name] = function_address;
|
||||
binary.exports[function_name] = function_address;
|
||||
}
|
||||
|
||||
return exports;
|
||||
return binary;
|
||||
}
|
||||
|
||||
void setup_teb_and_peb(const unicorn& uc)
|
||||
struct event
|
||||
{
|
||||
bool signaled{};
|
||||
EVENT_TYPE type{};
|
||||
|
||||
bool is_signaled()
|
||||
{
|
||||
const auto res = this->signaled;
|
||||
|
||||
if (this->type == SynchronizationEvent)
|
||||
{
|
||||
this->signaled = false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
struct process_context
|
||||
{
|
||||
unicorn_object<TEB> teb{};
|
||||
unicorn_object<PEB> peb{};
|
||||
unicorn_object<RTL_USER_PROCESS_PARAMETERS> process_params{};
|
||||
|
||||
mapped_binary executable{};
|
||||
mapped_binary ntdll{};
|
||||
|
||||
std::vector<event> events{};
|
||||
};
|
||||
|
||||
process_context setup_teb_and_peb(const unicorn& uc)
|
||||
{
|
||||
setup_stack(uc, STACK_ADDRESS, STACK_SIZE);
|
||||
auto gs = setup_gs_segment(uc, GS_SEGMENT_ADDR, GS_SEGMENT_SIZE);
|
||||
|
||||
const auto teb_object = gs.reserve<TEB>();
|
||||
const auto peb_object = gs.reserve<PEB>();
|
||||
const auto ldr_object = gs.reserve<PEB_LDR_DATA>();
|
||||
process_context context{};
|
||||
|
||||
teb_object.access([&](TEB& teb)
|
||||
context.teb = gs.reserve<TEB>();
|
||||
context.peb = gs.reserve<PEB>();
|
||||
//context.ldr = gs.reserve<PEB_LDR_DATA>();
|
||||
context.process_params = gs.reserve<RTL_USER_PROCESS_PARAMETERS>();
|
||||
|
||||
context.teb.access([&](TEB& teb)
|
||||
{
|
||||
teb.NtTib.StackLimit = reinterpret_cast<void*>(STACK_ADDRESS);
|
||||
teb.NtTib.StackBase = reinterpret_cast<void*>((STACK_ADDRESS + STACK_SIZE));
|
||||
teb.NtTib.Self = &teb_object.ptr()->NtTib;
|
||||
teb.ProcessEnvironmentBlock = peb_object.ptr();
|
||||
teb.NtTib.Self = &context.teb.ptr()->NtTib;
|
||||
teb.ProcessEnvironmentBlock = context.peb.ptr();
|
||||
});
|
||||
|
||||
peb_object.access([&](PEB& peb)
|
||||
context.peb.access([&](PEB& peb)
|
||||
{
|
||||
peb.ImageBaseAddress = nullptr;
|
||||
peb.Ldr = ldr_object.ptr();
|
||||
//peb.Ldr = context.ldr.ptr();
|
||||
peb.ProcessParameters = context.process_params.ptr();
|
||||
});
|
||||
|
||||
ldr_object.access([&](PEB_LDR_DATA& ldr)
|
||||
/*context.ldr.access([&](PEB_LDR_DATA& ldr)
|
||||
{
|
||||
ldr.InLoadOrderModuleList.Flink = &ldr_object.ptr()->InLoadOrderModuleList;
|
||||
ldr.InLoadOrderModuleList.Flink = &context.ldr.ptr()->InLoadOrderModuleList;
|
||||
ldr.InLoadOrderModuleList.Blink = ldr.InLoadOrderModuleList.Flink;
|
||||
|
||||
ldr.InMemoryOrderModuleList.Flink = &ldr_object.ptr()->InMemoryOrderModuleList;
|
||||
ldr.InMemoryOrderModuleList.Flink = &context.ldr.ptr()->InMemoryOrderModuleList;
|
||||
ldr.InMemoryOrderModuleList.Blink = ldr.InMemoryOrderModuleList.Flink;
|
||||
|
||||
ldr.InInitializationOrderModuleList.Flink = &ldr_object.ptr()->InInitializationOrderModuleList;
|
||||
ldr.InInitializationOrderModuleList.Flink = &context.ldr.ptr()->InInitializationOrderModuleList;
|
||||
ldr.InInitializationOrderModuleList.Blink = ldr.InInitializationOrderModuleList.Flink;
|
||||
});
|
||||
});*/
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> load_file(const std::filesystem::path& file)
|
||||
@@ -348,33 +445,338 @@ namespace
|
||||
return {(std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, uint64_t> map_file(const unicorn& uc, const std::filesystem::path& file)
|
||||
mapped_binary map_file(const unicorn& uc, const std::filesystem::path& file)
|
||||
{
|
||||
const auto data = load_file(file);
|
||||
return map_module(uc, data, file.generic_string());
|
||||
}
|
||||
|
||||
void handle_NtQueryPerformanceCounter(const unicorn& uc)
|
||||
{
|
||||
const unicorn_object<LARGE_INTEGER> performance_counter{uc, uc.reg(UC_X86_REG_R10)};
|
||||
const unicorn_object<LARGE_INTEGER> performance_frequency{uc, uc.reg(UC_X86_REG_RDX)};
|
||||
|
||||
try
|
||||
{
|
||||
if (performance_counter)
|
||||
{
|
||||
performance_counter.access([](LARGE_INTEGER& value)
|
||||
{
|
||||
QueryPerformanceCounter(&value);
|
||||
});
|
||||
}
|
||||
|
||||
if (performance_frequency)
|
||||
{
|
||||
performance_frequency.access([](LARGE_INTEGER& value)
|
||||
{
|
||||
QueryPerformanceFrequency(&value);
|
||||
});
|
||||
}
|
||||
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_SUCCESS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_ACCESS_VIOLATION);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t get_memory_protection(const unicorn& uc, uint64_t address)
|
||||
{
|
||||
uint32_t count{};
|
||||
uc_mem_region* regions{};
|
||||
|
||||
e(uc_mem_regions(uc, ®ions, &count));
|
||||
const auto _ = utils::finally([&]
|
||||
{
|
||||
uc_free(regions);
|
||||
});
|
||||
|
||||
for (const auto& region : std::span(regions, count))
|
||||
{
|
||||
if (is_within_start_and_end(address, region.begin, region.end))
|
||||
{
|
||||
return region.perms;
|
||||
}
|
||||
}
|
||||
|
||||
return UC_PROT_NONE;
|
||||
}
|
||||
|
||||
uint32_t map_nt_to_unicorn_protection(const uint32_t nt_protection)
|
||||
{
|
||||
switch (nt_protection)
|
||||
{
|
||||
case PAGE_NOACCESS:
|
||||
return UC_PROT_NONE;
|
||||
case PAGE_READONLY:
|
||||
return UC_PROT_READ;
|
||||
case PAGE_READWRITE:
|
||||
case PAGE_WRITECOPY:
|
||||
return UC_PROT_READ | UC_PROT_WRITE;
|
||||
case PAGE_EXECUTE:
|
||||
case PAGE_EXECUTE_READ:
|
||||
return UC_PROT_READ | UC_PROT_EXEC;
|
||||
case PAGE_EXECUTE_READWRITE:
|
||||
case PAGE_EXECUTE_WRITECOPY:
|
||||
default:
|
||||
return UC_PROT_ALL;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t map_unicorn_to_nt_protection(const uint32_t unicorn_protection)
|
||||
{
|
||||
const bool has_exec = unicorn_protection & UC_PROT_EXEC;
|
||||
const bool has_read = unicorn_protection & UC_PROT_READ;
|
||||
const bool has_write = unicorn_protection & UC_PROT_WRITE;
|
||||
|
||||
if (!has_read)
|
||||
{
|
||||
return PAGE_NOACCESS;
|
||||
}
|
||||
|
||||
if (has_exec && has_write)
|
||||
{
|
||||
return PAGE_EXECUTE_READWRITE;
|
||||
}
|
||||
|
||||
if (has_exec)
|
||||
{
|
||||
return PAGE_EXECUTE_READ;
|
||||
}
|
||||
|
||||
if (has_write)
|
||||
{
|
||||
return PAGE_READWRITE;
|
||||
}
|
||||
|
||||
return PAGE_READONLY;
|
||||
}
|
||||
|
||||
void handle_NtManageHotPatch(const unicorn& uc)
|
||||
{
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
void handle_NtCreateEvent(const unicorn& uc, process_context& context)
|
||||
{
|
||||
const unicorn_object<uint64_t> event_handle{uc, uc.reg(UC_X86_REG_R10)};
|
||||
const auto object_attributes = uc.reg(UC_X86_REG_R8);
|
||||
const auto event_type = uc.reg<EVENT_TYPE>(UC_X86_REG_R9D);
|
||||
const auto initial_state = static_cast<BOOLEAN>(uc.read_stack(5));
|
||||
|
||||
if (object_attributes)
|
||||
{
|
||||
puts("Unsupported object attributes");
|
||||
uc.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t index = context.events.size();
|
||||
event_handle.write(index);
|
||||
|
||||
context.events.emplace_back(initial_state != FALSE, event_type);
|
||||
|
||||
static_assert(sizeof(EVENT_TYPE) == sizeof(uint32_t));
|
||||
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
void handle_NtQueryVirtualMemory(const unicorn& uc, const process_context& context)
|
||||
{
|
||||
const auto process_handle = uc.reg(UC_X86_REG_R10);
|
||||
const auto base_address = uc.reg(UC_X86_REG_RDX);
|
||||
const auto info_class = uc.reg<uint32_t>(UC_X86_REG_R8D);
|
||||
const auto memory_information = uc.reg(UC_X86_REG_R9);
|
||||
const auto memory_information_length = static_cast<uint32_t>(uc.read_stack(5));
|
||||
const unicorn_object<uint32_t> return_length{uc, uc.read_stack(6)};
|
||||
|
||||
if (process_handle != ~0ULL)
|
||||
{
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info_class != MemoryImageInformation)
|
||||
{
|
||||
printf("Unsupported memory info class: %X\n", info_class);
|
||||
uc.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (return_length)
|
||||
{
|
||||
return_length.write(sizeof(MEMORY_IMAGE_INFORMATION));
|
||||
}
|
||||
|
||||
if (memory_information_length != sizeof(MEMORY_IMAGE_INFORMATION))
|
||||
{
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_BUFFER_OVERFLOW);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_within_start_and_length(base_address, context.ntdll.image_base, context.ntdll.size_of_image))
|
||||
{
|
||||
puts("Bad image request");
|
||||
uc.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const unicorn_object<MEMORY_IMAGE_INFORMATION> info{uc, memory_information};
|
||||
|
||||
info.access([&](MEMORY_IMAGE_INFORMATION& image_info)
|
||||
{
|
||||
image_info.ImageBase = reinterpret_cast<void*>(context.ntdll.image_base);
|
||||
image_info.SizeOfImage = context.ntdll.size_of_image;
|
||||
});
|
||||
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
void handle_NtQuerySystemInformation(const unicorn& uc, const process_context& context)
|
||||
{
|
||||
const auto info_class = uc.reg<uint32_t>(UC_X86_REG_R10D);
|
||||
const auto system_information = uc.reg(UC_X86_REG_RDX);
|
||||
const auto system_information_length = uc.reg<uint32_t>(UC_X86_REG_R8D);
|
||||
const unicorn_object<uint32_t> return_length{uc, uc.reg(UC_X86_REG_R9)};
|
||||
|
||||
if (info_class != SystemBasicInformation)
|
||||
{
|
||||
printf("Unsupported system info class: %X\n", info_class);
|
||||
uc.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (return_length)
|
||||
{
|
||||
return_length.write(sizeof(SYSTEM_BASIC_INFORMATION));
|
||||
}
|
||||
|
||||
if (system_information_length != sizeof(SYSTEM_BASIC_INFORMATION))
|
||||
{
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_BUFFER_OVERFLOW);
|
||||
return;
|
||||
}
|
||||
|
||||
const unicorn_object<SYSTEM_BASIC_INFORMATION> info{uc, system_information};
|
||||
|
||||
info.access([&](SYSTEM_BASIC_INFORMATION& basic_info)
|
||||
{
|
||||
basic_info.Reserved = 0;
|
||||
basic_info.TimerResolution = 0x0002625a;
|
||||
basic_info.PageSize = 0x1000;
|
||||
basic_info.LowestPhysicalPageNumber = 0x00000001;
|
||||
basic_info.HighestPhysicalPageNumber = 0x00c9c7ff;
|
||||
basic_info.AllocationGranularity = 0x10000;
|
||||
basic_info.MinimumUserModeAddress = 0x0000000000010000;
|
||||
basic_info.MaximumUserModeAddress = 0x00007ffffffeffff;
|
||||
basic_info.ActiveProcessorsAffinityMask = 0x0000000000000fff;
|
||||
basic_info.NumberOfProcessors = 1;
|
||||
});
|
||||
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
void handle_NtProtectVirtualMemory(const unicorn& uc)
|
||||
{
|
||||
const auto process_handle = uc.reg(UC_X86_REG_R10);
|
||||
const unicorn_object<uint64_t> base_address{uc, uc.reg(UC_X86_REG_RDX)};
|
||||
const unicorn_object<uint32_t> bytes_to_protect{uc, uc.reg(UC_X86_REG_R8)};
|
||||
const auto protection = uc.reg<uint32_t>(UC_X86_REG_R9D);
|
||||
const unicorn_object<uint32_t> old_protection{uc, uc.read_stack(5)};
|
||||
|
||||
if (process_handle != ~0ULL)
|
||||
{
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto address = page_align_down(base_address.read());
|
||||
base_address.write(address);
|
||||
|
||||
const auto size = page_align_up(bytes_to_protect.read());
|
||||
bytes_to_protect.write(static_cast<uint32_t>(size));
|
||||
|
||||
const auto current_uc_protection = get_memory_protection(uc, address);
|
||||
const auto current_protection = map_unicorn_to_nt_protection(current_uc_protection);
|
||||
old_protection.write(current_protection);
|
||||
|
||||
const auto requested_protection = map_nt_to_unicorn_protection(protection);
|
||||
e(uc_mem_protect(uc, address, size, requested_protection));
|
||||
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
const unicorn uc{UC_ARCH_X86, UC_MODE_64};
|
||||
|
||||
setup_kusd(uc);
|
||||
setup_teb_and_peb(uc);
|
||||
auto context = setup_teb_and_peb(uc);
|
||||
|
||||
const auto executable_exports = map_file(uc, R"(C:\Users\mauri\Desktop\ConsoleApplication6.exe)");
|
||||
context.executable = map_file(uc, R"(C:\Users\mauri\Desktop\ConsoleApplication6.exe)");
|
||||
|
||||
const auto ntdll_exports = map_file(uc, R"(C:\Windows\System32\ntdll.dll)");
|
||||
context.peb.access([&](PEB& peb)
|
||||
{
|
||||
peb.ImageBaseAddress = reinterpret_cast<void*>(context.executable.image_base);
|
||||
});
|
||||
|
||||
const auto entry1 = ntdll_exports.at("LdrInitializeThunk");
|
||||
const auto entry2 = ntdll_exports.at("RtlUserThreadStart");
|
||||
context.ntdll = map_file(uc, R"(C:\Windows\System32\ntdll.dll)");
|
||||
|
||||
const auto entry1 = context.ntdll.exports.at("LdrInitializeThunk");
|
||||
const auto entry2 = context.ntdll.exports.at("RtlUserThreadStart");
|
||||
|
||||
(void)entry1;
|
||||
(void)entry2;
|
||||
|
||||
unicorn_hook hook(uc, UC_HOOK_INTR, 0, 0, [](const uint64_t address, const uint32_t /*size*/)
|
||||
{
|
||||
printf("Syscall: %llX\n", address);
|
||||
});
|
||||
unicorn_hook hook(uc, UC_HOOK_INSN, 0, std::numeric_limits<uint64_t>::max(),
|
||||
[&](const unicorn&, const uint64_t address, const uint32_t /*size*/)
|
||||
{
|
||||
const auto syscall_id = uc.reg<uint32_t>(UC_X86_REG_EAX);
|
||||
|
||||
printf("Handling syscall: %X (%llX)\n", syscall_id, address);
|
||||
|
||||
try
|
||||
{
|
||||
switch (syscall_id)
|
||||
{
|
||||
case 0x23:
|
||||
handle_NtQueryVirtualMemory(uc, context);
|
||||
break;
|
||||
case 0x31:
|
||||
handle_NtQueryPerformanceCounter(uc);
|
||||
break;
|
||||
case 0x36:
|
||||
handle_NtQuerySystemInformation(uc, context);
|
||||
break;
|
||||
case 0x48:
|
||||
handle_NtCreateEvent(uc, context);
|
||||
break;
|
||||
case 0x50:
|
||||
handle_NtProtectVirtualMemory(uc);
|
||||
break;
|
||||
case 0x11A:
|
||||
handle_NtManageHotPatch(uc);
|
||||
break;
|
||||
default:
|
||||
printf("Unhandled syscall: %X\n", syscall_id);
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED);
|
||||
uc.stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
uc.reg<uint64_t>(UC_X86_REG_RAX, STATUS_UNSUCCESSFUL);
|
||||
}
|
||||
}, UC_X86_INS_SYSCALL);
|
||||
|
||||
unicorn_hook hook2(uc, UC_HOOK_CODE, 0, std::numeric_limits<uint64_t>::max(),
|
||||
[](const unicorn&, const uint64_t address, const uint32_t /*size*/)
|
||||
{
|
||||
printf("Inst: %llX\n", address);
|
||||
});
|
||||
|
||||
const auto err = uc_emu_start(uc, entry1, 0, 0, 0);
|
||||
if (err != UC_ERR_OK)
|
||||
@@ -406,7 +808,7 @@ int main(int /*argc*/, char** /*argv*/)
|
||||
puts(e.what());
|
||||
|
||||
#ifdef _WIN32
|
||||
MessageBoxA(nullptr, e.what(), "ERROR", MB_ICONERROR);
|
||||
//MessageBoxA(nullptr, e.what(), "ERROR", MB_ICONERROR);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,34 @@ public:
|
||||
return this->uc_;
|
||||
}
|
||||
|
||||
template <typename T = uint64_t>
|
||||
T reg(const int regid) const
|
||||
{
|
||||
T value{};
|
||||
e(uc_reg_read(this->uc_, regid, &value));
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T = uint64_t>
|
||||
void reg(const int regid, const T& value) const
|
||||
{
|
||||
e(uc_reg_write(this->uc_, regid, &value));
|
||||
}
|
||||
|
||||
void stop() const
|
||||
{
|
||||
e(uc_emu_stop(this->uc_));
|
||||
}
|
||||
|
||||
uint64_t read_stack(const size_t index) const
|
||||
{
|
||||
uint64_t result{};
|
||||
const auto rsp = this->reg(UC_X86_REG_RSP);
|
||||
|
||||
e(uc_mem_read(this->uc_, rsp + (index * sizeof(result)), &result, sizeof(result)));
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
uc_engine* uc_{};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user