Post

DT_NEEDED HELP !

This blogpost will explain some protection techniques against modification of dynamic program header entries.

DT_NEEDED HELP !

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

(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:

ghidra DT_NEEDED

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 with Ghidra but 7 with readelf ?
  • 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).

you did it crazy soab

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 the PT_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:

  1. The loader will parse the Program Header Table (PHT) for PT_LOAD entries and use them to map the program in memory.
  2. The linker will also parse the program header to find the PT_DYNAMIC entry.
  3. The linker then reads the entry p_vaddr to find the virtual address of the Dynamic Table.
  4. 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 :

  1. Sets the address of the dynamic section by reading p_offset of the PT_DYNAMIC entry.
  2. If there is a .dynamic section, it replaces the address the dynamic section by the value of the sh_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. spiderman shitpost 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 that p_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]

cat dance

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.

This post is licensed under CC BY 4.0 by the author.

Trending Tags