Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
109 lines (92 sloc) 4.27 KB
layout author thumbnail comments date title categories image excerpt featured-img header tags
post
matteo_malvica
logo
true
2018-12-02 09:08:06 +0000
Revealing software-breakpoints from memory [linux version]
blog
/assets/images/5.png
https:/avanzo.github.io/assets/images/header_nero_sito_1200px256px.jpg
image overlay_image image_description overlay_filter cta_label cta_url
https:/avanzo.github.io/assets/images/2.png
https:/avanzo.github.io/assets/images/header_nero_sito_1200px256px.jpg
banner
rgba(0, 0, 0, 0.3)
@avanzo
assembly
debugging
security
ia32
infosec

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.

{: .text-justify} 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.

{: .text-justify} 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. {: .text-justify} {% highlight c hl_lines="1 3 4" %} #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; } {% endhighlight %}

So let's compile it and feed it to GDB {% highlight text hl_lines="1 3 4" %} gcc debug-aware.c -o debug-aware -fno-stack-protector -z execstack -no-pie -m32 {% endhighlight %}

We now want to check the normal behaviour by placing a break point at the end final main's RET. {% highlight text hl_lines="1 3 4" %} gdb-peda$ disass main 0x08049192 <+0>: lea ecx,[esp+0x4] [...] 0x080491f8 <+102>: ret

gdb-peda$ b *0x080491f8 Breakpoint 1 at 0x80491f8 {% endhighlight %}

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). {% highlight text hl_lines="1 3 4" %} gdb-peda$ b *0x080491f8 gdb-peda$ run Starting program: /root/debug-aware Here is your EAX-saved Instruction Pointer: 0x58 {% endhighlight %}

We now place the breakpoint at the 'POP EAX' 0x080491c8 address. {% highlight text hl_lines="1 3 4" %} 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 {% endhighlight %}

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.

{% highlight text hl_lines="1 3 4" %} gdb-peda$ c Continuing. Here is your EAX-saved Instruction Pointer: 0xcc {% endhighlight %}

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.