Skip to content

Commit 9ca4777

Browse files
author
Rafael Stahl
committed
ExeFile: improved support for sections, relocs and 64-bit; add tests for BitManip
1 parent 907f911 commit 9ca4777

5 files changed

Lines changed: 184 additions & 4 deletions

File tree

src/hacklib/include/hacklib/ExeFile.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,28 @@
55
#include <string>
66
#include <unordered_map>
77
#include <vector>
8+
#include <span>
9+
#include <cstdint>
810

911

1012
namespace hl
1113
{
1214
// Represents an executable image in PE or EFI format.
1315
class ExeFile
1416
{
17+
public:
18+
enum class SectionType
19+
{
20+
Unknown,
21+
Code
22+
};
23+
struct Section
24+
{
25+
std::vector<uint8_t> data;
26+
std::string name;
27+
SectionType type = SectionType::Unknown;
28+
};
29+
1530
public:
1631
ExeFile();
1732
~ExeFile();
@@ -27,11 +42,14 @@ class ExeFile
2742
// Returns null if not found.
2843
uintptr_t getExport(const std::string& name) const;
2944

45+
std::span<const Section> getSections() const { return m_sections; }
46+
3047
private:
3148
std::unique_ptr<class ExeFileImpl> m_impl;
3249
bool m_valid = false;
3350
std::vector<uintptr_t> m_relocs;
3451
std::unordered_map<std::string, uintptr_t> m_exports;
52+
std::vector<Section> m_sections;
3553
};
3654
}
3755

src/hacklib/src/ExeFile_UNIX.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ using Elf_Ehdr = Elf64_Ehdr;
88
using Elf_Phdr = Elf64_Phdr;
99
using Elf_Shdr = Elf64_Shdr;
1010
using Elf_Sym = Elf64_Sym;
11+
using Elf_Rel = Elf64_Rel;
1112
#else
1213
using Elf_Ehdr = Elf32_Ehdr;
1314
using Elf_Phdr = Elf32_Phdr;
1415
using Elf_Shdr = Elf32_Shdr;
1516
using Elf_Sym = Elf32_Sym;
17+
using Elf_Rel = Elf32_Rel;
1618
#endif
1719

1820

@@ -93,9 +95,25 @@ bool hl::ExeFile::loadFromMem(uintptr_t moduleBase)
9395
for (size_t i = 0; i < numSections; i++)
9496
{
9597
auto section = &m_impl->sectionHeaders[i];
98+
auto sectionData = (uint8_t*)(moduleBase + section->sh_offset);
99+
auto sectionName = m_impl->strTable + section->sh_name;
100+
101+
Section outSection;
102+
outSection.name = sectionName;
103+
if (section->sh_type != SHT_NOBITS)
104+
{
105+
outSection.data.assign(sectionData, sectionData + section->sh_size);
106+
}
96107

97108
switch (section->sh_type)
98109
{
110+
case SHT_PROGBITS:
111+
if (outSection.name == ".text")
112+
{
113+
outSection.type = SectionType::Code;
114+
}
115+
break;
116+
99117
case SHT_SYMTAB:
100118
case SHT_DYNSYM:
101119
symTableSectionIndex = i;
@@ -112,9 +130,23 @@ bool hl::ExeFile::loadFromMem(uintptr_t moduleBase)
112130
m_exports.insert(symbols.begin(), symbols.end());
113131
symTableSectionIndex = -1;
114132
}
133+
134+
case SHT_REL:
135+
{
136+
size_t numRelocs = section->sh_size / sizeof(Elf_Rel);
137+
for (size_t r = 0; r < numRelocs; r++)
138+
{
139+
Elf_Rel* rel = ((Elf_Rel*)sectionData) + r;
140+
m_relocs.push_back(rel->r_offset);
141+
}
142+
break;
143+
}
144+
115145
default:
116146
break;
117147
}
148+
149+
m_sections.push_back(std::move(outSection));
118150
}
119151

