From a259072b27ed05f935bb90db9c054199d03b6b9d Mon Sep 17 00:00:00 2001 From: momo5502 Date: Tue, 24 Sep 2024 18:35:34 +0200 Subject: [PATCH] More fuzzing progress --- src/bad-sample/CMakeLists.txt | 11 +++ src/bad-sample/bad.cpp | 70 +++++++++++++++ src/emulator/emulator.hpp | 2 + src/emulator/serialization.hpp | 29 ++++++- src/fuzzer/main.cpp | 57 +++++++----- src/fuzzing-engine/fuzzer.cpp | 14 ++- src/fuzzing-engine/input_generator.cpp | 60 +++++++++---- src/fuzzing-engine/input_generator.hpp | 6 ++ src/unicorn-emulator/unicorn_x64_emulator.cpp | 26 ++++++ src/windows-emulator/module/mapped_module.hpp | 13 +++ src/windows-emulator/process_context.hpp | 5 +- src/windows-emulator/syscalls.cpp | 86 +++++++++++-------- src/windows-emulator/windows_emulator.cpp | 35 ++++---- src/windows-emulator/windows_emulator.hpp | 1 + 14 files changed, 321 insertions(+), 94 deletions(-) create mode 100644 src/bad-sample/CMakeLists.txt create mode 100644 src/bad-sample/bad.cpp diff --git a/src/bad-sample/CMakeLists.txt b/src/bad-sample/CMakeLists.txt new file mode 100644 index 00000000..93e997ae --- /dev/null +++ b/src/bad-sample/CMakeLists.txt @@ -0,0 +1,11 @@ +file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS + *.cpp + *.hpp + *.rc +) + +list(SORT SRC_FILES) + +add_executable(bad-sample ${SRC_FILES}) + +momo_assign_source_group(${SRC_FILES}) diff --git a/src/bad-sample/bad.cpp b/src/bad-sample/bad.cpp new file mode 100644 index 00000000..66333627 --- /dev/null +++ b/src/bad-sample/bad.cpp @@ -0,0 +1,70 @@ +#include +#include +#include + +#define THE_SIZE 30 + +extern "C" __declspec(noinline) +__declspec(dllexport) +void vulnerable(const uint8_t* data, const size_t size) +{ + if (size < 10) + { + return; + } + + if (data[9] != 'A') + { + return; + } + + if (data[8] != 'B') + { + return; + } + + if (data[7] != 'C') + { + return; + } + + if (data[2] != 'V') + { + return; + } + + if (data[4] != 'H') + { + return; + } + + if (data[0] != 'H' || data[1] != 'u') + { + return; + } + + if (size < 100) + { + return; + } + + *(int*)1 = 1; +} + +uint8_t buffer[THE_SIZE] = {}; + + +int main(int argc, const char* argv[]) +{ + const void* input = buffer; + auto size = sizeof(buffer); + + if (argc > 1) + { + input = argv[1]; + size = strlen(argv[1]); + } + + vulnerable((uint8_t*)input, size); + return 0; +} diff --git a/src/emulator/emulator.hpp b/src/emulator/emulator.hpp index 19bd31f6..c6bdd5e9 100644 --- a/src/emulator/emulator.hpp +++ b/src/emulator/emulator.hpp @@ -36,6 +36,7 @@ struct basic_block using edge_generation_hook_callback = std::function; +using basic_block_hook_callback = std::function; using instruction_hook_callback = std::function; @@ -73,6 +74,7 @@ public: virtual emulator_hook* hook_interrupt(interrupt_hook_callback callback) = 0; virtual emulator_hook* hook_edge_generation(edge_generation_hook_callback callback) = 0; + virtual emulator_hook* hook_basic_block(basic_block_hook_callback callback) = 0; virtual void delete_hook(emulator_hook* hook) = 0; diff --git a/src/emulator/serialization.hpp b/src/emulator/serialization.hpp index 14e485d6..4f0a704d 100644 --- a/src/emulator/serialization.hpp +++ b/src/emulator/serialization.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace utils { @@ -12,7 +13,7 @@ namespace utils class buffer_deserializer; template - concept Serializable = requires(T a, const T ac, buffer_serializer & serializer, buffer_deserializer & deserializer) + concept Serializable = requires(T a, const T ac, buffer_serializer& serializer, buffer_deserializer& deserializer) { { ac.serialize(serializer) } -> std::same_as; { a.deserialize(deserializer) } -> std::same_as; @@ -100,7 +101,7 @@ namespace utils this->offset_ += sizeof(old_size); #endif - + return result; } @@ -148,6 +149,19 @@ namespace utils return object; } + template + void read_optional(std::optional& val) + { + if (this->read()) + { + val = this->read(); + } + else + { + val = {}; + } + } + template void read_vector(std::vector& result) { @@ -294,6 +308,17 @@ namespace utils } } + template + void write_optional(const std::optional& val) + { + this->write(val.has_value()); + + if (val.has_value()) + { + this->write(*val); + } + } + template void write_span(const std::span vec) { diff --git a/src/fuzzer/main.cpp b/src/fuzzer/main.cpp index 8fa3ee2c..0eaa50c2 100644 --- a/src/fuzzer/main.cpp +++ b/src/fuzzer/main.cpp @@ -15,6 +15,11 @@ namespace { win_emu.logger.disable_output(true); win_emu.emu().start_from_ip(); + + if (win_emu.process().exception_rip.has_value()) + { + throw std::runtime_error("Exception!"); + } } catch (...) { @@ -28,7 +33,8 @@ namespace void forward_emulator(windows_emulator& win_emu) { - win_emu.emu().hook_memory_execution(0x140001000, 1, [&](uint64_t, size_t, uint64_t) + const auto target = win_emu.process().executable->find_export("vulnerable"); + win_emu.emu().hook_memory_execution(target, 1, [&](uint64_t, size_t, uint64_t) { win_emu.emu().stop(); }); @@ -39,41 +45,50 @@ namespace struct fuzzer_executer : fuzzer::executer { windows_emulator emu{}; + std::span emulator_data{}; + std::unordered_set visited_blocks{}; const std::function* handler{nullptr}; fuzzer_executer(std::span data) + : emulator_data(data) { - utils::buffer_deserializer deserializer{data}; - emu.deserialize(deserializer); - //emu.save_snapshot(); - - emu.emu().hook_edge_generation([&](const basic_block& current_block, - const basic_block&) + emu.fuzzing = true; + emu.emu().hook_basic_block([&](const basic_block& block) { - if (this->handler) + if (this->handler && visited_blocks.emplace(block.address).second) { - (*this->handler)(current_block.address); + (*this->handler)(block.address); } }); + + utils::buffer_deserializer deserializer{emulator_data}; + emu.deserialize(deserializer); + emu.save_snapshot(); + + const auto ret = emu.emu().read_stack(0); + + emu.emu().hook_memory_execution(ret, 1, [&](uint64_t, size_t, uint64_t) + { + emu.emu().stop(); + }); + } + + void restore_emulator() + { + /*utils::buffer_deserializer deserializer{ emulator_data }; + emu.deserialize(deserializer);*/ + emu.restore_snapshot(); } fuzzer::execution_result execute(std::span data, const std::function& coverage_handler) override { - printf("Input size: %zd\n", data.size()); + //printf("Input size: %zd\n", data.size()); this->handler = &coverage_handler; + this->visited_blocks.clear(); - utils::buffer_serializer serializer{}; - emu.serialize(serializer); - - const auto _ = utils::finally([&] - { - utils::buffer_deserializer deserializer{serializer.get_buffer()}; - emu.deserialize(deserializer); - }); - - //emu.restore_snapshot(); + restore_emulator(); const auto memory = emu.emu().allocate_memory(page_align_up(std::max(data.size(), 1ULL)), memory_permission::read_write); @@ -117,7 +132,7 @@ namespace void run_fuzzer(const windows_emulator& base_emulator) { - const auto concurrency = std::thread::hardware_concurrency(); + const auto concurrency = std::thread::hardware_concurrency() + 2; utils::buffer_serializer serializer{}; base_emulator.serialize(serializer); diff --git a/src/fuzzing-engine/fuzzer.cpp b/src/fuzzing-engine/fuzzer.cpp index 190f0e8a..c207eca1 100644 --- a/src/fuzzing-engine/fuzzer.cpp +++ b/src/fuzzing-engine/fuzzer.cpp @@ -37,13 +37,15 @@ namespace fuzzer input_generator& generator; handler& handler; + std::atomic_uint64_t executions{0}; private: std::atomic_bool stop_{false}; }; - void perform_fuzzing_iteration(const fuzzing_context& context, executer& executer) + void perform_fuzzing_iteration(fuzzing_context& context, executer& executer) { + ++context.executions; context.generator.access_input([&](const std::span input) { uint64_t score{0}; @@ -52,9 +54,10 @@ namespace fuzzer ++score; }); - if(result == execution_result::error) + if (result == execution_result::error) { - printf("Found error!"); + printf("Found error!\n"); + context.stop(); } return score; @@ -116,6 +119,11 @@ namespace fuzzer while (!context.should_stop()) { std::this_thread::sleep_for(std::chrono::seconds{1}); + + const auto executions = context.executions.exchange(0); + const auto highest_scorer = context.generator.get_highest_scorer(); + const auto avg_score = context.generator.get_average_score(); + printf("Executions/s: %lld - Score: %llX - Avg: %.3f\n", executions, highest_scorer.score, avg_score); } } } diff --git a/src/fuzzing-engine/input_generator.cpp b/src/fuzzing-engine/input_generator.cpp index 28cc0e69..a04e869d 100644 --- a/src/fuzzing-engine/input_generator.cpp +++ b/src/fuzzing-engine/input_generator.cpp @@ -1,5 +1,7 @@ #include "input_generator.hpp" +#include + namespace fuzzer { namespace @@ -52,7 +54,31 @@ namespace fuzzer { auto next_input = this->generate_next_input(); const auto score = handler(next_input); - this->store_input_entry({std::move(next_input), score}); + + input_entry e{}; + e.data = std::move(next_input); + e.score = score; + + this->store_input_entry(std::move(e)); + } + + input_entry input_generator::get_highest_scorer() + { + std::unique_lock lock{this->mutex_}; + return this->highest_scorer_; + } + + double input_generator::get_average_score() + { + std::unique_lock lock{this->mutex_}; + + double score{0.0}; + for (const auto& e : this->top_scorer_) + { + score += static_cast(e.score); + } + + return score / static_cast(this->top_scorer_.size()); } void input_generator::store_input_entry(input_entry entry) @@ -64,29 +90,33 @@ namespace fuzzer return; } - const auto score = entry.score; + if (entry.score > this->highest_scorer_.score) + { + this->highest_scorer_ = entry; + } if (this->top_scorer_.size() < MAX_TOP_SCORER) { this->top_scorer_.emplace_back(std::move(entry)); - } - else - { - const auto index = this->rng.get() % this->top_scorer_.size(); - this->top_scorer_[index] = std::move(entry); - } - - this->lowest_score = score; - if (score < this->lowest_score) - { return; } - for (const auto& e : this->top_scorer_) + const auto insert_at_random = this->rng.get(10) == 0; + const auto index = insert_at_random + ? (this->rng.get() % this->top_scorer_.size()) + : this->lowest_scorer; + + this->top_scorer_[index] = std::move(entry); + + this->lowest_score = this->top_scorer_[0].score; + this->lowest_scorer = 0; + + for (size_t i = 1; i < this->top_scorer_.size(); ++i) { - if (e.score < this->lowest_score) + if (this->top_scorer_[i].score < this->lowest_score) { - this->lowest_score = e.score; + this->lowest_score = this->top_scorer_[i].score; + this->lowest_scorer = i; } } } diff --git a/src/fuzzing-engine/input_generator.hpp b/src/fuzzing-engine/input_generator.hpp index 25304874..8cbea089 100644 --- a/src/fuzzing-engine/input_generator.hpp +++ b/src/fuzzing-engine/input_generator.hpp @@ -24,12 +24,18 @@ namespace fuzzer void access_input(const std::function& handler); + input_entry get_highest_scorer(); + double get_average_score(); + private: std::mutex mutex_{}; random_generator rng{}; std::vector top_scorer_{}; input_score lowest_score{0}; + size_t lowest_scorer{0}; + + input_entry highest_scorer_{}; std::vector generate_next_input(); diff --git a/src/unicorn-emulator/unicorn_x64_emulator.cpp b/src/unicorn-emulator/unicorn_x64_emulator.cpp index e7faaa77..693f1e24 100644 --- a/src/unicorn-emulator/unicorn_x64_emulator.cpp +++ b/src/unicorn-emulator/unicorn_x64_emulator.cpp @@ -389,6 +389,32 @@ namespace unicorn return result; } + emulator_hook* hook_basic_block(basic_block_hook_callback callback) override + { + function_wrapper wrapper( + [c = std::move(callback)](uc_engine*, const uint64_t address, const size_t size) + { + basic_block block{}; + block.address = address; + block.size = size; + + c(block); + }); + + unicorn_hook hook{*this}; + auto container = std::make_unique(); + + uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_BLOCK, wrapper.get_function(), + wrapper.get_user_data(), 0, std::numeric_limits::max()) + ); + + container->add(std::move(wrapper), std::move(hook)); + + auto* result = container->as_opaque_hook(); + this->hooks_.push_back(std::move(container)); + return result; + } + emulator_hook* hook_edge_generation(edge_generation_hook_callback callback) override { function_wrapper wrapper( diff --git a/src/windows-emulator/module/mapped_module.hpp b/src/windows-emulator/module/mapped_module.hpp index 0a26dd2b..1590e4c7 100644 --- a/src/windows-emulator/module/mapped_module.hpp +++ b/src/windows-emulator/module/mapped_module.hpp @@ -27,4 +27,17 @@ struct mapped_module { return address >= this->image_base && address < (this->image_base + this->size_of_image); } + + uint64_t find_export(const std::string_view export_name) const + { + for (auto& symbol : this->exports) + { + if (symbol.name == export_name) + { + return symbol.address; + } + } + + return 0; + } }; diff --git a/src/windows-emulator/process_context.hpp b/src/windows-emulator/process_context.hpp index 1de4e4c6..3e6d686b 100644 --- a/src/windows-emulator/process_context.hpp +++ b/src/windows-emulator/process_context.hpp @@ -106,7 +106,6 @@ struct port } }; - struct process_context { process_context(x64_emulator& emu) @@ -123,6 +122,8 @@ struct process_context uint64_t current_ip{0}; uint64_t previous_ip{0}; + std::optional exception_rip{}; + emulator_object teb; emulator_object peb; emulator_object process_params; @@ -150,6 +151,7 @@ struct process_context buffer.write(this->executed_instructions); buffer.write(this->current_ip); buffer.write(this->previous_ip); + buffer.write_optional(this->exception_rip); buffer.write(this->teb); buffer.write(this->peb); buffer.write(this->process_params); @@ -176,6 +178,7 @@ struct process_context buffer.read(this->executed_instructions); buffer.read(this->current_ip); buffer.read(this->previous_ip); + buffer.read_optional(this->exception_rip); buffer.read(this->teb); buffer.read(this->peb); buffer.read(this->process_params); diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index 16e4a2f9..9a98846a 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -1050,6 +1050,24 @@ namespace return STATUS_SUCCESS; } + if (info_class == ProcessDefaultHardErrorMode) + { + if (return_length) + { + return_length.write(sizeof(ULONG)); + } + + if (process_information_length != sizeof(ULONG)) + { + return STATUS_BUFFER_OVERFLOW; + } + + const emulator_object info{c.emu, process_information}; + info.write(0); + + return STATUS_SUCCESS; + } + if (info_class == ProcessEnclaveInformation || info_class == ProcessMitigationPolicy) { @@ -1078,40 +1096,6 @@ namespace return STATUS_SUCCESS; } - if (info_class == ProcessImageFileNameWin32) - { - const auto peb = c.proc.peb.read(); - emulator_object proc_params{c.emu, peb.ProcessParameters}; - const auto params = proc_params.read(); - - const auto length = params.ImagePathName.Length + sizeof(UNICODE_STRING) + 2; - - if (return_length) - { - return_length.write(static_cast(length)); - } - - if (process_information_length < length) - { - return STATUS_BUFFER_OVERFLOW; - } - - const emulator_object info{c.emu, process_information}; - info.access([&](UNICODE_STRING& str) - { - const auto buffer_start = static_cast(process_information) + sizeof(UNICODE_STRING); - const auto string = read_unicode_string(c.emu, params.ImagePathName); - - c.emu.write_memory(buffer_start, string.c_str(), (string.size() + 1) * 2); - - str.Length = params.ImagePathName.Length; - str.MaximumLength = str.Length; - str.Buffer = reinterpret_cast(buffer_start); - }); - - return STATUS_SUCCESS; - } - printf("Unsupported process info class: %X\n", info_class); c.emu.stop(); @@ -1409,7 +1393,7 @@ namespace const emulator_object connection_info_length) { auto port_name = read_unicode_string(c.emu, server_port_name); - printf("NtConnectPort: %S\n", port_name.c_str()); + c.win_emu.logger.print(color::dark_gray, "NtConnectPort: %S\n", port_name.c_str()); port p{}; p.name = std::move(port_name); @@ -1693,6 +1677,16 @@ namespace return STATUS_NOT_SUPPORTED; } + NTSTATUS handle_NtQueryInformationJobObject() + { + return STATUS_NOT_SUPPORTED; + } + + NTSTATUS handle_NtSetSystemInformation() + { + return STATUS_NOT_SUPPORTED; + } + NTSTATUS handle_NtRaiseHardError(const syscall_context& c, const NTSTATUS error_status, const ULONG /*number_of_parameters*/, const emulator_object /*unicode_string_parameter_mask*/, @@ -1706,7 +1700,26 @@ namespace } printf("Hard error: %X\n", static_cast(error_status)); + c.proc.exception_rip = c.emu.read_instruction_pointer(); c.emu.stop(); + + return STATUS_SUCCESS; + } + + NTSTATUS handle_NtRaiseException(const syscall_context& c, + const emulator_object /*exception_record*/, + const emulator_object thread_context, BOOLEAN handle_exception) + { + if (handle_exception) + { + puts("Unhandled exceptions not supported yet!"); + c.emu.stop(); + return STATUS_NOT_SUPPORTED; + } + + c.proc.exception_rip = thread_context.read().Rip; + c.emu.stop(); + return STATUS_SUCCESS; } @@ -1900,6 +1913,9 @@ void syscall_dispatcher::add_handlers() add_handler(NtIsUILanguageComitted); add_handler(NtQueryInstallUILanguage); add_handler(NtUpdateWnfStateData); + add_handler(NtRaiseException); + add_handler(NtQueryInformationJobObject); + add_handler(NtSetSystemInformation); #undef add_handler diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index 2f8773eb..9b44eaef 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -294,19 +294,6 @@ namespace }); } - uint64_t find_exported_function(const std::vector& exports, const std::string_view name) - { - for (auto& symbol : exports) - { - if (symbol.name == name) - { - return symbol.address; - } - } - - return 0; - } - using exception_record_map = std::unordered_map>; emulator_object save_exception_record(emulator_allocator& allocator, @@ -505,9 +492,9 @@ void windows_emulator::setup_process(const std::filesystem::path& application, this->dispatcher_.setup(context.ntdll->exports, context.win32u->exports); - const auto ldr_initialize_thunk = find_exported_function(context.ntdll->exports, "LdrInitializeThunk"); - const auto rtl_user_thread_start = find_exported_function(context.ntdll->exports, "RtlUserThreadStart"); - context.ki_user_exception_dispatcher = find_exported_function(context.ntdll->exports, "KiUserExceptionDispatcher"); + const auto ldr_initialize_thunk = context.ntdll->find_export("LdrInitializeThunk"); + const auto rtl_user_thread_start = context.ntdll->find_export("RtlUserThreadStart"); + context.ki_user_exception_dispatcher = context.ntdll->find_export("KiUserExceptionDispatcher"); CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_ALL; @@ -553,7 +540,14 @@ void windows_emulator::setup_hooks() this->emu().hook_interrupt([&](const int interrupt) { - printf("Interrupt: %i 0x%llX\n", interrupt, this->emu().read_instruction_pointer()); + const auto rip = this->emu().read_instruction_pointer(); + printf("Interrupt: %i 0x%llX\n", interrupt, rip); + + if (this->fuzzing) + { + this->process().exception_rip = rip; + this->emu().stop(); + } }); this->emu().hook_memory_violation([&](const uint64_t address, const size_t size, const memory_operation operation, @@ -574,6 +568,13 @@ void windows_emulator::setup_hooks() name); } + if (this->fuzzing) + { + this->process().exception_rip = ip; + this->emu().stop(); + return memory_violation_continuation::stop; + } + dispatch_access_violation(this->emu(), this->process().ki_user_exception_dispatcher, address, operation); return memory_violation_continuation::resume; }); diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index 6c76dde4..e02ccb5e 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -66,6 +66,7 @@ public: bool verbose{false}; bool verbose_calls{false}; bool buffer_stdout{false}; + bool fuzzing{false}; private: std::unique_ptr emu_{};