4

So basically I am working on some tripped dynamic linked ELF binaries (32 bit Linux x86), using objdump to disassemble them, modifying and trying to reassemble them.

In the unstripped binary, we can get the beginning address of main function based on the symbol table, however, on the stripped binary, we just don't know where the main function is.

Of course I can just adjust the whole text section, and starting from the original entry point of the ELF.

But the problems are:

  1. There is some control transfer from the prologue/epilog of this ELF (such as _start; __do_global_dtors_aux; __libc_csu_fini; __i686.get_pc_thunk.bx; __do_global_ctors_aux) into the .dtors,.ctorssection, which means I have to also disassemble this section.

  2. I am afraid that if I start from entry point in the re-assembled ELF, then I would probably double-init some stuff, because in my re-assembled asm code, I have the code of _start; __do_global_dtors_aux; __libc_csu_fini while linker will also attach these functions in the new ELF.

So I would like to use some way to identify the main function in a stripped ELF (heuristically)...

Right now I don't have some strategies on this issue, Could anyone give me some help?

4 Answers 4

3

The catch is to determine whether the image in question uses a "standard" C runtime library of sorts (glibc, musl, uclibc) or not. If it does, then you can grab the entry point address and match the code at that address against your collection of startup routines from those libraries and pinpoint the main() location as you'd know which call is the one transferring control to main().

Then, the image might not be linked against any well-known C runtime, say, if it's a code piece that directly invokes kernel syscalls or if it managed to whip its own CRT library.

Another good point would be if the program wasn't written in C at all and uses some other fancy language, but that seems to be outside the scope of the question as main() won't be relevant for those, I guess.

3

I started to answer to this in « Reversing ELF 64-bit LSB executable, x86-64 ,gdb », but this was focused only to AMD64.

Indeed, the key is really to locate the __libc_start_main function and take its first argument as a pointer to the entry of the main function. The full description of this function is the following (from its manual page):

__libc_start_main

Name

__libc_start_main - initialization routine

Synopsis

int __libc_start_main(int (*main) (int, char**, char**), int argc, char** ubp_av,
                      void (*init) (void), void (*fini) (void), 
                      void (*rtld_fini) (void), void (* stack_end));

Description

The __libc_start_main() function shall perform any necessary initialization of the execution environment, call the main function with appropriate arguments, and handle the return from main(). If the main() function returns, the return value shall be passed to the exit() function.

Note: While this specification is intended to be implementation independent, process and library initialization may include:

  • Performing any necessary security checks if the effective user ID is not the same as the real user ID.
  • Initialize the threading subsystem.
  • Registering the rtld_fini to release resources when this dynamic shared object exits (or is unloaded).
  • Registering the fini handler to run at program exit.
  • Calling the initializer function (*init)().
  • Calling main() with appropriate arguments.
  • Calling exit() with the return value from main().

This list is an example only.

__libc_start_main() is not in the source standard; it is only in the binary standard.

See Also

The section on Process Initialization in each of the architecture specific parts of ISO/IEC 23360.

So, the __libc_start_main gives you not only the address of the main() procedure but also access to int argc, char** argv and char** envp.

The point will then be to extract each of these arguments from the assembly code which may vary depending on the ABI (Application Binary Interface) you are using (function arguments may be pushed on the stack or in specific registers).

3
objdump -f exe_name

exe_name:     file format elf32-little
architecture: UNKNOWN!, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00306990

The start address mentioned above is the main entry point in the executable. You can also verify this using gdb

(gdb) break *0x00306990

Normally, the start address is mapped to symbol _start, so you can also do

(gdb) break _start

If I recall correctly, _start calls __libc_start_main which in turn calls __libc_csu_init

2

So I am thinking that, in the asm code disassembled from objdump, we should always find this :

push addr
call __libc_start_main

and in a heuristical perspective, we can just consider the addr in the above code is the beginning addr of main function.

Am I right? Is there any exception?

1
  • 2
    In fact, it depends on the ABI you are using. For example, in SystemV AMD64, you will find the first argument in the register %rdi.
    – perror
    Jun 26, 2014 at 19:54

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.