Skip to content

TheBrokenPipe/ExeOnlyDump

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ExeOnlyDump (xodump) - Linux Execute-Only Binary Dumper

Execute-only binary dumper for x86-64 (AMD64, x64) Linux. Handles both statically and dynamically linked, normal, and SUID/SGID Linux executables. Execute-only binaries are binaries that you can execute but not read, copy, or debug. They are typically used to prevent users from reproducing or reverse-engineering them.

   

History

I wrote this tool while doing a C programming university course. We had programming assignments, and the lecturers provided compiled assignment solutions as execute-only executables for us to test our implementations against. I asked one of the lecturers for permission to "steal" those demos and reverse engineer them, and I was given the go-ahead.

Nothing can read the memory of execute-only programs, but the program itself can. The demo executables were all dynamically-linked, so I exploited LD_PRELOAD to preload a library with a constructor function that dumps the mapped pages of the main executable.

I told the course staff about it, and they said they would start doing more static linking. At the same time, a debugging assignment was released and it involved an execute-only and SGID server component that students were not supposed to know about. LD_PRELOAD doesn't work on static or SUID/SGID binaries for obvious reasons, so I needed a better method.

I soon discovered that strace(1) works on execute-only and/or SUID/SGID binaries. I threw together an strace(1) knock-off to manipulate the write(2) calls to stdout to dump the entire program (instead of just a string) onto the terminal, whenever printf(3) is called. It worked and gave me access to the server backend of the debugging assignment, which allowed me to hunt down a backdoor/bug for getting free marks.

Afterwards, I extended my strace(1) knock-off to inject arbitrary syscalls into the execute-only program, which basically allowed me to get the program to dump itself to a disk file for me. I also added some very basic debugging commands like memory dump, to add a layer of flexibility. I later shared the program privately with the course staff and promised them not to release it to anyone until they found a solution to secure their binaries.

The reason I am dropping this here is that they have finally secured those binaries. Please do not use xodump for any form of misconduct. Please do not come to me crying if you get caught using xodump to steal code you are not meant to access. You have been warned.

How To Use ExeOnlyDump

Fully Automatic Binary Dump

To automatically dump a binary without any form of user interaction, run the command:

xodump -o <output-file> <input-executable>

It will execute <input-executable>, dump it and terminate it, all in one go. With this mode, you will not get the chance to actually let the executable run.

Manual Binary Dump

If you want a bit of interaction, you can simply run:

xodump <input-executable> [args]

You will first land at a question asking if you really want to continue:

Then you will be prompted to choose a mode. You can choose automatic mode, interactive mode, or quit the program:

Automatic

If you select automatic mode, you will be prompted for the name of the output file:

Then the executable will be dumped and you will be asked whether you want to continue its execution:

Interactive

If you select interactive mode, you will be asked which syscall to break at and whether you want to specify any additional conditions:

Just enter 12 for the syscall and no for conditions, and you will be shown a memory map and a command prompt:

DUMP allows you to dump a range of memory to a disk file, and VIEW allows you to view a region of memory, MAP shows the memory map, AUTO performs an automatic dump of the executable (same as automatic mode), SYSCALL performs a user-defined syscall injection, QUIT quits the program, GO continues the execution of the execute-only executable, and SYS allows you to run a system command (e.g., ls to list the directory). Here is a quick demonstration of some of the commands:

Doing It From Scratch

Arbitrary Syscall Injection

Assuming that the debuggee is stopped at a syscall entry, save all registers. Load a custom set of registers for the syscall you want to inject and execute the syscall. After the syscall completes, save the result and restore the registers from the register save you made earlier. Step back one instruction and return the syscall result you saved.

Debuggee Memory Allocation

It is not possible to use the debuggee's heap to allocate memory, so you must directly map memory by injecting mmap(2) calls. After you're done with the memory, throw it away with munmap(2).

Debugger/Debuggee Communication

Communication can be done through pipes. Create a pipe before you fork and use that pipe to talk to the debuggee. To transfer a buffer from the debugger to the debuggee, allocate it first by injecting mmap(2). After the debuggee has free memory to receive the buffer, write the buffer to the write end of the pipe from the debugger. Finally, make the debuggee read the buffer to the allocated memory from the read end of the pipe by injecting read(2) calls. Debuggee to debugger memory transfers can be done in the same fashion. It is very important to note that the debugger and debuggee cannot be running at the same time because they wait on each other, so you do not want to write too much data to the pipe and block the current program, which would cause a deadlock.

Shared memory should also work, but I have not tested it yet. It will be a pain when it comes to memory transfer from the debuggee to the debugger, as you will have to actually inject code to copy the memory to the shared pages.

Binary Dumping

Inject open(2) and read(2) to read the debuggee's /proc/self/maps file, then transfer it to the debugger. Parse it and inject write(2) calls to write the desired regions of memory to a disk file.

Limitations

  • ptrace(2) must be available. This should be the case on most systems. If not, you can use the LD_PRELOAD trick if the executable is not static or SUID/SGID.
  • Symbols cannot be dumped. I don't believe they are normally mapped into memory.
  • My code only works on x86-64 Linux. However, it shouldn't be too hard to port it to other architectures and Unix-like operating systems.

Contributors