void DumpStrings(hadesmem::Process const& process, hadesmem::PeFile const& pe_file) { std::wostream& out = GetOutputStreamW(); std::uint8_t* const file_beg = static_cast<std::uint8_t*>(pe_file.GetBase()); void* const file_end = file_beg + pe_file.GetSize(); // TODO: Fix to not require multiple passes. WriteNewline(out); WriteNormal(out, L"Narrow Strings:", 1); WriteNewline(out); DumpStringsImpl(process, pe_file, file_beg, file_end, false); WriteNewline(out); WriteNormal(out, L"Wide Strings (Pass 1):", 1); WriteNewline(out); DumpStringsImpl(process, pe_file, file_beg, file_end, true); WriteNewline(out); WriteNormal(out, L"Wide Strings (Pass 2):", 1); WriteNewline(out); DumpStringsImpl(process, pe_file, file_beg + 1, file_end, true); }
void HandleLongOrUnprintableString(std::wstring const& name, std::wstring const& description, std::size_t tabs, WarningType warning_type, std::string value) { std::wostream& out = GetOutputStreamW(); // TODO: Fix perf for extremely long names. Instead of reading indefinitely // and then checking the size after the fact, we should perform a bounded // read. auto const unprintable = FindFirstUnprintableClassicLocale(value); std::size_t const kMaxNameLength = 1024; if (unprintable != std::string::npos) { WriteNormal(out, L"WARNING! Detected unprintable " + description + L". Truncating.", tabs); WarnForCurrentFile(warning_type); value.erase(unprintable); } else if (value.size() > kMaxNameLength) { WriteNormal(out, L"WARNING! Detected suspiciously long " + description + L". Truncating.", tabs); WarnForCurrentFile(warning_type); value.erase(kMaxNameLength); } WriteNamedNormal(out, name, value.c_str(), tabs); }
void DisassembleEp(hadesmem::Process const& process, hadesmem::PeFile const& pe_file, std::uintptr_t ep_rva, void* ep_va, std::size_t tabs) { if (!ep_va) { return; } std::wostream& out = GetOutputStreamW(); // Get the number of bytes from the EP to the end of the file. std::size_t max_buffer_size = GetBytesToEndOfFile(pe_file, ep_va); // Clamp the amount of data read to the theoretical maximum. std::size_t const kMaxInstructions = 10U; std::size_t const kMaxInstructionLen = 15U; std::size_t const kMaxInstructionsBytes = kMaxInstructions * kMaxInstructionLen; max_buffer_size = (std::min)(max_buffer_size, kMaxInstructionsBytes); auto const disasm_buf = hadesmem::ReadVector<std::uint8_t>(process, ep_va, max_buffer_size); std::uint64_t const ip = hadesmem::GetRuntimeBase(process, pe_file) + ep_rva; ud_t ud_obj; ud_init(&ud_obj); ud_set_input_buffer(&ud_obj, disasm_buf.data(), max_buffer_size); ud_set_syntax(&ud_obj, UD_SYN_INTEL); ud_set_pc(&ud_obj, ip); ud_set_mode(&ud_obj, pe_file.Is64() ? 64 : 32); // Be pessimistic. Use the minimum theoretical amount of instrutions we could // fit in our buffer. std::size_t const instruction_count = max_buffer_size / kMaxInstructionLen; for (std::size_t i = 0U; i < instruction_count; ++i) { std::uint32_t const len = ud_disassemble(&ud_obj); if (len == 0) { WriteNormal(out, L"WARNING! Disassembly failed.", tabs); // If we can't disassemble at least 5 instructions there's probably // something strange about the function. Even in the case of a nullsub // there is typically some INT3 or NOP padding after it... WarnForCurrentFile(i < 5U ? WarningType::kUnsupported : WarningType::kSuspicious); break; } char const* const asm_str = ud_insn_asm(&ud_obj); HADESMEM_DETAIL_ASSERT(asm_str); char const* const asm_bytes_str = ud_insn_hex(&ud_obj); HADESMEM_DETAIL_ASSERT(asm_bytes_str); auto const diasm_line = hadesmem::detail::MultiByteToWideChar(asm_str) + L" (" + hadesmem::detail::MultiByteToWideChar(asm_bytes_str) + L")"; WriteNormal(out, diasm_line, tabs); } }
void DumpPeFile(hadesmem::Process const& process, hadesmem::PeFile const& pe_file, std::wstring const& path) { std::wostream& out = GetOutputStreamW(); ClearWarnForCurrentFile(); WriteNewline(out); std::wstring const architecture_str{pe_file.Is64() ? L"64-Bit File: Yes" : L"64-Bit File: No"}; WriteNormal(out, architecture_str, 1); std::uint32_t const k1MB = (1U << 20); std::uint32_t const k100MB = k1MB * 100; if (pe_file.GetSize() > k100MB) { // Not actually unsupported, just want to flag large files for use in perf // testing. WriteNewline(out); WriteNormal(out, L"WARNING! File is over 100MB.", 0); // WarnForCurrentFile(WarningType::kUnsupported); } DumpHeaders(process, pe_file); DumpSections(process, pe_file); DumpOverlay(process, pe_file); DumpTls(process, pe_file); DumpExports(process, pe_file); bool has_new_bound_imports_any = false; DumpImports(process, pe_file, has_new_bound_imports_any); DumpBoundImports(process, pe_file, has_new_bound_imports_any); DumpRelocations(process, pe_file); if (!g_quiet && g_strings) { DumpStrings(process, pe_file); } HandleWarnings(path); }
void DumpSections(hadesmem::Process const& process, hadesmem::PeFile const& pe_file) { hadesmem::SectionList sections(process, pe_file); std::wostream& out = GetOutputStreamW(); if (std::begin(sections) != std::end(sections)) { WriteNewline(out); WriteNormal(out, L"Sections:", 1); } else { // Other checks on number of sections are done as part of header handling. hadesmem::NtHeaders const nt_hdrs(process, pe_file); if (nt_hdrs.GetNumberOfSections()) { WriteNewline(out); WriteNormal(out, L"WARNING! Section list is inavlid.", 1); WarnForCurrentFile(WarningType::kUnsupported); } } for (auto const& s : sections) { WriteNewline(out); if (s.IsVirtual()) { WriteNormal(out, L"WARNING! Section is virtual.", 2); WarnForCurrentFile(WarningType::kSuspicious); } HandleLongOrUnprintableString( L"name", L"section name", 2, WarningType::kSuspicious, s.GetName()); WriteNamedHex(out, L"VirtualAddress", s.GetVirtualAddress(), 2); WriteNamedHex(out, L"VirtualSize", s.GetVirtualSize(), 2); WriteNamedHex(out, L"PointerToRawData", s.GetPointerToRawData(), 2); WriteNamedHex(out, L"SizeOfRawData", s.GetSizeOfRawData(), 2); WriteNamedHex(out, L"PointerToRelocations", s.GetPointerToRelocations(), 2); WriteNamedHex(out, L"PointerToLinenumbers", s.GetPointerToLinenumbers(), 2); WriteNamedHex(out, L"NumberOfRelocations", s.GetNumberOfRelocations(), 2); WriteNamedHex(out, L"NumberOfLinenumbers", s.GetNumberOfLinenumbers(), 2); WriteNamedHex(out, L"Characteristics", s.GetCharacteristics(), 2); } }
void DumpTls(hadesmem::Process const& process, hadesmem::PeFile const& pe_file) { std::unique_ptr<hadesmem::TlsDir const> tls_dir; try { tls_dir = std::make_unique<hadesmem::TlsDir>(process, pe_file); } catch (std::exception const& /*e*/) { return; } std::wostream& out = GetOutputStreamW(); WriteNewline(out); WriteNormal(out, L"TLS:", 1); WriteNewline(out); WriteNamedHex( out, L"StartAddressOfRawData", tls_dir->GetStartAddressOfRawData(), 2); WriteNamedHex( out, L"EndAddressOfRawData", tls_dir->GetEndAddressOfRawData(), 2); WriteNamedHex(out, L"AddressOfIndex", tls_dir->GetAddressOfIndex(), 2); WriteNamedHex( out, L"AddressOfCallBacks", tls_dir->GetAddressOfCallBacks(), 2); if (tls_dir->GetAddressOfCallBacks()) { std::vector<ULONGLONG> callbacks; try { tls_dir->GetCallbacks(std::back_inserter(callbacks)); } catch (std::exception const& /*e*/) { WriteNormal(out, L"WARNING! TLS callbacks are inavlid.", 2); WarnForCurrentFile(WarningType::kSuspicious); } for (auto const& c : callbacks) { WriteNamedHex(out, L"Callback", static_cast<DWORD_PTR>(c), 2); } } WriteNamedHex(out, L"SizeOfZeroFill", tls_dir->GetSizeOfZeroFill(), 2); WriteNamedHex(out, L"Characteristics", tls_dir->GetCharacteristics(), 2); }
void DumpBoundImports(hadesmem::Process const& process, hadesmem::PeFile const& pe_file, bool has_new_bound_imports_any) { std::wostream& out = GetOutputStreamW(); if (!HasBoundImportDir(process, pe_file)) { // Sample: dllmaxvals.dll (Corkami PE Corpus) if (has_new_bound_imports_any) { WriteNewline(out); WriteNormal( out, L"WARNING! No bound import directory on file with an import dir " L"indicating the presence of a bound import dir.", 1); WarnForCurrentFile(WarningType::kSuspicious); } return; } if (!has_new_bound_imports_any) { WriteNewline(out); WriteNormal( out, L"WARNING! Seemingly valid bound import directory on file with an " L"import dir indicating no new bound import dir.", 1); WarnForCurrentFile(WarningType::kSuspicious); return; } hadesmem::BoundImportDescriptorList const bound_import_descs(process, pe_file); if (std::begin(bound_import_descs) == std::end(bound_import_descs)) { WriteNewline(out); WriteNormal(out, L"WARNING! Empty or invalid bound import directory.", 1); WarnForCurrentFile(WarningType::kSuspicious); return; } WriteNewline(out); WriteNormal(out, L"Bound Import Descriptors:", 1); std::uint32_t num_descs = 0U; for (auto const& desc : bound_import_descs) { WriteNewline(out); if (num_descs++ == 1000) { WriteNormal(out, L"WARNING! Processed 1000 bound import descriptors. Stopping " L"early to avoid resource exhaustion attacks.", 2); WarnForCurrentFile(WarningType::kUnsupported); break; } DWORD const time_date_stamp = desc.GetTimeDateStamp(); std::wstring time_date_stamp_str; if (!ConvertTimeStamp(time_date_stamp, time_date_stamp_str)) { WriteNormal(out, L"WARNING! Invalid timestamp.", 2); WarnForCurrentFile(WarningType::kSuspicious); } WriteNamedHexSuffix( out, L"TimeDateStamp", time_date_stamp, time_date_stamp_str, 2); WriteNamedHex(out, L"OffsetModuleName", desc.GetOffsetModuleName(), 2); WriteNamedNormal(out, L"ModuleName", desc.GetModuleName().c_str(), 2); WriteNamedHex(out, L"NumberOfModuleForwarderRefs", desc.GetNumberOfModuleForwarderRefs(), 2); hadesmem::BoundImportForwarderRefList const forwarder_refs( process, pe_file, desc); if (std::begin(forwarder_refs) != std::end(forwarder_refs)) { WriteNewline(out); WriteNormal(out, L"Module Forwarder Refs:", 2); } for (auto const& forwarder : forwarder_refs) { WriteNewline(out); DWORD const fwd_time_date_stamp = forwarder.GetTimeDateStamp(); std::wstring fwd_time_date_stamp_str; if (!ConvertTimeStamp(fwd_time_date_stamp, fwd_time_date_stamp_str)) { WriteNormal(out, L"WARNING! Invalid timestamp.", 3); WarnForCurrentFile(WarningType::kSuspicious); } WriteNamedHexSuffix( out, L"TimeDateStamp", fwd_time_date_stamp, fwd_time_date_stamp_str, 3); WriteNamedHex( out, L"OffsetModuleName", forwarder.GetOffsetModuleName(), 3); WriteNamedNormal( out, L"ModuleName", forwarder.GetModuleName().c_str(), 3); WriteNamedHex(out, L"Reserved", forwarder.GetReserved(), 3); } } }
void DumpMemory(hadesmem::Process const& process, bool continue_on_error) { std::wostream& out = GetOutputStreamW(); WriteNewline(out); WriteNormal(out, "Dumping image memory to disk.", 0); hadesmem::ModuleList modules(process); for (auto const& module : modules) { WriteNormal(out, "Checking for valid headers.", 0); try { hadesmem::PeFile const pe_file(process, module.GetHandle(), hadesmem::PeFileType::Image, static_cast<DWORD>(module.GetSize())); hadesmem::NtHeaders nt_headers(process, pe_file); } catch (std::exception const& /*e*/) { WriteNormal(out, "WARNING! Invalid headers.", 0); return; } WriteNormal(out, L"Reading memory.", 1); auto raw = hadesmem::ReadVectorEx<std::uint8_t>( process, module.GetHandle(), module.GetSize(), hadesmem::ReadFlags::kZeroFillReserved); hadesmem::Process const local_process(::GetCurrentProcessId()); hadesmem::PeFile const pe_file(local_process, raw.data(), hadesmem::PeFileType::Image, static_cast<DWORD>(raw.size())); hadesmem::NtHeaders nt_headers(local_process, pe_file); WriteNormal(out, L"Copying headers.", 1); std::vector<std::uint8_t> raw_new; std::copy(std::begin(raw), std::begin(raw) + nt_headers.GetSizeOfHeaders(), std::back_inserter(raw_new)); WriteNormal(out, L"Copying section data.", 1); hadesmem::SectionList const sections(local_process, pe_file); std::vector<std::pair<DWORD, DWORD>> raw_datas; for (auto const& section : sections) { auto const section_size = (std::max)(section.GetVirtualSize(), section.GetSizeOfRawData()); auto const ptr_raw_data_new = section.GetPointerToRawData() < raw_new.size() ? static_cast<DWORD>( RoundUp(raw_new.size(), nt_headers.GetFileAlignment())) : section.GetPointerToRawData(); raw_datas.emplace_back(ptr_raw_data_new, section_size); if (ptr_raw_data_new > raw_new.size()) { raw_new.resize(ptr_raw_data_new); } auto const raw_data = raw.data() + section.GetVirtualAddress(); auto const raw_data_end = raw_data + section_size; raw_new.reserve(raw_new.size() + section_size); std::copy(raw_data, raw_data_end, std::back_inserter(raw_new)); } HADESMEM_DETAIL_ASSERT(raw_new.size() < (std::numeric_limits<DWORD>::max)()); hadesmem::PeFile const pe_file_new(local_process, raw_new.data(), hadesmem::PeFileType::Data, static_cast<DWORD>(raw_new.size())); WriteNormal(out, L"Fixing NT headers.", 1); hadesmem::NtHeaders nt_headers_new(local_process, pe_file_new); nt_headers_new.SetImageBase( reinterpret_cast<ULONG_PTR>(module.GetHandle())); nt_headers_new.UpdateWrite(); WriteNormal(out, L"Fixing section headers.", 1); hadesmem::SectionList sections_new(local_process, pe_file_new); std::size_t n = 0; for (auto& section : sections_new) { section.SetPointerToRawData(raw_datas[n].first); section.SetSizeOfRawData(raw_datas[n].second); section.UpdateWrite(); ++n; } WriteNormal(out, L"Fixing imports.", 1); hadesmem::ImportDirList const import_dirs(local_process, pe_file); hadesmem::ImportDirList const import_dirs_new(local_process, pe_file_new); auto i = std::begin(import_dirs), j = std::begin(import_dirs_new); bool thunk_mismatch = false; for (; i != std::end(import_dirs) && j != std::end(import_dirs_new); ++i, ++j) { hadesmem::ImportThunkList const import_thunks( local_process, pe_file, i->GetOriginalFirstThunk()); hadesmem::ImportThunkList import_thunks_new( local_process, pe_file_new, j->GetFirstThunk()); auto a = std::begin(import_thunks); auto b = std::begin(import_thunks_new); for (; a != std::end(import_thunks) && b != std::end(import_thunks_new); ++a, ++b) { b->SetFunction(a->GetFunction()); b->UpdateWrite(); } thunk_mismatch = thunk_mismatch || ((a != std::end(import_thunks)) ^ (b != std::end(import_thunks_new))); } bool const dir_mismatch = (i != std::end(import_dirs)) ^ (j != std::end(import_dirs)); WriteNormal(out, L"Writing file.", 1); auto const proc_path = hadesmem::GetPath(process); auto const proc_name = proc_path.substr(proc_path.rfind(L'\\') + 1); auto const proc_pid_str = std::to_wstring(process.GetId()); std::wstring dump_path; std::uint32_t c = 0; do { dump_path = proc_name + L"_" + proc_pid_str + L"_" + module.GetName() + L"_" + std::to_wstring(c++) + L".dmp"; } while (hadesmem::detail::DoesFileExist(dump_path) && c < 10); auto const dump_file = hadesmem::detail::OpenFile<char>( dump_path, std::ios::out | std::ios::binary); if (!*dump_file) { HADESMEM_DETAIL_THROW_EXCEPTION(hadesmem::Error() << hadesmem::ErrorString( "Unable to open dump file.")); } if (!dump_file->write(reinterpret_cast<char const*>(raw_new.data()), raw_new.size())) { HADESMEM_DETAIL_THROW_EXCEPTION(hadesmem::Error() << hadesmem::ErrorString( "Unable to write to dump file.")); } try { if (dir_mismatch) { HADESMEM_DETAIL_THROW_EXCEPTION( hadesmem::Error() << hadesmem::ErrorString("Mismatch in import dir processing.")); } if (thunk_mismatch) { HADESMEM_DETAIL_THROW_EXCEPTION( hadesmem::Error() << hadesmem::ErrorString("Mismatch in import thunk processing.")); } } catch (...) { if (continue_on_error) { std::cerr << "\nError!\n" << boost::current_exception_diagnostic_information() << '\n'; std::wcerr << "\nCurrent process: " << hadesmem::GetPathNative(process) << "\nCurrent module: " << module.GetName() << "\n"; } else { throw; } } } }
void DumpExports(hadesmem::Process const& process, hadesmem::PeFile const& pe_file) { std::unique_ptr<hadesmem::ExportDir const> export_dir; try { export_dir = std::make_unique<hadesmem::ExportDir const>(process, pe_file); } catch (std::exception const& /*e*/) { return; } std::wostream& out = GetOutputStreamW(); WriteNewline(out); WriteNormal(out, L"Export Dir:", 1); WriteNewline(out); WriteNamedHex(out, L"Characteristics", export_dir->GetCharacteristics(), 2); DWORD const time_date_stamp = export_dir->GetTimeDateStamp(); std::wstring time_date_stamp_str; if (!ConvertTimeStamp(time_date_stamp, time_date_stamp_str)) { WriteNormal(out, L"WARNING! Invalid timestamp.", 2); WarnForCurrentFile(WarningType::kSuspicious); } WriteNamedHexSuffix( out, L"TimeDateStamp", time_date_stamp, time_date_stamp_str, 2); WriteNamedHex(out, L"MajorVersion", export_dir->GetMajorVersion(), 2); WriteNamedHex(out, L"MinorVersion", export_dir->GetMinorVersion(), 2); WriteNamedHex(out, L"Name (Raw)", export_dir->GetNameRaw(), 2); // Name is not guaranteed to be valid. // Sample: dllord.dll (Corkami PE Corpus) try { auto name = export_dir->GetName(); HandleLongOrUnprintableString( L"Name", L"export module name", 2, WarningType::kSuspicious, name); } catch (std::exception const& /*e*/) { WriteNormal(out, L"WARNING! Failed to read export dir name.", 2); WarnForCurrentFile(WarningType::kSuspicious); } WriteNamedHex(out, L"OrdinalBase", export_dir->GetOrdinalBase(), 2); WriteNamedHex( out, L"NumberOfFunctions", export_dir->GetNumberOfFunctions(), 2); WriteNamedHex(out, L"NumberOfNames", export_dir->GetNumberOfNames(), 2); WriteNamedHex( out, L"AddressOfFunctions", export_dir->GetAddressOfFunctions(), 2); WriteNamedHex(out, L"AddressOfNames", export_dir->GetAddressOfNames(), 2); WriteNamedHex( out, L"AddressOfNameOrdinals", export_dir->GetAddressOfNameOrdinals(), 2); std::set<std::string> export_names; hadesmem::ExportList const exports(process, pe_file); if (std::begin(exports) != std::end(exports)) { WriteNewline(out); WriteNormal(out, L"Exports:", 2); } else { // Legitimate DLLs have an export dir with no exports for some reason (e.g. // visintl.dll from Office 15). if (export_dir->GetNumberOfFunctions() != 0) { WriteNewline(out); WriteNormal(out, L"WARNING! Invalid export list.", 2); WarnForCurrentFile(WarningType::kSuspicious); } } std::uint32_t num_exports = 0U; for (auto const& e : exports) { WriteNewline(out); // Some legitimate PE files have well over 10000 exports (e.g. // libgnat-4.9.dll). if (num_exports++ == 100000) { WriteNormal( out, L"WARNING! Processed 100000 exports. Stopping early to avoid resource " L"exhaustion attacks.", 2); WarnForCurrentFile(WarningType::kSuspicious); break; } if (e.ByName()) { auto const name = e.GetName(); // Sample: dllweirdexp.dll HandleLongOrUnprintableString( L"Name", L"export name", 3, WarningType::kSuspicious, name); // PE files can have duplicate exported function names (or even have them // all identical) because the import hint is used to check the name first // before performing a search. // Sample: None ("Import name hint" section of "Undocumented PECOFF" // whitepaper). if (!export_names.insert(name).second) { WriteNormal(out, L"WARNING! Detected duplicate export name.", 3); WarnForCurrentFile(WarningType::kSuspicious); } } WriteNamedHex(out, L"ProcedureNumber", e.GetProcedureNumber(), 3); WriteNamedHex(out, L"OrdinalNumber", e.GetOrdinalNumber(), 3); if (e.IsForwarded()) { WriteNamedNormal(out, L"Forwarder", e.GetForwarder().c_str(), 3); WriteNamedNormal( out, L"ForwarderModule", e.GetForwarderModule().c_str(), 3); WriteNamedNormal( out, L"ForwarderFunction", e.GetForwarderFunction().c_str(), 3); WriteNamedNormal( out, L"IsForwardedByOrdinal", e.IsForwardedByOrdinal(), 3); if (e.IsForwardedByOrdinal()) { try { WriteNamedHex(out, L"ForwarderOrdinal", e.GetForwarderOrdinal(), 3); } catch (std::exception const& /*e*/) { WriteNormal(out, L"WARNING! ForwarderOrdinal invalid.", 3); WarnForCurrentFile(WarningType::kSuspicious); } } } else { auto const ep_rva = e.GetRva(); WriteNamedHex(out, L"RVA", e.GetRva(), 3); auto const ep_va = e.GetVa(); WriteNamedHex(out, L"VA", reinterpret_cast<std::uintptr_t>(ep_va), 3); if (ep_va) { DisassembleEp(process, pe_file, ep_rva, ep_va, 4); } else if (!e.IsVirtualVa()) { WriteNormal(out, L"WARNING! Export VA is invalid.", 3); WarnForCurrentFile(WarningType::kSuspicious); } } } }
void DumpRelocations(hadesmem::Process const& process, hadesmem::PeFile const& pe_file) { if (!HasRelocationsDir(process, pe_file)) { return; } std::wostream& out = GetOutputStreamW(); WriteNewline(out); hadesmem::RelocationBlockList const reloc_blocks(process, pe_file); if (std::begin(reloc_blocks) != std::end(reloc_blocks)) { WriteNormal(out, L"Relocation Blocks:", 1); } else { // Sample: dllmaxvals.dll (Corkami PE Corpus) // Sample: fakerelocs.exe (Corkami PE Corpus) WriteNormal(out, L"WARNING! Relocation block list is invalid.", 1); WarnForCurrentFile(WarningType::kSuspicious); } for (auto const& block : reloc_blocks) { WriteNewline(out); auto const va = block.GetVirtualAddress(); WriteNamedHex(out, L"VirtualAddress", va, 2); auto const size = block.GetSizeOfBlock(); WriteNamedHex(out, L"SizeOfBlock", block.GetSizeOfBlock(), 2); WriteNewline(out); if (!size) { WriteNormal(out, L"WARNING! Detected zero sized relocation block.", 2); WarnForCurrentFile(WarningType::kUnsupported); continue; } WriteNormal(out, L"Relocations:", 2); hadesmem::RelocationList const relocs(process, pe_file, block.GetRelocationDataStart(), block.GetNumberOfRelocations()); for (auto const& reloc : relocs) { WriteNewline(out); auto const type = reloc.GetType(); WriteNamedHex(out, L"Type", type, 3); WriteNamedHex(out, L"Offset", reloc.GetOffset(), 3); // 11 = IMAGE_REL_BASED_HIGH3ADJ if (type > 11) { WriteNormal(out, L"WARNING! Unknown relocation type.", 3); WarnForCurrentFile(WarningType::kUnsupported); } } } }