DT_NEEDED HELP !
This blogpost will explain some protection techniques against modification of dynamic program header entries.
Introduction
Last winter, I wanted hook some Android application for a project at work. The primary objective was to bypass the anti-hooking techniques and intercept the exchanged data between a drone and its controlling application prior to a firmware analysis. Long story short, after multiple unsuccessful attempts I left this project on the side but it never really left my mind.
Fast forward 10 months later, I came accross an article that I missed from Nozomi Networks Labs.
Not only this article surprised me as they already did all the research work more than a year earlier, but they also managed to hook the packing lib and dump the dex files using the exact same techniques that I tried earlier.
This blog is a retro-analysis on how they did it and what I missed during my initial attempts.
Presentation of the Problem
Prior work
The DJI Fly (“dji.go.v5”) should not be misunderstood with the DJI Pilot application. The latter, (more specifically its packing mechanisms) has already been reverse engineered multiple times by both Synacktiv and Quarkslab. I highly recommend you to read Eric Le Guevel’s article the ART of obfuscation on which I based my research.
From what I have read, the DJI Pilot APK is protected by the SecNeo/BangCle wrapper and its protection scheme consists in splitting the classes in multiple encrypted dex files.
Dumping the dex files dynamically seemed pretty “easy”: Hook the decrypting function decrypt_jar_128K
from the libDexHelper.so
native lib and dump the memory. A less trivial approach would consist in reversing the encryption mechanisms and rebuild the dex files locally.
Note: in DJI Fly, past 1.12 ~ 1.13 the libDexHelper.so app is renamed libAppGuard.so. But it is the same thing actually.
Since both apps are packed by SecNeo, all I have to do is to reproduce theses steps. Easy ..right?
(Pic not related)
Well obviously not, otherwise this article wouldn’t exist.
- First of all, the
decrypt_jar_128K
method does not exist in DJI Fly. - Secondly, the DJI Fly unpacking lib comes with some heavily obfuscated anti-frida techniques.
From a static (and painful) analysis, I believe that the anti-frida code is triggered before the dex classes are finalized. So I spent a lot of time studying what were the anti-frida techniques and how to bypass them.
Attempts and Failures
Amongst the existing methods, I tried to inject a frida-gadget into the unpacking binary. For that, I used LIEF, more specifically their own tutorial to inject a frida gadget into a native lib.
This technique did not work and the app was no longer booting. To make sure this was not due to the injected gadget, I simply read and save the program usingn LIEF without editing the PT_DYNAMIC table (I will explain this concept further down this article) and yielded the same results.
Is displayed below the PT_DYNAMIC table of the libAppGuard.so
file before injection (Note: i trimmed the 16 bytes tags into 8 for readability):
1
2
3
4
5
6
7
8
9
10
11
12
13
➜ readelf -d libAppGuard.so
readelf: Error: no .dynamic section in the dynamic segment
Dynamic section at offset 0xefb90 contains 8 entries:
Tag Type Name/Value
0x00000001 (NEEDED) 0xcd15
0x00000001 (NEEDED) 0xcd23
0x00000001 (NEEDED) 0xcd2d
0x00000001 (NEEDED) 0xcd35
0x00000001 (NEEDED) 0xecc7
0x00000001 (NEEDED) 0xecd0
0x0000000e (SONAME) 0xed0d
0x00000000 (NULL) 0x0
And after injection:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
➜ readelf -d libAppGuard.so.out
readelf: Warning: Section 0 has an out of range sh_link value of 118202
readelf: Error: no .dynamic section in the dynamic segment
Dynamic section at offset 0xefb90 contains 33 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libapagnan.so]
0x00000001 (NEEDED) Shared library: [libandroid.so]
0x00000001 (NEEDED) Shared library: [liblog.so]
0x00000001 (NEEDED) Shared library: [libz.so]
0x00000001 (NEEDED) Shared library: [libm.so]
0x00000001 (NEEDED) Shared library: [libdl.so]
0x00000001 (NEEDED) Shared library: [libc.so]
0x0000000e (SONAME) Library soname: [libAppGuard.so]
0x00000019 (INIT_ARRAY) 0xe5240
0x0000001b (INIT_ARRAYSZ) 8 (bytes)
0x0000001a (FINI_ARRAY) 0xe5248
0x0000001c (FINI_ARRAYSZ) 16 (bytes)
0x00000004 (HASH) 0x228
0x6ffffef5 (GNU_HASH) 0x29f8
0x00000005 (STRTAB) 0xe098
0x00000006 (SYMTAB) 0x52a8
0x0000000a (STRSZ) 60705 (bytes)
0x0000000b (SYMENT) 24 (bytes)
0x00000003 (PLTGOT) 0xe8950
0x00000002 (PLTRELSZ) 14352 (bytes)
0x00000014 (PLTREL) RELA
0x00000017 (JMPREL) 0x27888
0x00000007 (RELA) 0x1d9d0
0x00000008 (RELASZ) 40632 (bytes)
0x00000009 (RELAENT) 24 (bytes)
0x0000001e (FLAGS) BIND_NOW
0x6ffffffb (FLAGS_1) Flags: NOW
0x6ffffffe (VERNEED) 0x1d990
0x6fffffff (VERNEEDNUM) 2
0x6ffffff0 (VERSYM) 0x1cdba
0x6ffffff9 (RELACOUNT) 1093
0x0000000c (INIT) 0x108098
0x00000000 (NULL) 0x0
Finally here is what you may see if you open the binary with Ghidra to check manually its contents:
Many Questions
If you pay attention to the results, you may be asking yourself:
- Why can’t
readelf
find the.dynamic
section ? - In the injected library, why are there 6
DT_NEEDED
tags withGhidra
but 7 withreadelf
? - Why in
readelf
are there integers instead of names before the injection but not after ? - Why in
Ghidra
are there incorrect lib names after the injection but not before ? (not shown in the screens) - In
readelf
after injection, where all these new dynamic entries come from ?
If you can answer all of these questions, congrats.
I eventually gave up and maybe come back later if I get a bit more skilled at RE. To be fair, progress was slow and spending more time to understand this was not the priority.
Btw I do consider myself as a rookie. There are certainly a lot of mistakes and imprecisions in this article.
Turning point
After a lot of failed attempts, I eventually set this project aside to focus on some other projects. Blantlantly aware of my incompetence, the idea was to come back at it once I’d be skilled enough to crack it. And that’s how it went until I found this blogpost from Nozomi Networks Labs. Not only they managed to hook and dump the dex files, but they did it using this exact same* method that I tried.
“We dump the decrypted .dex files by reading the raw memory layout of the application from /proc/self/maps through a code injection, exploiting DT_NEEDED entries with LIEF from QuarksLab, and inspecting it to extract the unpacked data.”
* Actually ☝️🤓, they used the same technique but did not injected any frida-agent (from what I’ve understood).
Unfortunately, their article does not provides more information on how they achieved it. But at least, I now have a proof of the feasability of this technique and that all I have to do now is to try harder™.
Anatomy of a Fail
So what did I miss ? If you want the short answer, jump straight to the conclusion. Otherwise, stay with me as I will first explain the concepts required to fully understand the answer.
The ELF File Format
ELF Structure
In Linux, at the beginning of each executable, there is an ELF ( Extensible Linking Format) file structure that starts with 7F 45 4c 46
or 0x7F ELF
.
In the ELF structure you may find information regarding the segments and sections tables.
A segment is the logical representation of a file in memory. A program header is an entry that describes a segment. You will often see these words used interchangeably.
The ELF structure is described below. I won’t go into much details as it’s already well documented online.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry; <- Entry point
Elf64_Off e_phoff; <- offset of PH
Elf64_Off e_shoff; <- offset of SH
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize; <- size of a PH entry
Elf64_Half e_phnum; <- number of PH entries
Elf64_Half e_shentsize; <- size of a SH entry
Elf64_Half e_shnum; <- number of SH entries
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
At this point, remember that this structure describes the offset
, size
and number
of entries of the segment ELF64_Phdr[]
and section Elf64_Shdr[]
tables and that these information are processed by the linker when loading the ELF file.
We will now clarify what are segments and sections.
Segments
Program headers (segments) describe how the operating system should load and map parts of an executable into memory.
<!> Segments are required to run the binary
1
2
3
4
5
6
7
8
9
10
typedef struct {
Elf64_Word p_type; <- Type of the Program Header
Elf64_Word p_flags;
Elf64_Off p_offset; <- Offset in the file
Elf64_Addr p_vaddr; <- Address in memory
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
} Elf64_Phdr;
The Dynamic Segment:
In the process header table, there should be exactly one entry of type PT_DYNAMIC
. This entry is used by the linker to find the position of the dynamic table as pointed by the p_vaddr
attribute.
Sections
Sections define the logical organization of the executable’s contents, like code, data, or symbols, for the linker and loader. Sections are useful data for static analysis or debugging.
<!> Sections are not required for runtime
As a result, a program only needs the segments to be valid in order to be executed properly. Sections can be stripped off the file or corrupted voluntarily to complexify reverse engineering.
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
The Dynamic Section:
The dynamic section is the dynamic table.
The dynamic section must be pointed by the PT_DYNAMIC
header of the process header table.
The dynamic section can be pointed by the SHT_DYNAMIC
entry of the section table.
Actually ☝️🤓,
.dynamic
is the cannonical name of the ELF section that holds the dynamic table.SHT_DYNAMIC
is the type of.dynamic
section. The same goes for thePT_DYNAMIC
which is the type of the dynamic segment.
In other words, both the dynamic section and the dynamic segment should refer the same table. But you must not trust the section table.
Is presented below the structure of an entry of the dynamic table:
1
2
3
4
5
6
7
typedef struct {
Elf64_Sxword d_tag; // type (DT_NEEDED, DT_INIT, etc.)
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
Loading and Linking Processes
When you double click on an ELF file, the program does not magically appears in memory, ready to run. The Linux kernel is responsible for answering your request and will load your process in memory and “prepare” it to run.
To understand what the Kernel launches a process, read this https://lwn.net/Articles/631631/.
We will go into the relevant ELF Structure details soon. Don’t worry if you don’t understand everything yet.
Here is a (very simplified) TLDR:
- The loader will parse the Program Header Table (PHT) for
PT_LOAD
entries and use them to map the program in memory. - The linker will also parse the program header to find the
PT_DYNAMIC
entry. - The linker then reads the entry
p_vaddr
to find the virtual address of the Dynamic Table. - The linker does more stuff we won’t talk about here
Readelf Internals
As the good IT engineer you are, you did read the man page of the readelf
program. Right ?
1
2
3
-d
--dynamic
Displays the contents of the file's dynamic section, if it has one.
Hm, that’s interesting. I thought sections should not be trusted. Why doesn’t the program searches for the DT_DYNAMIC instead ? The answer is obviously more nuanced than this.
The next chapter will walk you through how readelf
finds and parse the dynamic table.
How does readelf finds the dynamic table ?
Here is how the function process_program_headers
finds the dynamic table :
- Sets the address of the dynamic section by reading
p_offset
of the PT_DYNAMIC entry. - If there is a
.dynamic
section, it replaces the address the dynamic section by the value of thesh_offset
attribute of the SHT_DYNAMIC entry.
You may find below the source code of the “process_program_headers” of the readelf executable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static void process_program_headers(Filedata * filedata)
{
Elf_Internal_Phdr * segment;
//[some code]
for (i = 0, segment = filedata->program_headers;
i < filedata->file_header.e_phnum;
i++, segment++)
{
if (do_segments){
switch (segment->p_type)
{
//[SOME CODE]
case PT_DYNAMIC:
if (dynamic_addr){
error (_("more than one dynamic segment\n"));
/* By default, assume that the .dynamic section is the first
section in the DYNAMIC segment. */
dynamic_addr = segment->p_offset;
dynamic_size = segment->p_filesz;
/* Try to locate the .dynamic section. If there is
a section header table, we can easily locate it. */
if (filedata->section_headers != NULL)
{
Elf_Internal_Shdr * sec;
sec = find_section (filedata, ".dynamic");
if (sec == NULL || sec->sh_size == 0)
{
/* A corresponding .dynamic section is expected, but on
IA-64/OpenVMS it is OK for it to be missing. */
if (!is_ia64_vms (filedata))
error (_("no .dynamic section in the dynamic segment\n"));
break;
}
dynamic_addr = sec->sh_offset;
dynamic_size = sec->sh_size;
/* The PT_DYNAMIC segment, which is used by the run-time
loader, should exactly match the .dynamic section. */
if (do_checks
&& (dynamic_addr != segment->p_offset
|| dynamic_size != segment->p_filesz))
warn (_("the .dynamic section is not the same as the dynamic segment\n"));
}
break;
}
}
}
}
}
How does readelf finds sections ?
It’s as simple as looping through the sections and checking the name.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static Elf_Internal_Shdr *find_section (Filedata * filedata, const char * name)
{
unsigned int i;
if (filedata->section_headers == NULL)
return NULL;
// Loops through section headers
for (i = 0; i < filedata->file_header.e_shnum; i++)
// If the section name is valid
// then compare the string
if (section_name_valid (filedata, filedata->section_headers + i)
&& streq (section_name (filedata, filedata->section_headers + i),
name))
return filedata->section_headers + i;
return NULL;
}
You may also notice the reason of the no .dynamic section in the dynamic segment
error message that we had in the previous chapter. It loops through the sh_name
of each section entry. It is an offset to the string in the String Table. Since sh_name
is null in all of our section entries, readelf
cannot find it.
How does readelf finds the dynamic string table ?
You guessed it. It loops through the sections headers table for the SHT_DYNAMIC
section. It then loops through the entries to find the DT_STRTAB
.
Here are other paths you may use to find to a String table:
1
2
3
4
5
6
7
Via the sections:
- SHT_DYNAMIC.sh_addr-> DT_STRTAB.d_val <- this method is used by readelf
- SHT_STRTAB.sh_addr
- SHT_STRTAB.sh_offset
Via the program headers:
- PT_DYNAMIC.p_vaddr -> DT_STRTAB.d_val
LIEF internals
For now, forget about the injection thing. The goal here is to understand how the tool finds and parse the dynamic table and then, how it re-builds the file for saving.
ELF File parsing process
In this sub-chapter we will see how LIEF finds the Dynamic and String tables.
Luckily for us the LIEF devs have some good coding practices and the code is well documented.
1
2
3
4
5
6
7
8
9
10
11
12
template<class ELF_T>
ok_error_t Parser::parse_dyn_table(Segment& pt_dyn) {
// Parse the dynamic table. To process this table, we can either process
// the content of the PT_DYNAMIC segment or process the content of the PT_LOAD
// segment that wraps the dynamic table. The second approach should be
// preferred since it uses a more accurate representation.
// (c.f. samples `issue_dynamic_table.elf` provided by @lebr0nli)
...
const uint64_t dyn_start = pt_dyn.virtual_address();
const uint64_t dyn_end = dyn_start + pt_dyn.virtual_size();
const uint64_t load_start = segment->virtual_address();
const uint64_t load_end = load_start + segment->virtual_size();
The comment is clear: the p_vaddr
attribute of the dynamic segment is used to find the dynamic table. LIEF even goes further that this as they also compute the relative offset of the dynamic table based on the PT_LOAD segment that contains the dynamic table (source).
Note: If no wrapping PT_LOAD segment is found, the parser falls back to reading directly from the PT_DYNAMIC segment’s file offset
ELF File building process
Understanding how LIEF rebuilds ELF files was a daunting task. Instead we will learn in the next chapter how it works by comparing the results of the rebuilding of multiple files.
Solving Problems
Double Dynamic Table
Let’s compare the SHT_DYNAMIC entry with the PT_DYNAMIC entry
1
2
3
4
5
6
7
SHT_DYN:
- sh_addr = 0xEFB90
- sh_offset = 0xEFB90
PT_DYNAMIC
- p_vaddr = 0xE8720
- p_offset = 0xEFB90
Something is off. Let’s do a double hexdump in the offset of the two files. The left part of the array is the d_tag
which corresponds to the type of the entry. The right part of the array is the index in the string table and references the name of the entry.
If we open the file at 0xEFB90
we get:
1
2
3
4
5
6
7
8
9
10
11
12
➜ hexdump -C -s 0xEFB90 ./libAppGuard.so -v | head
000efb90 01 00 00 00 00 00 00 00 15 cd 00 00 00 00 00 00 |................|
000efba0 01 00 00 00 00 00 00 00 23 cd 00 00 00 00 00 00 |........#.......|
000efbb0 01 00 00 00 00 00 00 00 2d cd 00 00 00 00 00 00 |........-.......|
000efbc0 01 00 00 00 00 00 00 00 35 cd 00 00 00 00 00 00 |........5.......|
000efbd0 01 00 00 00 00 00 00 00 c7 ec 00 00 00 00 00 00 |................|
000efbe0 01 00 00 00 00 00 00 00 d0 ec 00 00 00 00 00 00 |................|
000efbf0 0e 00 00 00 00 00 00 00 0d ed 00 00 00 00 00 00 |................|
000efc00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000efc10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000efc20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
If we open the file at 0xE8720
we get:
1
2
3
4
5
6
7
8
9
10
11
12
➜ hexdump -C -s 0xE8720 ./libAppGuard.so | head
000e8720 01 00 00 00 00 00 00 00 15 cd 00 00 00 00 00 00 |................|
000e8730 01 00 00 00 00 00 00 00 23 cd 00 00 00 00 00 00 |........#.......|
000e8740 01 00 00 00 00 00 00 00 2d cd 00 00 00 00 00 00 |........-.......|
000e8750 01 00 00 00 00 00 00 00 35 cd 00 00 00 00 00 00 |........5.......|
000e8760 01 00 00 00 00 00 00 00 c7 ec 00 00 00 00 00 00 |................|
000e8770 01 00 00 00 00 00 00 00 d0 ec 00 00 00 00 00 00 |................|
000e8780 0e 00 00 00 00 00 00 00 0d ed 00 00 00 00 00 00 |................|
000e8790 19 00 00 00 00 00 00 00 40 52 0e 00 00 00 00 00 |........@R......|
000e87a0 1b 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 |................|
000e87b0 1a 00 00 00 00 00 00 00 48 52 0e 00 00 00 00 00 |........HR......|
Wait, we got two dynamic tables ! Both 7 first entries are identical, but the second one has some more entries following. So Which one should we trust ?
What do we know so far ?
- Sections should not be trusted
PT_DYNAMIC
is not used to map the file in memory and thatp_offset
should be disregarded.
There is only one value that we can trust : p_vaddr = 0x0E8720
And this is where our actual dynamic table is*.
*Once mapped in memory.
If I replace the sh_addr, sh_offset and p_offset values by 0xE8720, readelf can now correctly parse them !
1
2
3
4
5
6
7
8
9
10
11
12
13
➜ readelf -d libAppGuard_1E8720.so
readelf: Error: no .dynamic section in the dynamic segment
Dynamic section at offset 0xe8720 contains 32 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libandroid.so]
0x0000000000000001 (NEEDED) Shared library: [liblog.so]
0x0000000000000001 (NEEDED) Shared library: [libz.so]
0x0000000000000001 (NEEDED) Shared library: [libm.so]
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x000000000000000e (SONAME) Library soname: [libAppGuard.so]
[Some more stuff]
Yipee dance ! But we’re not done yet because I did some diffing analysis on the original binary and an injected one using LIEF. Here is what I found:
LIEF did find and parsed the p_vaddr
Dynamic Table but has written it over the p_offset
Dynamic Table. This has as consequences to overflow over the Section Header Table*. Moreover, the size of PT_DYNAMIC
segment was shortened from 0x230 to 0x210.
*It only overwritted over useless sections so our binary is still “runnable”.
Hooking that lib
So I injected “libaaudio.so” into the binary. This lib is globally resolved so I don’t have to add libs in the APK. Yet, the APK still does not work.
With adb logcat
we get:
1
08-26 20:51:23.986 14508 14508 E AndroidRuntime: java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/~~XPXwMp_0y7Lf8OsXWPEG8g==/dji.go.v5-23_e4w2buLG8LqG2P51N5g==/lib/arm64/libAppGuard.so" .dynamic section has invalid size: 0x230, expected to match PT_DYNAMIC filesz: 0x210
It can be quickly fixed by patching the segment size in the PHT to 0x230. I kinda expected this to happen.
The app now starts but eventually stops after a few seconds. It does run for longer than what I got using frida. So I think this should be enough time to dump the dex classes.
<!> In an attempt to get a uninterrupted execution of the APK I did notice that what the fake Dynamic Table seems to be the actually used to load libraries. I think the binary is doing some dark magic or something I don’t understand.
If I find the answer I will update this post. If you have it, send me a message on Github. @NozomiNetworks plz 🥺👉👈
At this point writing a lib that will dump the dex files is beyond my skills. I may update this post later if I manage to do it.
Answers
Why can’t readelf
find the .dynamic
section ?
Because it searches for the section name .dynamic
. In our cases, all section names are stripped.
In the injected library, why are there 6 DT_NEEDED
tags with Ghidra
but 7 with readelf
?
Because Ghidra
uses the p_vaddr
of the PT_DYNAMIC
entry to find the dynamic table whereas readelf
uses the p_offset
of the same structure. In our case, theses values are different.
Why in readelf
are there integers instead of names before the injection but not after ?
Because readelf
loops through the segments to find the dynamic table (then sections, but not in our case). It finds the p_offset
Dynamic Table which has no DT_STRTAB
entry, thus fails to find the corresponding strings.
Why in Ghidra
are there incorrect lib names after the injection but not before ?
Because Ghidra
refers the dynamic table of the p_vaddr
attribute of the PT_DYNAMIC
entry. But LIEF wrote the injected dynamic table at the position of the p_offset
one. In the process it changed the order of the entries in the string tables which led to incorrect string readings.
In readelf
after injection, where all these new dynamic entries come from ?
LIEF parsed the corred dynamic entry. Upon modification it wrote the p_vaddr
dynamic at the position of the p_offset
of the PT_DYNAMIC
entry. The two dynamic tables have a different size. The p_vaddr
one was larger and overflowed over the section header table.
Restrospective
Simlpy knowing how the ELF format works is not enough when getting confronted with some actual obfuscated binaries. A deep understanding of how ELF files are parsed by various Linux processes is also important.
Actually you may achieve the same results with lief without patching the binary prior to injection. I did miss the invalid section size issue from the adb logcat that also occurs when injecting the original binary.
The linker requires the p_offset
of the PT_DYNAMIC
to match the p_addr
of the SHT_DYNAMIC
. Hence why there are two different values in the PT_DYNAMIC
. Same thing for the size.
readelf
reads the sections table to find the dynamic table. If the .dynamic
section is not found, it assumes that it is where PT_DYNAMIC
points to.
I still don’t understand why the DT_NEEDED entries of the p_vaddr
Dynamic Table are “overriden” by the p_offset
Dynamic Table.