#include "loader/elf.h" #include "common/assert.h" #include "common/file.h" #include "common/logging/log.h" #include #include namespace Loader { static std::unique_ptr LoadSelf(Common::File& f) { if (f.Remaining() < sizeof(SelfHeader)) { return nullptr; } auto self = std::make_unique(); f.Read(self.get(), sizeof(SelfHeader)); return self; } static std::unique_ptr LoadSelfSegments(Common::File& f, uint16_t num) { auto segs = std::make_unique(num); f.Read(segs.get(), sizeof(SelfSegment) * num); return segs; } static std::unique_ptr LoadEhdr64(Common::File& f) { if (f.Remaining() < sizeof(Elf64_Ehdr)) { return nullptr; } auto ehdr = std::make_unique(); f.Read(ehdr.get(), sizeof(Elf64_Ehdr)); return ehdr; } static void SaveEhdr64(Common::File& f, const Elf64_Ehdr* ehdr) { EXIT_IF(ehdr == nullptr); uint32_t bytes_written = 0; f.Write(ehdr, sizeof(Elf64_Ehdr), &bytes_written); EXIT_IF(bytes_written == 0); } static std::unique_ptr LoadPhdr64(Common::File& f, uint64_t offset, Elf64_Half num) { auto phdr = std::make_unique(num); f.Seek(offset); f.Read(phdr.get(), sizeof(Elf64_Phdr) * num); return phdr; } static void SavePhdr64(Common::File& f, uint64_t offset, Elf64_Half num, const Elf64_Phdr* phdr) { EXIT_IF(phdr == nullptr); uint32_t bytes_written = 0; f.Seek(offset); f.Write(phdr, sizeof(Elf64_Phdr) * num, &bytes_written); EXIT_IF(bytes_written == 0); } static std::unique_ptr LoadShdr64(Common::File& f, uint64_t offset, Elf64_Half num) { if (num == 0) { return nullptr; } auto shdr = std::make_unique(num); f.Seek(offset); f.Read(shdr.get(), sizeof(Elf64_Shdr) * num); return shdr; } static void SaveShdr64(Common::File& f, uint64_t offset, Elf64_Half num, const Elf64_Shdr* shdr) { if (num == 0) { return; } EXIT_IF(shdr == nullptr); uint32_t bytes_written = 0; f.Seek(offset); f.Write(shdr, sizeof(Elf64_Shdr) * num, &bytes_written); EXIT_IF(bytes_written == 0); } static std::unique_ptr LoadDynamic64(Elf64* f, uint64_t offset, uint64_t size) { auto dynamic_data = std::make_unique(size); // f.Seek(offset); // f.Read(dynamic_data, size); f->LoadSegment(reinterpret_cast(dynamic_data.get()), offset, size); return dynamic_data; } static std::unique_ptr LoadStrTable(Common::File& f, uint64_t offset, uint32_t size) { if (size == 0) { return nullptr; } auto str_table = std::make_unique(size); f.Seek(offset); f.Read(str_table.get(), size); return str_table; } static void DbgPrintEhdr64(Elf64_Ehdr* ehdr, Common::File& f) { f.Printf("ehdr->e_ident = "); for (auto i: ehdr->e_ident) { f.Printf("%02x", i); } f.Printf("\n"); f.Printf("ehdr->e_type = 0x%04" PRIx16 "\n", ehdr->e_type); f.Printf("ehdr->e_machine = 0x%04" PRIx16 "\n", ehdr->e_machine); f.Printf("ehdr->e_version = 0x%08" PRIx32 "\n", ehdr->e_version); f.Printf("ehdr->e_entry = 0x%016" PRIx64 "\n", ehdr->e_entry); f.Printf("ehdr->e_phoff = 0x%016" PRIx64 "\n", ehdr->e_phoff); f.Printf("ehdr->e_shoff = 0x%016" PRIx64 "\n", ehdr->e_shoff); f.Printf("ehdr->e_flags = 0x%08" PRIx32 "\n", ehdr->e_flags); f.Printf("ehdr->e_ehsize = 0x%04" PRIx16 "\n", ehdr->e_ehsize); f.Printf("ehdr->e_phentsize = 0x%04" PRIx16 "\n", ehdr->e_phentsize); f.Printf("ehdr->e_phnum = %" PRIu16 "\n", ehdr->e_phnum); f.Printf("ehdr->e_shentsize = 0x%04" PRIx16 "\n", ehdr->e_shentsize); f.Printf("ehdr->e_shnum = %" PRIu16 "\n", ehdr->e_shnum); f.Printf("ehdr->e_shstrndx = %" PRIu16 "\n", ehdr->e_shstrndx); } static void DbgPrintPhdr64(Elf64_Phdr* phdr, Common::File& f) { f.Printf("phdr->p_type = 0x%08" PRIx32 "\n", phdr->p_type); f.Printf("phdr->p_flags = 0x%08" PRIx32 "\n", phdr->p_flags); f.Printf("phdr->p_offset = 0x%016" PRIx64 "\n", phdr->p_offset); f.Printf("phdr->p_vaddr = 0x%016" PRIx64 "\n", phdr->p_vaddr); f.Printf("phdr->p_paddr = 0x%016" PRIx64 "\n", phdr->p_paddr); f.Printf("phdr->p_filesz = 0x%016" PRIx64 "\n", phdr->p_filesz); f.Printf("phdr->p_memsz = 0x%016" PRIx64 "\n", phdr->p_memsz); f.Printf("phdr->p_align = 0x%016" PRIx64 "\n", phdr->p_align); } static void DbgPrintShdr64(Elf64_Shdr* shdr, Common::File& f) { f.Printf("shdr->sh_name = %d\n", shdr->sh_name); f.Printf("shdr->sh_type = 0x%08" PRIx32 "\n", shdr->sh_type); f.Printf("shdr->sh_flags = 0x%016" PRIx64 "\n", shdr->sh_flags); f.Printf("shdr->sh_addr = 0x%016" PRIx64 "\n", shdr->sh_addr); f.Printf("shdr->sh_offset = 0x%016" PRIx64 "\n", shdr->sh_offset); f.Printf("shdr->sh_size = 0x%016" PRIx64 "\n", shdr->sh_size); f.Printf("shdr->sh_link = %" PRId32 "\n", shdr->sh_link); f.Printf("shdr->sh_info = 0x%08" PRIx32 "\n", shdr->sh_info); f.Printf("shdr->sh_addralign = 0x%016" PRIx64 "\n", shdr->sh_addralign); f.Printf("shdr->sh_entsize = 0x%016" PRIx64 "\n", shdr->sh_entsize); } // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define DBG_NAME(tag) \ case tag: name = #tag; break; static void DbgPrintDynamic64(const Elf64_Dyn* dyn, Common::File& f) { const char* name = "Unknown"; switch (dyn->d_tag) { DBG_NAME(DT_OS_HASH) DBG_NAME(DT_HASH) DBG_NAME(DT_OS_STRTAB) DBG_NAME(DT_OS_STRSZ) DBG_NAME(DT_STRTAB) DBG_NAME(DT_STRSZ) DBG_NAME(DT_OS_SYMTAB) DBG_NAME(DT_SYMTAB) DBG_NAME(DT_OS_HASHSZ) DBG_NAME(DT_OS_SYMTABSZ) DBG_NAME(DT_INIT) DBG_NAME(DT_FINI) DBG_NAME(DT_OS_PLTGOT) DBG_NAME(DT_PLTGOT) DBG_NAME(DT_OS_JMPREL) DBG_NAME(DT_JMPREL) DBG_NAME(DT_OS_PLTRELSZ) DBG_NAME(DT_PLTRELSZ) DBG_NAME(DT_OS_PLTREL) DBG_NAME(DT_PLTREL) DBG_NAME(DT_OS_RELA) DBG_NAME(DT_RELA) DBG_NAME(DT_OS_RELASZ) DBG_NAME(DT_RELASZ) DBG_NAME(DT_OS_RELAENT) DBG_NAME(DT_RELAENT) DBG_NAME(DT_INIT_ARRAY) DBG_NAME(DT_INIT_ARRAYSZ) DBG_NAME(DT_FINI_ARRAY) DBG_NAME(DT_FINI_ARRAYSZ) DBG_NAME(DT_PREINIT_ARRAY) DBG_NAME(DT_PREINIT_ARRAYSZ) DBG_NAME(DT_OS_SYMENT) DBG_NAME(DT_SYMENT) DBG_NAME(DT_DEBUG) DBG_NAME(DT_TEXTREL) DBG_NAME(DT_FLAGS) DBG_NAME(DT_NEEDED) DBG_NAME(DT_OS_NEEDED_MODULE) DBG_NAME(DT_OS_NEEDED_MODULE_1) DBG_NAME(DT_OS_IMPORT_LIB) DBG_NAME(DT_OS_IMPORT_LIB_1) DBG_NAME(DT_OS_IMPORT_LIB_ATTR) DBG_NAME(DT_OS_FINGERPRINT) DBG_NAME(DT_OS_ORIGINAL_FILENAME) DBG_NAME(DT_OS_ORIGINAL_FILENAME_1) DBG_NAME(DT_OS_MODULE_INFO) DBG_NAME(DT_OS_MODULE_INFO_1) DBG_NAME(DT_OS_MODULE_ATTR) DBG_NAME(DT_SONAME) DBG_NAME(DT_OS_EXPORT_LIB) DBG_NAME(DT_OS_EXPORT_LIB_1) DBG_NAME(DT_OS_EXPORT_LIB_ATTR) DBG_NAME(DT_RELACOUNT) DBG_NAME(DT_NULL) } f.Printf("d_tag = 0x%016" PRIx64 ", d_val = 0x%016" PRIx64 ", name = %s\n", dyn->d_tag, dyn->d_un.d_val, name); } Elf64::~Elf64() { Clear(); } void Elf64::LoadSegment(uint64_t vaddr, uint64_t file_offset, uint64_t size) { EXIT_IF(m_f == nullptr); if (m_self != nullptr) { EXIT_IF(m_self_segments == nullptr); EXIT_IF(m_phdr == nullptr); for (uint16_t i = 0; i < m_self->segments_num; i++) { const auto& seg = m_self_segments[i]; if ((seg.type & 0x800u) != 0) { auto phdr_id = ((seg.type >> 20u) & 0xFFFu); const auto& phdr = m_phdr[phdr_id]; if (file_offset >= phdr.p_offset && file_offset < phdr.p_offset + phdr.p_filesz) { EXIT_NOT_IMPLEMENTED(seg.decompressed_size != phdr.p_filesz); EXIT_NOT_IMPLEMENTED(seg.compressed_size != seg.decompressed_size); auto offset = file_offset - phdr.p_offset; EXIT_NOT_IMPLEMENTED(offset + size > seg.decompressed_size); m_f->Seek(offset + seg.offset); m_f->Read(reinterpret_cast(static_cast(vaddr)), size); return; } } } if (m_f->Size() - m_self->file_size == size) { m_f->Seek(m_self->file_size); m_f->Read(reinterpret_cast(static_cast(vaddr)), size); return; } EXIT("missing self segment\n"); } else { m_f->Seek(file_offset); m_f->Read(reinterpret_cast(static_cast(vaddr)), size); } } const Elf64_Dyn* Elf64::GetDynValue(Elf64_Sxword tag) const { for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) { if (dyn->d_tag == tag) { return dyn; } } return nullptr; } std::vector Elf64::GetDynList(Elf64_Sxword tag) const { std::vector ret; for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) { if (dyn->d_tag == tag) { ret.push_back(dyn); } } return ret; } bool Elf64::IsShared() const { return (m_ehdr->e_type == ET_DYNAMIC); } bool Elf64::IsNextGen() const { return (m_ehdr->e_ident[EI_ABIVERSION] == 2); } const char* Elf64::GetSectionName(int index) const { if (m_ehdr == nullptr || m_shdr == nullptr || m_str_table == nullptr || index < 0 || index >= m_ehdr->e_shnum) { return nullptr; } const auto name_offset = m_shdr[index].sh_name; if (name_offset >= m_str_table_size) { return nullptr; } const auto remaining = m_str_table_size - name_offset; if (std::memchr(m_str_table.get() + name_offset, '\0', remaining) == nullptr) { return nullptr; } return m_str_table.get() + name_offset; } void Elf64::Clear() { if (m_f != nullptr) { m_f->Close(); } m_f.reset(); m_self.reset(); m_ehdr.reset(); m_self_segments.reset(); m_phdr.reset(); m_shdr.reset(); m_str_table.reset(); m_str_table_size = 0; m_dynamic.reset(); m_dynamic_data.reset(); } void Elf64::DbgDump(const std::string& folder) { auto folder_str = Common::FixDirectorySlash(folder); Common::File::CreateDirectories(folder_str); for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) { if (m_phdr[i].p_filesz == 0u) { continue; } char str[512]; int s = snprintf(str, 512, "phdr_%03d", i); EXIT_NOT_IMPLEMENTED(s >= 512); Common::File fout; fout.Create(folder_str + str); auto buf = std::make_unique(static_cast(m_phdr[i].p_filesz)); // m_f->Seek(m_phdr[i].p_offset); // m_f->Read(buf, static_cast(m_phdr[i].p_filesz)); LoadSegment(reinterpret_cast(buf.get()), m_phdr[i].p_offset, m_phdr[i].p_filesz); fout.Write(buf.get(), static_cast(m_phdr[i].p_filesz)); fout.Close(); } for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) { if (m_shdr[i].sh_size == 0u) { continue; } char str[512]; int s = snprintf(str, 512, "shdr_%03d", i); EXIT_NOT_IMPLEMENTED(s >= 512); Common::File fout; fout.Create(folder_str + str); auto buf = std::make_unique(static_cast(m_shdr[i].sh_size)); m_f->Seek(m_shdr[i].sh_offset); m_f->Read(buf.get(), static_cast(m_shdr[i].sh_size)); fout.Write(buf.get(), static_cast(m_shdr[i].sh_size)); fout.Close(); } Common::File fout; fout.Create(folder_str + "ehdr.txt"); DbgPrintEhdr64(m_ehdr.get(), fout); fout.Close(); fout.Create(folder_str + "phdr.txt"); for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) { fout.Printf("--- phdr [%d] ---\n", i); DbgPrintPhdr64(m_phdr.get() + i, fout); } fout.Close(); fout.Create(folder_str + "shdr.txt"); for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) { const char* section_name = GetSectionName(i); fout.Printf("--- shdr [%d] %s ---\n", i, section_name != nullptr ? section_name : ""); DbgPrintShdr64(m_shdr.get() + i, fout); } fout.Close(); fout.Create(folder_str + "dynamic.txt"); for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) { DbgPrintDynamic64(dyn, fout); } fout.Close(); } uint64_t Elf64::GetEntry() { return m_ehdr->e_entry; } bool Elf64::IsSelf() const { if (m_f == nullptr || m_f->IsInvalid()) { return false; } if (m_self == nullptr) { return false; } const bool known_magic = (m_self->ident[0] == 0x4f && m_self->ident[1] == 0x15 && m_self->ident[2] == 0x3d && m_self->ident[3] == 0x1d) || (m_self->ident[0] == 0x54 && m_self->ident[1] == 0x14 && m_self->ident[2] == 0xf5 && m_self->ident[3] == 0xee); if (!known_magic) { return false; } const bool known_ident_tail = (m_self->ident[4] == 0x00 && m_self->ident[5] == 0x01 && m_self->ident[6] == 0x01 && m_self->ident[7] == 0x12 && m_self->ident[8] == 0x01 && m_self->ident[9] == 0x01 && m_self->ident[10] == 0x00 && m_self->ident[11] == 0x00 && m_self->unknown == 0x22) || (m_self->ident[4] == 0x10 && m_self->ident[5] == 0x01 && m_self->ident[6] == 0x01 && m_self->ident[7] == 0x12 && m_self->ident[8] == 0x01 && m_self->ident[9] == 0x01 && m_self->ident[10] == 0x00 && m_self->ident[11] == 0x10 && m_self->unknown == 0x32); if (!known_ident_tail) { LOGF("Unknown SELF file\n"); return false; } return true; } bool Elf64::IsValid() const { if (m_f == nullptr || m_f->IsInvalid()) { return false; } if (m_ehdr == nullptr) { return false; } if (m_ehdr->e_ident[EI_MAG0] != '\x7f' || m_ehdr->e_ident[EI_MAG1] != 'E' || m_ehdr->e_ident[EI_MAG2] != 'L' || m_ehdr->e_ident[EI_MAG3] != 'F') { LOGF("Not an ELF file\n"); return false; } if (m_ehdr->e_ident[EI_CLASS] != ELFCLASS64) { LOGF("ehdr->e_ident[EI_CLASS] (0x%x) != ELFCLASS64\n", m_ehdr->e_ident[EI_CLASS]); return false; } if (m_ehdr->e_ident[EI_DATA] != ELFDATA2LSB) { LOGF("ehdr->e_ident[EI_DATA] (0x%x) != ELFDATA2LSB\n", m_ehdr->e_ident[EI_DATA]); return false; } if (m_ehdr->e_ident[EI_VERSION] != EV_CURRENT) { LOGF("ehdr->e_ident[EI_VERSION] != EV_CURRENT\n"); return false; } if (m_ehdr->e_ident[EI_OSABI] != ELFOSABI_FREEBSD) { LOGF("ehdr->e_ident[EI_OSABI] (0x%x) != ELFOSABI_FREEBSD\n", m_ehdr->e_ident[EI_OSABI]); return false; } if (m_ehdr->e_ident[EI_ABIVERSION] != 0 && m_ehdr->e_ident[EI_ABIVERSION] != 2) { LOGF("ehdr->e_ident[EI_ABIVERSION] (0x%x) != (0 or 2)\n", m_ehdr->e_ident[EI_ABIVERSION]); return false; } if (m_ehdr->e_type != ET_DYNEXEC && m_ehdr->e_type != ET_DYNAMIC) { LOGF("ehdr->e_type (%04x) != ET_DYNEXEC && m_ehdr->e_type != ET_DYNAMIC\n", m_ehdr->e_type); return false; } if (m_ehdr->e_machine != EM_X86_64) { LOGF("ehdr->e_machine (%04x) != EM_X86_64\n", m_ehdr->e_machine); return false; } if (m_ehdr->e_version != EV_CURRENT) { LOGF("ehdr->e_version != EV_CURRENT\n"); return false; } if (m_ehdr->e_phentsize != sizeof(Elf64_Phdr)) { LOGF("ehdr->e_phentsize != sizeof(Elf64_Phdr)\n"); return false; } if (m_ehdr->e_shentsize > 0 && m_ehdr->e_shentsize != sizeof(Elf64_Shdr)) { LOGF("ehdr->e_shentsize (%d) != sizeof(Elf64_Shdr)\n", m_ehdr->e_shentsize); return false; } return true; } void Elf64::Open(const std::filesystem::path& file_name) { Clear(); m_f = std::make_unique(); m_f->Open(file_name, Common::File::Mode::Read); if (m_f->IsInvalid()) { EXIT("Can't open %s\n", Common::PathToString(file_name).c_str()); } m_self = LoadSelf(*m_f); if (!IsSelf()) { m_self.reset(); m_f->Seek(0); } else { m_self_segments = LoadSelfSegments(*m_f, m_self->segments_num); } auto ehdr_pos = m_f->Tell(); m_ehdr = LoadEhdr64(*m_f); if (!IsValid()) { m_ehdr.reset(); } if (m_ehdr != nullptr /*&& m_self == nullptr*/) { m_phdr = LoadPhdr64(*m_f, ehdr_pos + m_ehdr->e_phoff, m_ehdr->e_phnum); if (m_self == nullptr) { m_shdr = LoadShdr64(*m_f, ehdr_pos + m_ehdr->e_shoff, m_ehdr->e_shnum); if (m_shdr != nullptr) { if (m_ehdr->e_shstrndx < m_ehdr->e_shnum) { m_str_table_size = static_cast(m_shdr[m_ehdr->e_shstrndx].sh_size); m_str_table = LoadStrTable(*m_f, m_shdr[m_ehdr->e_shstrndx].sh_offset, m_str_table_size); } } } else if (m_ehdr->e_shnum != 0) { LOGF("SELF: skipping ELF section table: shoff=0x%016" PRIx64 ", shnum=%" PRIu16 "\n", m_ehdr->e_shoff, m_ehdr->e_shnum); } for (Elf64_Half i = 0; i < m_ehdr->e_phnum; i++) { switch (m_phdr[i].p_type) { case PT_DYNAMIC: m_dynamic = LoadDynamic64(this, m_phdr[i].p_offset, m_phdr[i].p_filesz); break; case PT_OS_DYNLIBDATA: m_dynamic_data = LoadDynamic64(this, m_phdr[i].p_offset, m_phdr[i].p_filesz); break; default: break; } } } } void Elf64::Save(const std::filesystem::path& file_name) { EXIT_IF(!IsValid()); if (IsValid()) { Common::File f; f.Create(file_name); if (f.IsInvalid()) { EXIT("Can't create %s\n", Common::PathToString(file_name).c_str()); } SaveEhdr64(f, m_ehdr.get()); SavePhdr64(f, m_ehdr->e_phoff, m_ehdr->e_phnum, m_phdr.get()); SaveShdr64(f, m_ehdr->e_shoff, m_ehdr->e_shnum, m_shdr.get()); for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) { if (m_phdr[i].p_filesz == 0u) { continue; } auto buf = std::make_unique(static_cast(m_phdr[i].p_filesz)); LoadSegment(reinterpret_cast(buf.get()), m_phdr[i].p_offset, m_phdr[i].p_filesz); uint32_t bytes_written = 0; f.Seek(m_phdr[i].p_offset); f.Write(buf.get(), static_cast(m_phdr[i].p_filesz), &bytes_written); EXIT_IF(bytes_written == 0); } for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) { if (m_shdr[i].sh_size == 0u) { continue; } auto buf = std::make_unique(static_cast(m_shdr[i].sh_size)); m_f->Seek(m_shdr[i].sh_offset); m_f->Read(buf.get(), static_cast(m_shdr[i].sh_size)); uint32_t bytes_written = 0; f.Seek(m_shdr[i].sh_offset); f.Write(buf.get(), static_cast(m_shdr[i].sh_size), &bytes_written); EXIT_IF(bytes_written == 0); } f.Close(); } } } // namespace Loader