120152
m_valid = true;

src/hacklib/src/ExeFile_WIN32.cpp

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,32 @@ static std::vector<uintptr_t> LoadRelocs(uintptr_t relocData)
4242
uintptr_t reloc = rvaRegion | relocLow;
4343
relocs.push_back(reloc);
4444
}
45+
else if (type == IMAGE_REL_BASED_DIR64)
46+
{
47+
uintptr_t reloc = rvaRegion + relocLow;
48+
relocs.push_back(reloc);
49+
}
4550
}
4651
}
4752

4853
return relocs;
4954
}
5055

56+
static uintptr_t RvaToRawDataOffset(const hl::ExeFileImpl& impl, DWORD rva)
57+
{
58+
for (auto sectionHeader : impl.sectionHeaders)
59+
{
60+
uint32_t va = sectionHeader->VirtualAddress;
61+
uint32_t size = sectionHeader->SizeOfRawData;
62+
if (rva >= va && rva < va + size)
63+
{
64+
uint32_t delta = rva - va;
65+
return sectionHeader->PointerToRawData + delta;
66+
}
67+
}
68+
return 0;
69+
}
70+
5171

5272
hl::ExeFile::ExeFile()
5373
{
@@ -75,9 +95,14 @@ bool hl::ExeFile::loadFromMem(uintptr_t moduleBase)
7595
return false;
7696
}
7797

78-
// Only support 32-bit images for now.
98+
// Check if image type matches.
99+
#ifdef ARCH_64BIT
100+
if (m_impl->peHeader->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 ||
101+
(m_impl->peHeader->FileHeader.Characteristics & IMAGE_FILE_32BIT_MACHINE))
102+
#else
79103
if (m_impl->peHeader->FileHeader.Machine != IMAGE_FILE_MACHINE_I386 ||
80104
!(m_impl->peHeader->FileHeader.Characteristics & IMAGE_FILE_32BIT_MACHINE))
105+
#endif
81106
{
82107
return false;
83108
}
@@ -89,13 +114,37 @@ bool hl::ExeFile::loadFromMem(uintptr_t moduleBase)
89114
auto sectionHeader = (IMAGE_SECTION_HEADER*)((uintptr_t)m_impl->peHeader + sizeof(IMAGE_NT_HEADERS) +
90115
i * sizeof(IMAGE_SECTION_HEADER));
91116
m_impl->sectionHeaders.push_back(sectionHeader);
117+
118+
Section outSection;
119+
for (int i = 0; i < IMAGE_SIZEOF_SHORT_NAME; i++)
120+
{
121+
if (sectionHeader->Name[i] == 0)
122+
{
123+
break;
124+
}
125+
outSection.name.push_back((char)sectionHeader->Name[i]);
126+
}
127+
if (sectionHeader->SizeOfRawData > 0)
128+
{
129+
auto sectionData = (const uint8_t*)(moduleBase + sectionHeader->PointerToRawData);
130+
outSection.data.assign(sectionData, sectionData + sectionHeader->SizeOfRawData);
131+
}
132+
if (sectionHeader->Characteristics & IMAGE_SCN_CNT_CODE)
133+
{
134+
outSection.type = SectionType::Code;
135+
}
136+
m_sections.push_back(std::move(outSection));
92137
}
93138

94139
// Load relocs.
95140
auto relocDir = &m_impl->peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
96141
if (relocDir->VirtualAddress != 0)
97142
{
98-
m_relocs = LoadRelocs(moduleBase + relocDir->VirtualAddress);
143+
uintptr_t relocsOffset = RvaToRawDataOffset(*m_impl, relocDir->VirtualAddress);
144+
if (relocsOffset)
145+
{
146+
m_relocs = LoadRelocs(moduleBase + relocsOffset);
147+
}
99148
}
100149

101150
m_valid = true;

