So, as soon as the gllin binary was released for download, I came up with an evil plan – will for sure blog about it after it is executed. But first (as part of usual preparation for an evil plan) I needed to find out whether in a normal program under Linux the heap is executable, or rather what section is executable and writable. While attempting this I made a funny and completely unuseful observation which I’m going to share with you now. Here’s the test program:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void sayhello(int world_number) {
int local;
static int stat;
printf("Hello World %i! Local variable at %p and static at %p\n",
world_number, &local, &stat);
}
int main(int argc, char *argv[], char **envp) {
void (*say[2])(int i) = { sayhello, malloc(0x1000) };
memcpy(say[1], say[0], 0x50);
say[0](0);
say[1](1);
return 0;
}
After “make hello” I have the ELF under ./hello and load it into gdb and inspect:
$ gdb ./hello GNU gdb 6.6 ... (gdb) break sayhello Breakpoint 1 at 0x40068f: file hello.c, line 9. (gdb) run ... Breakpoint 1, sayhello (world_number=0) at hello.c:9 (gdb) up #1 ... in main (argc=1, argv=..., envp=...) at hello.c:17 (gdb) disassemble say[0] (say[0] + 15) Dump of assembler code from 0x400684 to 0x400693: 0x0000000000400684 <sayhello+0>: push %rbp 0x0000000000400685 <sayhello+1>: mov %rsp,%rbp 0x0000000000400688 <sayhello+4>: sub $0x20,%rsp 0x000000000040068c <sayhello+8>: mov %edi,0xffffffffffffffec(%rbp) 0x000000000040068f <sayhello+11>: lea 0xfffffffffffffffc(%rbp),%rdx End of assembler dump. (gdb) disassemble say[1] (say[1] + 15) Dump of assembler code from 0x602010 to 0x60201f: 0x0000000000602010: push %rbp 0x0000000000602011: mov %rsp,%rbp 0x0000000000602014: sub $0x20,%rsp 0x0000000000602018: mov %edi,0xffffffffffffffec(%rbp) 0x000000000060201b: int3 0x000000000060201c: lea 0xfffffffffffffffc(%rbp),%edx End of assembler dump.
You may now ask yourself the same question that I asked myself: WTF? or I may first explain what is happening above and you may ask the question then. We loaded the program into the debugger. The program was supposed to greet the world once and then call a copy of sayhello we made with memcpy(). We set a breakpoint at the start of the function and run the program. When it enters sayhello, it hits the break and we have a chance to look at the copy of the function. We step out of the sayhello frame so that we can access the say array. We disassemble the start of the original function and the start of its copy, and we see that they differ (!). Someone is messing in MY functions?! Or memcpy() is perhaps broken?!
No, it’s just gdb. When we set a breakpoint at sayhello it inserted the extra instruction (which I would have maybe recognised if I used x86 asm more often) to get notified in the right moment. We copied the function together with the breakpoint and we hit the original breakpoint. gdb then hid it from out eyes (first disassembly) but it didn’t know that we had secretly made a copy (second disassembly) and we now have a pretty little breakpoint of our own.
So what useful did we learn? Nothing really. That checksumming the program in runtime may sometimes work.
Good news is that memcpy() is fine and the world is safe. Pheww..