Fix ptrace getting wrong entry point address for some ELFs. Closes #2022

* The entry point is remapped several times:
  - ELF header declares it at RVA 0x1234
  - [new in this change] The section containing that says it's at addresses
    0x1200 - 0x1500, but on disk it's actually at file offset 0x1100 meaning our
    entry point is actually 0x100 earlier, at 0x1134
  - The ASLR maps the executable section at *file offset* 0x1100 to 0xDEADBEEF00
    Importantly when file offset != base RVA, this needs to be taken into
    account.
  - Finally the entry point is at 0x34 offset into the section because it's
    mmapped, so the entry point is 0xDEADBEEF34.
This commit is contained in:
baldurk
2020-08-27 17:36:08 +01:00
parent 4c8067b65c
commit 8c4bb6610f
+48 -9
View File
@@ -296,7 +296,7 @@ bool StopChildAtMain(pid_t childPid)
}
rdcstr exepath;
long basePointer = 0;
long baseVirtualPointer = 0;
uint32_t sectionOffset = 0;
rdcstr mapsName = StringFormat::Fmt("/proc/%u/maps", childPid);
@@ -318,8 +318,8 @@ bool StopChildAtMain(pid_t childPid)
{
RDCCOMPILE_ASSERT(sizeof(long) == sizeof(void *), "Expected long to be pointer sized");
int pathOffset = 0;
int num = sscanf(line, "%lx-%*x r-xp %x %*x:%*x %*u %n", &basePointer, &sectionOffset,
&pathOffset);
int num = sscanf(line, "%lx-%*x r-xp %x %*x:%*x %*u %n", &baseVirtualPointer,
&sectionOffset, &pathOffset);
if(num != 2 || pathOffset == 0)
{
@@ -334,7 +334,7 @@ bool StopChildAtMain(pid_t childPid)
}
}
if(basePointer == 0)
if(baseVirtualPointer == 0)
{
RDCERR("Couldn't find executable mapping in maps file");
return false;
@@ -352,20 +352,59 @@ bool StopChildAtMain(pid_t childPid)
Elf64_Ehdr elf_header;
size_t read = FileIO::fread(&elf_header, sizeof(elf_header), 1, elf);
FileIO::fclose(elf);
if(read != 1)
{
FileIO::fclose(elf);
RDCERR("Couldn't read ELF header from %s", exepath.c_str());
return false;
}
void *entry = (void *)(basePointer + elf_header.e_entry - sectionOffset);
size_t entryVirtual = (size_t)elf_header.e_entry;
// if the section doesn't shift between file offset and virtual address this will be the same
size_t entryFileOffset = entryVirtual;
long origEntryWord = ptrace(PTRACE_PEEKTEXT, childPid, entry, 0);
if(elf_header.e_shoff)
{
long breakpointWord = (origEntryWord & 0xffffff00) | 0xcc;
ptraceRet = ptrace(PTRACE_POKETEXT, childPid, entry, breakpointWord);
FileIO::fseek64(elf, elf_header.e_shoff, SEEK_SET);
RDCASSERTEQUAL(elf_header.e_shentsize, sizeof(Elf64_Shdr));
for(Elf64_Half s = 0; s < elf_header.e_shnum; s++)
{
Elf64_Shdr section_header;
size_t read = FileIO::fread(&section_header, sizeof(section_header), 1, elf);
if(read != 1)
{
FileIO::fclose(elf);
RDCERR("Couldn't read section header from %s", exepath.c_str());
return false;
}
if(section_header.sh_addr <= entryVirtual &&
entryVirtual < section_header.sh_addr + section_header.sh_size)
{
entryFileOffset =
(entryVirtual - (size_t)section_header.sh_addr) + (size_t)section_header.sh_offset;
break;
}
}
}
FileIO::fclose(elf);
void *entry = (void *)(baseVirtualPointer + entryFileOffset - sectionOffset);
// this reads a 'word' and returns as long, upcast (if needed) to uint64_t
uint64_t origEntryWord = (uint64_t)ptrace(PTRACE_PEEKTEXT, childPid, entry, 0);
uint64_t breakpointWord = (origEntryWord & 0xffffffffffffff00ULL) | 0xccULL;
// downcast back to long, if that means truncating
ptraceRet = ptrace(PTRACE_POKETEXT, childPid, entry, (long)breakpointWord);
RDCASSERTEQUAL(ptraceRet, 0);
// continue