mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-23 05:31:03 +00:00
VMP program simulation patches (#643)
This PR fixes some issues with VMP program emulation. It has successfully allowed programs protected by VMP 3.5 to run normally, and it modifies the following functions: 1. Correct implementation of ProcessInstrumentationCallback 2. Add missing path check in handle_NtCreateFile 3. Fix the check in handle_NtOpenSection 4. Fix the behavioral differences between the emulator and the kernel when the return value is an invalid pointer 5. Add printing of the content of the NtRaiseHardError message box 6. Bypass NtClose detection for VMP
This commit is contained in:
@@ -264,9 +264,14 @@ namespace icicle
|
||||
ice(res, "Failed to read memory");
|
||||
}
|
||||
|
||||
bool try_write_memory(const uint64_t address, const void* data, const size_t size) override
|
||||
{
|
||||
return icicle_write_memory(this->emu_, address, data, size);
|
||||
}
|
||||
|
||||
void write_memory(const uint64_t address, const void* data, const size_t size) override
|
||||
{
|
||||
const auto res = icicle_write_memory(this->emu_, address, data, size);
|
||||
const auto res = try_write_memory(address, data, size);
|
||||
ice(res, "Failed to write memory");
|
||||
}
|
||||
|
||||
|
||||
@@ -380,6 +380,11 @@ namespace unicorn
|
||||
uce(uc_mem_read(*this, address, data, size));
|
||||
}
|
||||
|
||||
bool try_write_memory(const uint64_t address, const void* data, const size_t size) override
|
||||
{
|
||||
return uc_mem_write(*this, address, data, size) == UC_ERR_OK;
|
||||
}
|
||||
|
||||
void write_memory(const uint64_t address, const void* data, const size_t size) override
|
||||
{
|
||||
uce(uc_mem_write(*this, address, data, size));
|
||||
|
||||
@@ -1124,4 +1124,10 @@ struct PROCESS_PRIORITY_CLASS
|
||||
UCHAR PriorityClass;
|
||||
};
|
||||
|
||||
struct PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION
|
||||
{
|
||||
ULONG Version;
|
||||
ULONG Reserved;
|
||||
uint64_t Callback;
|
||||
};
|
||||
// NOLINTEND(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
|
||||
@@ -68,5 +68,7 @@ using NTSTATUS = std::uint32_t;
|
||||
|
||||
#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L)
|
||||
|
||||
#define STATUS_SERVICE_NOTIFICATION ((NTSTATUS)0x40000018L)
|
||||
|
||||
#define FILE_DEVICE_NETWORK 0x00000012
|
||||
#define FSCTL_AFD_BASE FILE_DEVICE_NETWORK
|
||||
|
||||
@@ -19,6 +19,7 @@ class memory_interface
|
||||
virtual void read_memory(uint64_t address, void* data, size_t size) const = 0;
|
||||
virtual bool try_read_memory(uint64_t address, void* data, size_t size) const = 0;
|
||||
virtual void write_memory(uint64_t address, const void* data, size_t size) = 0;
|
||||
virtual bool try_write_memory(uint64_t address, const void* data, size_t size) = 0;
|
||||
|
||||
private:
|
||||
virtual void map_mmio(uint64_t address, size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb) = 0;
|
||||
|
||||
@@ -102,6 +102,8 @@ class emulator_thread : public ref_counted_object
|
||||
|
||||
std::vector<std::byte> last_registers{};
|
||||
|
||||
bool debugger_hide{false};
|
||||
|
||||
void mark_as_ready(NTSTATUS status);
|
||||
|
||||
bool is_await_time_over(utils::clock& clock) const
|
||||
@@ -180,6 +182,8 @@ class emulator_thread : public ref_counted_object
|
||||
buffer.write_optional(this->wow64_cpu_reserved);
|
||||
|
||||
buffer.write_vector(this->last_registers);
|
||||
|
||||
buffer.write(this->debugger_hide);
|
||||
}
|
||||
|
||||
void deserialize_object(utils::buffer_deserializer& buffer) override
|
||||
@@ -225,6 +229,8 @@ class emulator_thread : public ref_counted_object
|
||||
buffer.read_optional(this->wow64_cpu_reserved, [this] { return emulator_object<WOW64_CPURESERVED>(*this->memory_ptr); });
|
||||
|
||||
buffer.read_vector(this->last_registers);
|
||||
|
||||
buffer.read(this->debugger_hide);
|
||||
}
|
||||
|
||||
void leak_memory()
|
||||
|
||||
@@ -104,6 +104,16 @@ class emulator_object
|
||||
return this->address_ != 0;
|
||||
}
|
||||
|
||||
std::optional<T> try_read(const size_t index = 0) const
|
||||
{
|
||||
T obj{};
|
||||
if (this->memory_->try_read_memory(this->address_ + index * this->size(), &obj, sizeof(obj)))
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
T read(const size_t index = 0) const
|
||||
{
|
||||
T obj{};
|
||||
@@ -111,6 +121,11 @@ class emulator_object
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool try_write(const T& value, const size_t index = 0) const
|
||||
{
|
||||
return this->memory_->try_write_memory(this->address_ + index * this->size(), &value, sizeof(value));
|
||||
}
|
||||
|
||||
void write(const T& value, const size_t index = 0) const
|
||||
{
|
||||
this->memory_->write_memory(this->address_ + index * this->size(), &value, sizeof(value));
|
||||
|
||||
@@ -641,7 +641,14 @@ void memory_manager::read_memory(const uint64_t address, void* data, const size_
|
||||
|
||||
bool memory_manager::try_read_memory(const uint64_t address, void* data, const size_t size) const
|
||||
{
|
||||
return this->memory_->try_read_memory(address, data, size);
|
||||
try
|
||||
{
|
||||
return this->memory_->try_read_memory(address, data, size);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void memory_manager::write_memory(const uint64_t address, const void* data, const size_t size)
|
||||
@@ -649,6 +656,18 @@ void memory_manager::write_memory(const uint64_t address, const void* data, cons
|
||||
this->memory_->write_memory(address, data, size);
|
||||
}
|
||||
|
||||
bool memory_manager::try_write_memory(const uint64_t address, const void* data, const size_t size)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this->memory_->try_write_memory(address, data, size);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void memory_manager::map_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb)
|
||||
{
|
||||
this->memory_->map_mmio(address, size, std::move(read_cb), std::move(write_cb));
|
||||
|
||||
@@ -65,6 +65,7 @@ class memory_manager : public memory_interface
|
||||
void read_memory(uint64_t address, void* data, size_t size) const final;
|
||||
bool try_read_memory(uint64_t address, void* data, size_t size) const final;
|
||||
void write_memory(uint64_t address, const void* data, size_t size) final;
|
||||
bool try_write_memory(uint64_t address, const void* data, size_t size) final;
|
||||
|
||||
bool protect_memory(uint64_t address, size_t size, nt_memory_permission permissions, nt_memory_permission* old_permissions = nullptr);
|
||||
|
||||
|
||||
@@ -388,6 +388,7 @@ void process_context::setup(x86_64_emulator& emu, memory_manager& memory, regist
|
||||
this->rtl_user_thread_start = ntdll.find_export("RtlUserThreadStart");
|
||||
this->ki_user_apc_dispatcher = ntdll.find_export("KiUserApcDispatcher");
|
||||
this->ki_user_exception_dispatcher = ntdll.find_export("KiUserExceptionDispatcher");
|
||||
this->instrumentation_callback = 0;
|
||||
|
||||
this->default_register_set = emu.save_registers();
|
||||
}
|
||||
@@ -413,6 +414,7 @@ void process_context::serialize(utils::buffer_serializer& buffer) const
|
||||
buffer.write_optional(this->rtl_user_thread_start32);
|
||||
buffer.write(this->ki_user_apc_dispatcher);
|
||||
buffer.write(this->ki_user_exception_dispatcher);
|
||||
buffer.write(this->instrumentation_callback);
|
||||
|
||||
buffer.write(this->events);
|
||||
buffer.write(this->files);
|
||||
@@ -467,6 +469,7 @@ void process_context::deserialize(utils::buffer_deserializer& buffer)
|
||||
buffer.read_optional(this->rtl_user_thread_start32);
|
||||
buffer.read(this->ki_user_apc_dispatcher);
|
||||
buffer.read(this->ki_user_exception_dispatcher);
|
||||
buffer.read(this->instrumentation_callback);
|
||||
|
||||
buffer.read(this->events);
|
||||
buffer.read(this->files);
|
||||
|
||||
@@ -110,6 +110,7 @@ struct process_context
|
||||
uint64_t rtl_user_thread_start{};
|
||||
uint64_t ki_user_apc_dispatcher{};
|
||||
uint64_t ki_user_exception_dispatcher{};
|
||||
uint64_t instrumentation_callback{};
|
||||
|
||||
// For WOW64 processes
|
||||
std::optional<emulator_object<PEB32>> peb32;
|
||||
|
||||
@@ -101,6 +101,8 @@ void syscall_dispatcher::dispatch(windows_emulator& win_emu)
|
||||
}
|
||||
|
||||
entry->second.handler(c);
|
||||
|
||||
dispatch_callback(win_emu, entry->second.name);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
@@ -116,6 +118,24 @@ void syscall_dispatcher::dispatch(windows_emulator& win_emu)
|
||||
}
|
||||
}
|
||||
|
||||
void syscall_dispatcher::dispatch_callback(windows_emulator& win_emu, std::string& syscall_name)
|
||||
{
|
||||
auto& emu = win_emu.emu();
|
||||
auto& context = win_emu.process;
|
||||
|
||||
if (context.instrumentation_callback != 0 && syscall_name != "NtContinue")
|
||||
{
|
||||
auto rip_old = emu.reg<uint64_t>(x86_register::rip);
|
||||
|
||||
// The increase in RIP caused by executing the syscall here has not yet occurred.
|
||||
// If RIP is set directly, it will lead to an incorrect address, so the length of
|
||||
// the syscall instruction needs to be subtracted.
|
||||
emu.reg<uint64_t>(x86_register::rip, context.instrumentation_callback - 2);
|
||||
|
||||
emu.reg<uint64_t>(x86_register::r10, rip_old);
|
||||
}
|
||||
}
|
||||
|
||||
syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports, const std::span<const std::byte> ntdll_data,
|
||||
const exported_symbols& win32u_exports, const std::span<const std::byte> win32u_data)
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ class syscall_dispatcher
|
||||
std::span<const std::byte> win32u_data);
|
||||
|
||||
void dispatch(windows_emulator& win_emu);
|
||||
static void dispatch_callback(windows_emulator& win_emu, std::string& syscall_name);
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const;
|
||||
void deserialize(utils::buffer_deserializer& buffer);
|
||||
|
||||
@@ -232,7 +232,7 @@ NTSTATUS handle_query(x86_64_emulator& emu, const uint64_t buffer, const uint32_
|
||||
const auto length_setter = [&](const size_t required_size) {
|
||||
if (return_length)
|
||||
{
|
||||
return_length.write(static_cast<LengthType>(required_size));
|
||||
return_length.try_write(static_cast<LengthType>(required_size));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace syscalls
|
||||
// syscalls/exception.cpp
|
||||
NTSTATUS handle_NtRaiseHardError(const syscall_context& c, NTSTATUS error_status, ULONG number_of_parameters,
|
||||
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> unicode_string_parameter_mask,
|
||||
emulator_object<DWORD> parameters, HARDERROR_RESPONSE_OPTION valid_response_option,
|
||||
uint64_t parameters, HARDERROR_RESPONSE_OPTION valid_response_option,
|
||||
emulator_object<HARDERROR_RESPONSE> response);
|
||||
NTSTATUS handle_NtRaiseException(const syscall_context& c,
|
||||
emulator_object<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>> exception_record,
|
||||
@@ -131,6 +131,9 @@ namespace syscalls
|
||||
NTSTATUS handle_NtReadVirtualMemory(const syscall_context& c, handle process_handle, emulator_pointer base_address,
|
||||
emulator_pointer buffer, ULONG number_of_bytes_to_read,
|
||||
emulator_object<ULONG> number_of_bytes_read);
|
||||
NTSTATUS handle_NtWriteVirtualMemory(const syscall_context& c, handle process_handle, emulator_pointer base_address,
|
||||
emulator_pointer buffer, ULONG number_of_bytes_to_write,
|
||||
emulator_object<ULONG> number_of_bytes_write);
|
||||
NTSTATUS handle_NtSetInformationVirtualMemory();
|
||||
BOOL handle_NtLockVirtualMemory();
|
||||
|
||||
@@ -1094,6 +1097,7 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
|
||||
add_handler(NtCreateSemaphore);
|
||||
add_handler(NtOpenSemaphore);
|
||||
add_handler(NtReadVirtualMemory);
|
||||
add_handler(NtWriteVirtualMemory);
|
||||
add_handler(NtQueryInformationToken);
|
||||
add_handler(NtDxgkIsFeatureEnabled);
|
||||
add_handler(NtAddAtomEx);
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
|
||||
namespace syscalls
|
||||
{
|
||||
NTSTATUS handle_NtRaiseHardError(const syscall_context& c, const NTSTATUS error_status, const ULONG /*number_of_parameters*/,
|
||||
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>>
|
||||
/*unicode_string_parameter_mask*/,
|
||||
const emulator_object<DWORD> /*parameters*/, const HARDERROR_RESPONSE_OPTION /*valid_response_option*/,
|
||||
NTSTATUS handle_NtRaiseHardError(const syscall_context& c, const NTSTATUS error_status, const ULONG number_of_parameters,
|
||||
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> /*unicode_string_parameter_mask*/,
|
||||
const uint64_t parameters, const HARDERROR_RESPONSE_OPTION /*valid_response_option*/,
|
||||
const emulator_object<HARDERROR_RESPONSE> response)
|
||||
{
|
||||
if (response)
|
||||
@@ -15,6 +14,18 @@ namespace syscalls
|
||||
response.write(ResponseAbort);
|
||||
}
|
||||
|
||||
if (error_status & STATUS_SERVICE_NOTIFICATION && number_of_parameters >= 3)
|
||||
{
|
||||
std::array<uint64_t, 3> params = {0, 0, 0};
|
||||
|
||||
if (c.emu.try_read_memory(parameters, ¶ms, sizeof(params)))
|
||||
{
|
||||
std::u16string message =
|
||||
read_unicode_string(c.emu, emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>>{c.emu, params[0]});
|
||||
c.win_emu.log.error("Error Message: %s\n", u16_to_u8(message).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
c.proc.exit_status = error_status;
|
||||
c.win_emu.callbacks.on_exception();
|
||||
c.emu.stop();
|
||||
|
||||
@@ -1014,6 +1014,12 @@ namespace syscalls
|
||||
std::error_code ec{};
|
||||
|
||||
const windows_path path = f.name;
|
||||
|
||||
if (!path.is_absolute())
|
||||
{
|
||||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
const bool is_directory = std::filesystem::is_directory(c.win_emu.file_sys.translate(path), ec);
|
||||
|
||||
if (is_directory || create_options & FILE_DIRECTORY_FILE)
|
||||
|
||||
@@ -29,7 +29,9 @@ namespace syscalls
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (info_class == MemoryBasicInformation)
|
||||
// https://www.exploit-db.com/exploits/44464
|
||||
// Both information classes appear to return the same output structure, MEMORY_BASIC_INFORMATION
|
||||
if (info_class == MemoryBasicInformation || info_class == MemoryPrivilegedBasicInformation)
|
||||
{
|
||||
if (return_length)
|
||||
{
|
||||
@@ -208,6 +210,16 @@ namespace syscalls
|
||||
{
|
||||
potential_base = c.win_emu.memory.find_free_allocation_base(static_cast<size_t>(allocation_bytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntallocatevirtualmemory
|
||||
// BaseAddress
|
||||
// A pointer to a variable that will receive the base address of the allocated region of pages. If the
|
||||
// initial value of BaseAddress is non-NULL, the region is allocated starting at the specified virtual
|
||||
// address rounded down to the next host page size address boundary. If the initial value of BaseAddress
|
||||
// is NULL, the operating system will determine where to allocate the region.
|
||||
potential_base = page_align_up(potential_base);
|
||||
}
|
||||
|
||||
if (!potential_base)
|
||||
{
|
||||
@@ -280,23 +292,53 @@ namespace syscalls
|
||||
const emulator_pointer buffer, const ULONG number_of_bytes_to_read,
|
||||
const emulator_object<ULONG> number_of_bytes_read)
|
||||
{
|
||||
number_of_bytes_read.write(0);
|
||||
number_of_bytes_read.try_write(0);
|
||||
|
||||
if (process_handle != CURRENT_PROCESS)
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> memory{};
|
||||
memory.resize(number_of_bytes_to_read);
|
||||
std::vector<uint8_t> memory(number_of_bytes_to_read, 0);
|
||||
|
||||
if (!c.emu.try_read_memory(base_address, memory.data(), memory.size()))
|
||||
if (!c.emu.try_read_memory(base_address, memory.data(), number_of_bytes_to_read))
|
||||
{
|
||||
return STATUS_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
c.emu.write_memory(buffer, memory.data(), memory.size());
|
||||
number_of_bytes_read.write(number_of_bytes_to_read);
|
||||
if (!c.emu.try_write_memory(buffer, memory.data(), number_of_bytes_to_read))
|
||||
{
|
||||
return STATUS_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
number_of_bytes_read.try_write(number_of_bytes_to_read);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtWriteVirtualMemory(const syscall_context& c, const handle process_handle, const emulator_pointer base_address,
|
||||
const emulator_pointer buffer, const ULONG number_of_bytes_to_write,
|
||||
const emulator_object<ULONG> number_of_bytes_write)
|
||||
{
|
||||
number_of_bytes_write.try_write(0);
|
||||
|
||||
if (process_handle != CURRENT_PROCESS)
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> memory(number_of_bytes_to_write, 0);
|
||||
|
||||
if (!c.emu.try_read_memory(buffer, memory.data(), number_of_bytes_to_write))
|
||||
{
|
||||
return STATUS_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (!c.emu.try_write_memory(base_address, memory.data(), number_of_bytes_to_write))
|
||||
{
|
||||
return STATUS_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
number_of_bytes_write.try_write(number_of_bytes_to_write);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@ namespace syscalls
|
||||
NTSTATUS handle_NtClose(const syscall_context& c, const handle h)
|
||||
{
|
||||
const auto value = h.value;
|
||||
|
||||
if (h.h == 0xDEADC0DE)
|
||||
{
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (value.is_pseudo)
|
||||
{
|
||||
return STATUS_SUCCESS;
|
||||
|
||||
@@ -281,6 +281,22 @@ namespace syscalls
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (info_class == ProcessInstrumentationCallback)
|
||||
{
|
||||
if (process_information_length != sizeof(PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION))
|
||||
{
|
||||
return STATUS_BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION info;
|
||||
|
||||
c.emu.read_memory(process_information, &info, sizeof(PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION));
|
||||
|
||||
c.proc.instrumentation_callback = info.Callback;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
c.win_emu.log.error("Unsupported info process class: %X\n", info_class);
|
||||
c.emu.stop();
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace syscalls
|
||||
}
|
||||
|
||||
if (attributes.RootDirectory != KNOWN_DLLS_DIRECTORY && attributes.RootDirectory != KNOWN_DLLS32_DIRECTORY &&
|
||||
attributes.RootDirectory != BASE_NAMED_OBJECTS_DIRECTORY)
|
||||
attributes.RootDirectory != BASE_NAMED_OBJECTS_DIRECTORY && !filename.starts_with(u"\\KnownDlls"))
|
||||
{
|
||||
c.win_emu.log.error("Unsupported section\n");
|
||||
c.emu.stop();
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace syscalls
|
||||
|
||||
if (info_class == ThreadHideFromDebugger)
|
||||
{
|
||||
c.win_emu.current_thread().debugger_hide = true;
|
||||
c.win_emu.callbacks.on_suspicious_activity("Hiding thread from debugger");
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
@@ -134,6 +135,8 @@ namespace syscalls
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
emulator_thread& cur_emulator_thread = c.win_emu.current_thread();
|
||||
|
||||
if (info_class == ThreadWow64Context)
|
||||
{
|
||||
// ThreadWow64Context is only valid for WOW64 processes
|
||||
@@ -277,7 +280,7 @@ namespace syscalls
|
||||
}
|
||||
|
||||
const emulator_object<BOOLEAN> info{c.emu, thread_information};
|
||||
info.write(0);
|
||||
info.write(cur_emulator_thread.debugger_hide);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user