src/hacklib/src/Process_WIN32.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ hl::Process hl::LaunchProcess(const std::string& command, const std::vector<std:
4040

4141
STARTUPINFOA startupInfo = {};
4242
startupInfo.cb = sizeof(startupInfo);
43-
PROCESS_INFORMATION processInfo;
43+
PROCESS_INFORMATION processInfo{};
4444
BOOL result = CreateProcessA(NULL, const_cast<char*>(cmdline.c_str()), NULL, NULL, false, 0, NULL,
4545
initialDirectoryCStr, &startupInfo, &processInfo);
4646

src/test/test.cpp

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
#include "hacklib/Patch.h"
1010
#include "hacklib/PatternScanner.h"
1111
#include "hacklib/Process.h"
12+
#include "hacklib/BitManip.h"
1213
#include <chrono>
1314
#include <cstdio>
1415
#include <functional>
1516
#include <iostream>
1617
#include <stdexcept>
1718
#include <thread>
19+
#include <algorithm>
1820

1921

2022
#define HL_ASSERT(cond, format, ...) \
@@ -144,6 +146,59 @@ static void ExpectException(F func)
144146
}
145147

146148

149+
#define ASSERT_EQ(a, b) HL_ASSERT((a) == (b), #a " == " #b)
150+
static void TestBitManip()
151+
{
152+
ASSERT_EQ(hl::HasOverlap(0, 1, 2, 3), false);
153+
ASSERT_EQ(hl::HasOverlap(2, 3, 0, 1), false);
154+
ASSERT_EQ(hl::HasOverlap(0, 1, 1, 2), false);
155+
ASSERT_EQ(hl::HasOverlap(1, 2, 0, 1), false);
156+
ASSERT_EQ(hl::HasOverlap(0, 1, 0, 1), true);
157+
ASSERT_EQ(hl::HasOverlap(0, 1, 0, 3), true);
158+
ASSERT_EQ(hl::HasOverlap(5, 7, 6, 8), true);
159+
ASSERT_EQ(hl::HasOverlap(6, 8, 5, 7), true);
160+
161+
ASSERT_EQ(hl::HasOverlapInclusive(0, 1, 2, 3), false);
162+
ASSERT_EQ(hl::HasOverlapInclusive(2, 3, 0, 1), false);
163+
ASSERT_EQ(hl::HasOverlapInclusive(0, 1, 1, 2), true);
164+
ASSERT_EQ(hl::HasOverlapInclusive(1, 2, 0, 1), true);
165+
ASSERT_EQ(hl::HasOverlapInclusive(0, 1, 0, 1), true);
166+
ASSERT_EQ(hl::HasOverlapInclusive(0, 1, 0, 3), true);
167+
ASSERT_EQ(hl::HasOverlapInclusive(5, 7, 6, 8), true);
168+
ASSERT_EQ(hl::HasOverlapInclusive(6, 8, 5, 7), true);
169+
170+
ASSERT_EQ(hl::Align(0x0, 0x10), 0x0);
171+
ASSERT_EQ(hl::Align(0x5, 0x10), 0x10);
172+
ASSERT_EQ(hl::Align(0x15, 0x10), 0x20);
173+
ASSERT_EQ(hl::Align(0x25, 0x10), 0x30);
174+
ASSERT_EQ(hl::Align(0x10, 0x10), 0x10);
175+
ASSERT_EQ(hl::Align(0x11, 0x10), 0x20);
176+
ASSERT_EQ(hl::Align(0x1f, 0x10), 0x20);
177+
ASSERT_EQ(hl::Align(0xffffffff, 0x10u), 0x0);
178+
179+
ASSERT_EQ(hl::AlignDown(0x0, 0x10), 0x0);
180+
ASSERT_EQ(hl::AlignDown(0x5, 0x10), 0x0);
181+
ASSERT_EQ(hl::AlignDown(0x15, 0x10), 0x10);
182+
ASSERT_EQ(hl::AlignDown(0x25, 0x10), 0x20);
183+
ASSERT_EQ(hl::AlignDown(0x10, 0x10), 0x10);
184+
ASSERT_EQ(hl::AlignDown(0x11, 0x10), 0x10);
185+
ASSERT_EQ(hl::AlignDown(0x1f, 0x10), 0x10);
186+
ASSERT_EQ(hl::AlignDown(0xffffffff, 0x10u), 0xfffffff0);
187+
188+
ASSERT_EQ(hl::MakeMask<uint8_t>(0, 0), 0x0);
189+
ASSERT_EQ(hl::MakeMask<uint8_t>(0, 1), 0x1);
190+
ASSERT_EQ(hl::MakeMask<uint8_t>(0, 2), 0x3);
191+
ASSERT_EQ(hl::MakeMask<uint8_t>(0, 4), 0xf);
192+
ASSERT_EQ(hl::MakeMask<uint8_t>(0, 8), 0xff);
193+
ASSERT_EQ(hl::MakeMask<uint16_t>(0, 16), 0xffff);
194+
195+
ASSERT_EQ(hl::MakeMask<uint8_t>(1, 0), 0x0);
196+
ASSERT_EQ(hl::MakeMask<uint8_t>(1, 1), 0x2);
197+
ASSERT_EQ(hl::MakeMask<uint8_t>(1, 7), 0xfe);
198+
199+
ASSERT_EQ(hl::MakeMask<uint64_t>(0, 64), 0xffffffffffffffffull);
200+
}
201+
147202
static void TestMemory()
148203
{
149204
void* mem = g_dummyCode.data();
@@ -242,7 +297,12 @@ static void TestInject()
242297
libName = "lib" + libName + ".so";
243298
#endif
244299

245-
auto process = hl::LaunchProcess("./hl_test", { "--child" });
300+
#ifdef WIN32
301+
std::string procName = "hl_test";
302+
#else
303+
std::string procName = "./hl_test";
304+
#endif
305+
auto process = hl::LaunchProcess(procName, { "--child" });
246306

247307
// Give the child time to set everything up.
248308
std::this_thread::sleep_for(std::chrono::milliseconds(200));
@@ -437,6 +497,25 @@ static void TestHooks()
437497
HL_ASSERT(cbCounter == 0, "Hook not undone");
438498
}
439499

