objdump is a standard component of the GNU binutils. It is useful for obtaining all kinds of information from an ELF file. This page describes some of its more common reverse engineering applications
Dump an object to memory or save to file for later use. OutputVar:= ObjDump (Object, Compress, Password) Function Example: size:= ObjDump(obj, var) Parameters OutputVar. The name of the variable in which to store the size of dumped object. The object to be dumped to memory. Compress (optional). Objdump is a program for displaying various information about object files. As you will recall from our previous posts, object code is generated during the third stage of compilation, also called assembly. In order to help us parse through the information displayed using objdump we will run it with several options.
(If you prefer win32 platform, you may find tool dumpbin.exe there (shipped with visual studio) offering similar functionality)
Installation
If you have a standard C/C++ development environment set up on your Linux box, you ought to already have the GNU binutils installed. Type 'objdump' to find out. If it's not there, then you probably need to install the development toolchain for your system. This version of objdump will know how to take apart files built for your particular CPU architecture.
If you want to take apart ELF files compiled for a different architecture, you will need to compile a new copy of the binutils for a separate architecture target:
- get the official binutils distribution: http://www.gnu.org/software/binutils/
- unpack and enter binutils directory
- ./configure --target=<arch> --prefix=<directory> --program-prefix=<prefix>
- make && make install
About the configure options:
- <arch> is the architecture to build for. Examine the file bfd/config.bfd to get an idea of what targets are available. As an example of what the target should look like, the target for PowerPC processor code stored in an ELF file is powerpc-elf.
- <directory> is the base directory for the new binutils toolchain to be stored in. It helps to keep this separate from the native toolchain.
- <prefix> indicates the prefix string that should be prepended to each of the tools on installation. For example, if the program prefix is 'powerpc-' then the built objdump tool will be named powerpc-objdump.
Common Usage
objdump requires that you supply at least some parameter. Here are some of the more interesting options for RE:
To disassemble an executable ELF file:
To disassemble a shared object (.so) ELF file:
The -R option is invaluable for dealing with relocatable code. Without it, there will be a lot of calls that appear to call back to the same location, e.g.:
The actual address will be patched in by the OS when the file is loaded. However, the -R option asks objdump to insert information about the dynamic relocation:
Another useful option available for x86-targeted builds of objdump is the -Mintel option. This asks objdump to use Intel ASM syntax vs. AT&T syntax:
Note the difference in the mov instruction syntax.
To disassemble code from a static library (.a) vs. a shared library (.so) while printing relocation information, use the -r option vs. the -R option.
When dealing with code that was compiled from C++ source and still retains its symbols, those symbols will be mangled. For example:
To demangle, use the -C option (which allows for a number of demangling options, GNU convention being the default). The above example is demangled to:
The standard -d option only disassembles sections of an ELF file that are suspected to contain executable code, usually the .text sections. In order to see other sections that might contain data (e.g., .rodata sections), use the -D option to disassemble all sections, regardless of whether they have legitimate code chunks. Often, they will not and the disassembly will be bogus. But the raw data bytes can be inspected. Further, use the -z option to print long blocks of zeros which objdump would otherwise omit by default:
To put it all together, this command line disassembles all sections of a static library, demangles C++ names, patches in relocation information, shows all blocks of zeros, and prints the disassembly using Intel-standard ASM syntax:
In today’s post, I want to present a dead-simple C program that we’ll compile into an object file and then use objdump to give us some assembly code. I’ll then take you through the generated assembly.
Using objdump
According to its manpage, objdump is used to dsplay information from object files. It has a whole host of different switches that you can supply to interrogate object files, but we’ll only have a very simple usage for it in this post.
I prefer Intel assembly syntax, so I’ll specify -M intel
. We want to disassemble the object file, so we’ll use -d
. It’s really helpful to also have the original source code intermixed with the assembly code, so we’ll turn that on with -S
.
Your command should look something like this
Simple example
The most basic program to look at is one that does nothing but return 0
back to the operating system.
Compiling this unit (ensuring to specify -g
to gcc for debug symbols) and then disassembling with objdump, we’re given back the following:
Whilst the whole block that gets dumped out is important, we’re really only worried about the inner implementation of the main
function call. The translation of this code is equally pretty simple.
Dissecting this code, we can see that the program first sets up the stack frame for the two parameters passed into main, argc
and argv
.
So, we save the previous rbp
to preserve its state.
And in accordance with the calling conventions for System V AMD64
The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, and R9, while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments.
Therefore, argc
being of type int
is therefore a DWORD
and is passed via edi
. argv
is a pointer and is a QWORD
; therefore it is passed using the 64 bit register rsi
.
Upon entry, we’re just filling up those spots in the stack.
Exiting we’re just setting our return value (which is always in the accumulator), restoring the pre-entry value that was in rbp
and returning to the caller.
Objdump Command
Write another, more complex C program; disassemble it and see if you can follow along with the results.
Comments are closed.