7

Reading Keith Makan's, "Introduction to the ELF Format : The ELF Header", he modifies e_entry,

The e_entry field lists the offset in the file where the program should start executing.Normally it points to your _start method (of course if you compiled it with the usual stuff). You can point the e_entry anywhere you like, as an example I'm going to show that you can call a function that would other wise be impossible under normal execution.

Also documented in man 5 elf, I'm wondering if Radare has any functionality to rewrite ELF-specific headers or if writing the bits manually is the current way to do this? For example, I know it'll show the entry point with ie.

2
  • 1
    You absolutely want to do this with radare2? If not, have a look at the bfd project (used by binutils).
    – 0xC0000022L
    Nov 19, 2018 at 22:15
  • 2
    @0xC0000022L I'm not sure there is a way to do this, generally speaking I assume that Radare can't do something, and then I ask and I find out it can (a huge value of this site). I assume it can only parse ELF. If it can only parse ELF, I think a workaround is a great contribution because I'm pretty spankin new to RE, and I'm sure others are testing the waters just like me. If Radare grows the ability later, I will just come back and mark the newer answer as chosen. Nov 19, 2018 at 22:18

2 Answers 2

15

Yes, obviously you can. radare2 has built-in features to handle binary headers. This including reading, parsing and modifying the headers of the binary. And this is not different for elf or pe files, it will work great with both.

TL;DR

$ ./example.elf
[*] you ran this binary!

$ r2 -w -nn example.elf
[0x00000000]> .pf.elf_header.entry=0x0000063a
[0x00000000]> q

$ ./example.elf
[*] wow how did you manage to call this?

Creating our test file

As described in the article you linked in your question, it is easy to create a binary with a function that should never be executed under regular circumstances. Here's the exact code that was used in the linked article:

$ cat example.c

#include <stdio.h>


void never_call (void) {
    printf ("[*] wow how did you manage to call this?\n");
    return;
}

int main (int argc, char **argv) {
    printf ("[*] you ran this binary!\n");
    return 0;
}

As you can see, the function never_call would, well... never be called. The program would execute the entrypoint which would execute the main function and will return.

Now let's compile it using the command line used in the article, and execute the program:

$ gcc -Wall -o example.elf example.c
$ ./example.elf
[*] you ran this binary!

As we said, only main() was executed. Now let's open the binary in radare2 to see the magic happens.


radare2 time!

Finding the address of the function

As you requested, we want to modify the entry point of the binary by modifying the pointed address in the elf header to be our never_call function. So first, we need to find the address of never_call in the binary.

$ r2 example.elf
[0x00000530]> f~never_call
0x0000063a 19 sym.never_call

We can see that the function never_call is at address 0x0000063a. As you probably know by now, the f command is used to list the flags that was marked by radare2, this including symbols as functions names. Then, we used ~ which is r2's internal grep and grepped for the relevant function.

Parsing the ELF Header

First, we need to seek to address 0 using s 0 and then and only then we can parse the header with a new command pf. The command pf is used to print formatted data such as structures, enums, and types. Let's load the format definition for elf64 using pfo elf64 and use the pf. command to list the format definitions:

[0x00002400]> s 0        # Seek to pos 0 in the binary

[0x00000000]> pfo elf64  # Load a Format Definition File for elf

[0x00000000]> pf.
pf.elf_header [16]z[2]E[2]Exqqqxwwwwww ident (elf_type)type (elf_machine)machine version entry phoff shoff flags ehsize phentsize phnum shentsize shnum shstrndx

pf.elf_phdr [4]E[4]Eqqqqqq (elf_p_type)type (elf_p_flags)flags offset vaddr paddr filesz memsz align

pf.elf_shdr x[4]E[8]Eqqqxxqq name (elf_s_type)type (elf_s_flags_64)flags addr offset size link info addralign entsize

One of the loaded definitions is the elf_header which holds the structure for the elf64 header. We can print the header like this:

