Our Blog

Our news

All you need to know

Linux Heap Exploitation Intro Series – (BONUS) printf might be leaking!

Reading time ~11 min

Intro

Hi there (again)! This series are going to an end as the next and feasible step is the widely known buffer overflow and its analysis in the heap and, I am not too convinced about it since the unsafe unlink method is long gone. But don’t be sad, today we are going for a bonus one! During the last post (double free attacks) one I stumbled across some weird behaviour that caught my attention by functions of the vfprintf.c family (for example printf or puts functions).

We will keep the level from last post and still use gdb but with the addition of pwndbg. This should be a short one, sorry if I dwell into too many details and fail to do so.

One last thing, this is by far, nothing that someone would be able to reproduce as the chances for this to happen on a real world scenario are close to zero, none, niks, nada. Other than that, I thought is a cool way to understand how we could be able to abuse other functions that also use the heap in such ways.

The vulnerability

Preface

I had a chat with a couple of people I look up to and admire for their skills and they provided very good points. One point in which they coincide is that the vulnerability here is the double free and that, by definition, the family of functions that use vfprintf.c is unsafe.

I am still undecided if the technique we are going to describe here is actually a vulnerability because, depending on the libc that our system is running, the code behaves very different. The “exploitable” case happens on libc 2.23 and libc 2.24. All other libc implementations are safe from this case. Also I haven’t reported this behaviour to the glibc maintainers and still doubting if doing so because the ways of linking different libc‘s to my code.

Then how do I know that it reliably works on libc 2.23 and libc 2.24 you might ask? Because I bugged several people and laptops :)

Last but not least everything in this blog post is 64bit oriented.

What

The weird thing I encountered while researching this, is that commonly such functions like printf, used to use the .rodata section to store the string and then send it to the stdout stream but, for what I’ve seen, on libc 2.23 and 2.24 that is not the case and it uses the heap by allocating 1032+8 bytes and does not free the memory on exit. The cause of this might be performance but, in any case, there is a quite similar bug pointing to the usage of “printf” family and its memory usage.

For further reference and in our case, keep in mind that vfprintf.c contains code similar to the following pseudo-code:

...
int vfprintf(args)
{
 int string_malloced = 0;
 if (string_malloced == 0)
 {
  mem_for_str = malloc(0x410-8);
 }
 stream_from_memory(stdout, mem_for_str, args);
 return bytes_written;
}
...

Basically, if there is no string malloc’d it will allocate a new string mem_for_str of actual size in memory 1040 bytes (0x410) but, usable size of 1040-8 bytes (0x410-8). Again, remember that the 8 bytes are for the chunk size header. Then it will use that allocated memory to format the “format string” in args, copy it into mem_for_str and then feed it into the stdout stream. If it is already malloc’d it will just reuse the mem_for_str chunk.

As you can see there is no free(mem_for_str) before returning the written bytes (return bytes_written). This will leave a chunk allocated that is up to abuse with our double-free primitive. Mwahaha!!

When stars leave a trail in your memory

The scenario to happen here is quite the same as the one we described for the double-free but, in our case, the responsible of doing a malloc that is to be abused later, are the functions printf or puts.

...
char *A, *B, *C;
A = malloc(0x410-8); // (1)
free(A);
printf("BBBB"); // (2)
free(A); // Double free
B = malloc(0x200-8);
C = malloc(0x200-8);
printf("X"*0x208); // Not real C code (4)
...

I think it’s a good time to explain this through our old friends the ascii heap drawings =)

We start with the allocation at (1), nothing fancy about it.

 +---HEAP GROWS UPWARDS
 |                 
 | +-+-+-+-+-+-+
 | |  CHUNK A  | <-- Chunk A, size (0x410)
 | |           |
 | |           |
 | +-----------+ <-- A ends here.
 | |    TOP    |
 | |           |
 V |           |
   +-----------+

Then we free it so the TOP chunk takes that space and then we do a printf("BBBB").

 +---HEAP GROWS UPWARDS
 |                 
 | +-+-+-+-+-+-+
 | |  PRINTF   | <-- Old Chunk A position.
 | | 4242424242| <-- Contents of printf
 | |           |
 | +-----------+ <-- printf chunk ends here
 | |    TOP    |
 | |           |
 V |           |
   +-----------+

Now if a double-free happens on old chunk A we are going to effectively have a free() on the chunk that printf has allocated. At this point if we keep using the printf() function, it is going to write to the TOP chunk’s contents because it thinks that its chunk is still malloc’d (string_malloced == 1). But what would happen if we allocate two chunks in the free() space of the old chunk A which is, in turn, used by the printf function?

 +---HEAP GROWS UPWARDS
 |                 
 | +-+-+-+-+-+-+
 | |  CHUNK B  | <-- Old printf chunk position.
 | +-----------+ 
 | |  CHUNK C  |
 | +-----------+ <-- Old printf chunk ends here
 | |    TOP    |
 | |           |
 V |           |
   +-----------+

