Thumbnail: logo

Revealing software-breakpoints from memory [linux version]

by on under blog

I was about to finish the Intermediate x86 class from OpenSecTraining, when I thought was worthwhile porting to Linux this interesting exercise about software breakpoints.

Whenever we send a software breakpoint on GDB or any other debugger, the debugger swaps the first instruction byte with the double C (0xCC) instruction and keeps track of each and every breakpoint/replaced-instruction via the breakpoint table and thus, leaving the code section of the executable altered. To see the breakpoint in action, we are first going to print the target instruction without the breakpoint and then with the breakpoint enabled and see how it changes. This means that we have to find a way to save EIP (our target instruction) into another general purpose register, which normally is not a permitted operation (i.e. ‘mov EAX, EIP’).
We accomplish this with the “call 0, pop EAX” sequence.

From the below code, we are targeting the “pop EAX” instruction, which will contain its very own instruction address thanks to the previously executed ‘call 0’ instruction, which is pushing EIP on the stack: those two combined instructions are a good way to demonstrate our goal. Finally we print out the saved instruction to screen.

#include<stdio.h>

main()
  {
  unsigned char * pointer;

  fflush(stdout);                  // flush stdout buffer
  asm __volatile__ (".byte 0xe8"); // call 0 instruction
  asm __volatile__ (".byte 0x0");  
  asm __volatile__ (".byte 0x0");
  asm __volatile__ (".byte 0x0");
  asm __volatile__ (".byte 0x0");
  __asm__ ("pop %eax");            // load EIP in EAX <<< breakpoint goes here!
  __asm__ ("mov %0, %%eax" : "=r"(pointer)); // load EAX in the pointer

  printf("Here is your EAX-saved Instruction Pointer: %#x\n", *pointer);

  return 0x5eaf00d;
  }

So let’s compile it and feed it to GDB

gcc debug-aware.c -o debug-aware -fno-stack-protector -z execstack -no-pie -m32

We now want to check the normal behaviour by placing a break point at the end final main’s RET.

gdb-peda$ disass main
   0x08049192 <+0>:	lea    ecx,[esp+0x4]
   [...]
   0x080491f8 <+102>:	ret

gdb-peda$ b *0x080491f8
Breakpoint 1 at 0x80491f8

We run the program and verify that, since no breakpoint is affecting our targeted instruction, it is printing the original instruction’s opcode (0x58 - pop eax).

gdb-peda$ b *0x080491f8
gdb-peda$ run
Starting program: /root/debug-aware
Here is your EAX-saved Instruction Pointer: 0x58

We now place the breakpoint at the ‘POP EAX’ 0x080491c8 address.

gdb-peda$ disass main
Dump of assembler code for function main:
   [...]
   0x080491c3 <+49>:	call   0x80491c8 <main+54>
   0x080491c8 <+54>:	pop    eax
   [...]

gdb-peda$ b *0x080491c8
Breakpoint 2 at 0x80491c8

We now have two breakpoints: one at the target instruction and one at the last RET. Running the program once more and continuing after the first breakpoint will show the actual value set by the debugger when placing the breakpoint.

gdb-peda$ c
Continuing.
Here is your EAX-saved Instruction Pointer: 0xcc

This proves that is solely a debugger duty to keep track of the replaced instructions, while in reality, the code section manifests itself in memory as it actually is: tampered with 0xCCs.

assembly, debugging, security, ia32, infosec


© 2018 Matteo Malvica. Illustrations by Sergio Kalisiak.