[0x00000000]> pf.elf_header
     ident : 0x00000000 = .ELF...
      type : 0x00000010 = type (enum elf_type) = 0x3 ; ET_DYN
   machine : 0x00000012 = machine (enum elf_machine) = 0x3e ; EM_AMD64
   version : 0x00000014 = 0x00000001
     entry : 0x00000018 = (qword)0x0000000000000530
     phoff : 0x00000020 = (qword)0x0000000000000040
     shoff : 0x00000028 = (qword)0x0000000000001948
     flags : 0x00000030 = 0x00000000
    ehsize : 0x00000034 = 0x0040
 phentsize : 0x00000036 = 0x0038
     phnum : 0x00000038 = 0x0009
 shentsize : 0x0000003a = 0x0040
     shnum : 0x0000003c = 0x001d
  shstrndx : 0x0000003e = 0x001c

As you can see, radare2 printed the elf64 header in a readable format so now we can see that entry, at 0x18, points to 0x530 which is our original entrypoint function. We can verify it by using ie, a radare2 command to print the entrypooint:

[0x00000000]> ie
[Entrypoints]
vaddr=0x00000530 paddr=0x00000530 baddr=0x00000000 laddr=0x00000000 haddr=0x00000018 hvaddr=0x00000018 type=program

Indeed, you can see that the entry point is 0x530 and the haddr, which is the header address, is 0x18.

Modifying the entry point

In order to modify this entry, we would need to open the file in writing mode. We can simply execute oo+ from our current session in order to re-open the file in write mode, or use the -w argument to radare2.

Then, we can simply use the pf command to write to the parsed structure the address of never_call function.

[0x00000000]> oo+
[0x00000000]> pf.elf_header.entry=0x0000063a
wv8 0x0000063a @ 0x00000018

This printed us a radare2 command to execute which will modify this address in the header. We can either execute it ourselves or use the . command to "interpret the output of the command as r2 commands".

So instead of executing wv8 ..., we will simply do:

[0x00000000]> .pf.elf_header.entry=0x0000063a

And now entry should be overridden with 0x63a which is our never_call function.

[0x00000000]> pf.elf_header
     ident : 0x00000000 = .ELF...
      type : 0x00000010 = type (enum elf_type) = 0x3 ; ET_DYN
   machine : 0x00000012 = machine (enum elf_machine) = 0x3e ; EM_AMD64
   version : 0x00000014 = 0x00000001
     entry : 0x00000018 = (qword)0x000000000000063a
     phoff : 0x00000020 = (qword)0x0000000000000040
     shoff : 0x00000028 = (qword)0x0000000000001948
     flags : 0x00000030 = 0x00000000
    ehsize : 0x00000034 = 0x0040
 phentsize : 0x00000036 = 0x0038
     phnum : 0x00000038 = 0x0009
 shentsize : 0x0000003a = 0x0040
     shnum : 0x0000003c = 0x001d
  shstrndx : 0x0000003e = 0x001c

[0x00000000]> pf.elf_header.entry
     entry : 0x00000018 = (qword)0x000000000000063a

Executing

Great! We can now exit radare and execute the program.

$ ./example.elf
[*] wow how did you manage to call this?

Last words

This long answer explained every step in the way but can really be narrowed to a simple command .pf.elf_header.entry=0x0000063a which sets the entry in the elf header to be the desired address. In the TL;DR version I demonstrated the use of -w to open the binary in write-mode and the use of -nn to load the binary structure (pfo elf64, etc...). So simply, opening radare2 like this r2 -w -nn example.elf and executing .pf.elf_header.entry=<address> would solve your problem.

Don't be afraid to ask how to do things in radare2. Although it is quite a scary framework, it is really powerful and with proper knowledge, can do much more things than seems like at first.

Read more

2
  • 1
    Wow, this is really cool. From the UI standpoint, it would be nice if the .pf.elf_header.entry=0x0000063a didn't write to $$ but to @ 0. Got it right the second time though. Nov 20, 2018 at 9:46
  • I just used this to answer another question on the site, thanks again for answering all my questions on re.se. I'll keep them coming. =) reverseengineering.stackexchange.com/a/19936/22669 Nov 20, 2018 at 22:58
2

I don't think this feature is supported according to the source code. Edit: I was wrong, sorry about that.

However, if you really need a library/tool to do that, I recommend LIEF.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.