Hopefully you can clearly see the havoc that a long printf could make here as it will overwrite chunk’s C metadata (size headers and specific bits). In the pseudo-code snippet at (4) we write into chunk B all the way into the headers of chunk C by writting 0x200 (512) times the letter ‘X’.

The playground

Let’s get our brains messed with: we know we can overwrite chunks through printf/puts if we have a double-free primitive, so our goal is going to be to call shellcode allocated in a chunk we control. This time we are going to show just two proof of concepts: One to show the behaviour in the debugger and the second one modifying last blog post’s jackpot code execution but, through this technique, and then jumping to a shellcode previously allocated in the stack. Let’s go!

Download Playground Code

The concept

We are going to use the file printf_double_free.c, which represents the When stars leave a trail in your memory section. Now, onto gdb+pwndbg as the code for this proof of concept has been already explained previously:

The exploitation

The grand finale is here! Get the file printf_fastbins_malloc_hook.c and go for code execution through the use of printf but, before any crazy debugging sessions we are going to explain some caveats.

NX

Most likely, if you are here reading this you already know about NX (or its Windows brother DEP), if you don’t, no problem: Non-eXecutable is a bit that, if set, it will protect from the code execution to jump into the memory parts of the heap or stack – we can’t have a shellcode in the heap or the stack and execute it if this bit is set.

This is why, when compiling this proof of concept we need the “-z execstack” flag (this is me being lazy and not wanting to craft a ROP chain). If you would be to exploit something through a technique similar to this you would have to either bypass NX through other ways or disable it through a ROP chain.

The shellcode

Now that we have our memory set as executable, I decided to set up a shellcode with the following command and a bit of help from the metasploit framework and python (you can copy paste from the video):

Let me explain what happened there: I generated the shellcode in raw format and straight fed it into the xxd command that outputs the bytes from the shellcode in hexadecimal byte by byte. Then I removed the trailing newline characters with tr -d '\n' to finally feed the hexadecimal formatted shellcode into python which will give me an escaped string version of our payload to inject it into our C code.

...
memcpy(sc, "j;X\x99H\xbb/bin/sh\x00SH\x89\xe7h-c\x00\x00H\x89\xe6R\xe8\x08\x00\x00\x00/bin/sh\x00VWH\x89\xe6\x0f\x05", 47);
...

Rememberance

If you have skimmed through the code already, you will see that it is almost the same code as the double-free PoC which jumps to the jackpot function. The difference is the following:

The following is the code that used double free and memcpy to write our rogue FD.

...
puts("\n[+] free p5");
free(p5);

puts("\n[+] allocate p6 with size 0x60");
char *p6 = malloc(0x60); // Takes the old address of p5
 
puts("\n[+] Double free p5");
free(p5); // double free p5

void *offset_byte_7f = (void *)malloc_hook-0x20-3;
memcpy(p6, &offset_byte_7f, 0x8);
...

Current PoC code that uses “printf” to write our rogue FD:

...
puts_heapless("\n[+] allocate p5 with size 0x60");
char *p5 = malloc(0x410-8); // Size of chunk for printf

puts_heapless("\n[+] free p5");
free(p5);
puts("This puts/printf will mess with p6 and p7"); // Takes the old address of p5 (1)
puts_heapless("\n[+] Double free p5");
free(p5); // double free p5

puts_heapless("\n[+] allocate p6 with size 0x60");
char *p6 = malloc(0x60); 
free(p6);

long int *offset_byte_7f = (long int *)malloc_hook-0x20-3;
offset_byte_7f = (void*)offset_byte_7f+0xf5;
printf(&offset_byte_7f); // (2)
...

The differences are the following:

– Usage of a helper function that doesn’t use the heap to print to stdout.
– The "puts" at (1) will cause a new malloc that will take chunk p5 place
– Since the double free of p5 will free the chunk for puts chunk, in this case we don’t need to write to a new chunk, we just need to do a puts to place our rogue FD pointer.

The execution

If you want this proof of concept to work, the first thing to do is compile it properly with the following command line:

gcc printf_fastbins_malloc_hook.c -o printf_fastbins_malloc_hook -ggdb -z execstack

After compiling, all we have to do is run it but, what would this PoC be without a debugging session to explain the nifty details?

Conclusions

We have managed to corrupt memory with a function from the “vfprintf” family. This leaves our imagination to think what other functions might be using the heap and leaving programmers unnoticed about such behaviour and, from an attacker perspective, the probability and scenarios of using any other functions like the one described here to corrupt and exploit heap can be on the increase.

 

That was all! Wish you heaps of happyness =)
Oh and! DO NOT hesitate on sending me an email at javier (at) the domain in the URL for any questions, inquiries, hatemail or wuddever!