500+
static void TestExeFile()
501+
{
502+
#ifdef WIN32
503+
std::string fileName = "hl_test.exe";
504+
#else
505+
std::string fileName = "hl_test";
506+
#endif
507+
hl::ExeFile exeFile;
508+
HL_ASSERT(exeFile.loadFromFile(fileName), "ExeFile::loadFromFile failed");
509+
auto relocs = exeFile.hasRelocs();
510+
HL_ASSERT(relocs, "ExeFile::getRelocations returned non-empty list");
511+
auto sections = exeFile.getSections();
512+
HL_ASSERT(sections.size() > 1, "ExeFile::getSections returned wrong number of sections");
513+
auto numCodeSections =
514+
std::count_if(sections.begin(), sections.end(), [](const hl::ExeFile::Section& section)
515+
{ return section.type == hl::ExeFile::SectionType::Code && section.name == ".text"; });
516+
HL_ASSERT(numCodeSections > 0, "Must find at least one code section");
517+
}
518+
440519
#ifdef WIN32
441520
static int vehCounter1 = 0;
442521
static int vehCounter2 = 0;
@@ -509,12 +588,14 @@ class TestMain : public hl::Main
509588
SetupLogging();
510589
TestCrashHandler();
511590

591+
HL_TEST(TestBitManip);
512592
HL_TEST(TestMemory);
513593
HL_TEST(TestInject);
514594
HL_TEST(TestModules);
515595
HL_TEST(TestPatch);
516596
HL_TEST(TestPatternScan);
517597
HL_TEST(TestHooks);
598+
HL_TEST(TestExeFile);
518599
HL_TEST(TestVEH);
519600

520601
HL_LOG_RAW("==========\nTests finished successfully.\n");

0 commit comments

Comments
 (0)