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:
Maurice Heumann
2025-12-30 17:53:13 +01:00
committed by GitHub
22 changed files with 190 additions and 17 deletions

View File

@@ -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");
}

View File

@@ -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));

View File

@@ -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)

View File

@@ -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

View File

@@ -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;

View File

@@ -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()

View File

@@ -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));

View File

@@ -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));

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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));
}
};

View File

@@ -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);

View File

@@ -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, &params, 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();

View File

@@ -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)

View 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;
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;
}