First, regarding the address of main:
It seems one would have to use the Section Headers to find out the address of main. Doing it just using the dynamic section seems not possible. Running readelf -D -s
and readelf -D --dyn-sym
does not give the address of main either.
Now, regarding finding the address of dlopen
. It turns out I was reading the wrong number of symbolic table entries from the hash tables. There are two types of hash tables (I have encountered so far): DT_HASH
tables, and DT_GNU_HASH
tables. The former have the amount of entries at hash_table_addr + 4
(source), the latter do not specify the amount of hash tables explicitely. One needs to obtain this amount by iterating through the hash table's bucket table. Other than that, my approach was good, and now I am able to find the address of dlopen
, malloc
, etc.
To obtain the number of entries (i.e. size of) a Symbol Table from a Hash Table, one can use (C):
ssize_t ReadData(int pid, void* buffer, const void* source, ssize_t size)
{
// Under Ubuntu and other distros with a 'hardened kernel', processes using this function
// should be run as root.
// See https://wiki.ubuntu.com/SecurityTeam/Roadmap/KernelHardening#ptrace_Protection
iovec local_vec;
local_vec.iov_base = Buffer;
local_vec.iov_len = Size;
iovec remote_vec;
remote_vec.iov_base = Address;
remote_vec.iov_len = Size;
return process_vm_readv(pid, &local_vec, 1, &remote_vec, 1, 0);
}
unsigned long FindNumEntriesHashTable(int pid, void* TablePtr, const void* TableLibAddr)
{
// Check if TablePtr is smaller than 0.
unsigned long pointer = ((long)TablePtr < 0) ? (unsigned long)TablePtr + (unsigned long)TableLibAddr : (unsigned long)TablePtr;
unsigned long ret = 0;
ReadData(pid, &ret, (void*)(pointer + sizeof(Elf_Word)), sizeof(Elf_Word));
return ret;
}
unsigned long FindNumEntriesGnuHashTable(int pid, void *TablePtr, const remote_voidptr TableLibAddr)
{
unsigned long pointer = ((long)TablePtr < 0) ? (unsigned long)TablePtr + (unsigned long)TableLibAddr : (unsigned long)TablePtr;
// Read in required info on the gnu_hash table
unsigned long nbuckets = 0;
unsigned long symndx = 0;
unsigned long maskwords = 0;
ReadData(pid, &nbuckets, (const remote_voidptr)pointer, sizeof(Elf_Word));
ReadData(pid, &symndx, (const remote_voidptr)(pointer + sizeof(Elf_Word)), sizeof(Elf_Word));
ReadData(pid, &maskwords, (const remote_voidptr)(pointer + 2 * sizeof(Elf_Word)), sizeof(Elf_Word));
// Calculate the offset to the bucket table. The size of the maskwords entries is 4 under 32 bit, 8 under 64 bit.
unsigned long masktab_size = (ENV_NUMBITS == 32) ? 4 * maskwords : 8 * maskwords;
unsigned long buckettab_offs = 4 * sizeof(Elf_Word) + masktab_size;
// Read in the bucket table
Elf_Word buckettab[nbuckets];
ReadData(pid, &buckettab, (const remote_voidptr)(pointer + buckettab_offs), nbuckets * sizeof(Elf_Word));
// Loop through the bucket table. If the given index is larger than the already known index, update.
unsigned long num_entries = 0;
for (size_t i = 0; i < nbuckets; i++)
{
if (num_entries == 0 || buckettab[i] > num_entries)
{
num_entries = buckettab[i];
}
}
if (num_entries == 0)
{
return 0;
}
// Add one, since the first entry is always NULL.
return num_entries++;
}