CSAW CTF 2017 Revisiting Auir

This is a short post to (hopefully) answer some of the questions that I have received
about my exploit auir.py and talk about a few interesting things that I have found.

Why Free?

In my exploit, I triggered the double free corruption error instead of allocating another chunk to invoke __malloc_hook because of the constraints on the one shot gadget that I used.

Does malloc actually get called when you trigger the double free corruption error?

Yes, it does invoke __malloc_hook. To be more exact, it does not directly call __malloc_hook but one of the functions that is used calls the malloc internally.

So, which function triggers the malloc?

This is where I was stuck because I genuinely did not know. I knew for sure that malloc gets called because some of the public ctf write-ups (sorry, can not exactly remember the link) talk about freeing the chunk twice triggers malloc. However, none of the write-ups (as far as I am aware) talks about the details on which function calls the malloc. Based on the advice I have received, it seems __fortify_fail was the function that calls the malloc internally.

Does __fortify_fail actually get called when you raise the double free corruption error?

I knew that __foritfy_fail gets triggered by __stack_check_fails:

{% highlight c %}

extern char **__libc_argv attribute_hidden;

void
attribute ((noreturn))
__stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}

{% endhighlight %}

This always gets triggered when you overwrite the stack canary (stack smashing, for instance). However, I was not quite sure if the function __fortify_fail actually gets triggered for the heap related error. In order to verify this, I created a small toy program that intentionally frees the same chunk twice:

{% highlight c %}

int main(){

//fit in fastbin	
char * ptr = (char *)malloc(0x20);
//double free	
free(ptr);
free(ptr);	

}

{% endhighlight %}

Using gdb, I set a breakpoint on __fortify_fail and ran the program. The result of the program as expected threw the double free corruption error. Looking through the backtrace, __fortify_fail did not seem to get called:

{% highlight c %}

0 0x00007ffff7a42428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54

1 0x00007ffff7a4402a in __GI_abort () at abort.c:89

2 0x00007ffff7a847ea in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7ffff7b9de98 "*** Error in `%"...) at ../sysdeps/posix/libc_fatal.c:175

3 0x00007ffff7a8d37a in malloc_printerr (ar_ptr=, ptr=, str=0x7ffff7b9df60 "double free or "..., action=3) at malloc.c:5006

4 _int_free (av=, p=, have_lock=0) at malloc.c:3867

5 0x00007ffff7a9153c in __GI___libc_free (mem=) at malloc.c:2968

6 0x0000000000400594 in main ()

7 0x00007ffff7a2d830 in __libc_start_main (main=0x400566
, argc=1, argv=0x7fffffffdf68, init=, fini=, rtld_fini=, stack_end=0x7fffffffdf58) at ../csu/libc-start.c:291

8 0x0000000000400499 in _start ()

{% endhighlight %}

Instead, malloc_printerr was called. This was good to know but there were still no traces of call to malloc. Since I did not want to read the source code line by line, I set a breakpoint at __malloc_hook and re-run the program to see which function actually calls the malloc internally:

{% highlight c %}

pwndbg> b*&__malloc_hook
Breakpoint 6 at 0x7ffff7dd1b10
pwndbg> r
--- snip --
pwndbg> bt

0 0x00000000000000cc in ?? ()

1 0x00007ffff7df3f5a in __strdup (s=0x7fffffffcb50 "/lib/x86_64-lin"...) at strdup.c:42

2 0x00007ffff7def7df in _dl_load_cache_lookup (name=name@entry=0x7ffff7b99646 "libgcc_s.so.1") at dl-cache.c:311

3 0x00007ffff7de0169 in _dl_map_object (loader=loader@entry=0x7ffff7ff74c0, name=name@entry=0x7ffff7b99646 "libgcc_s.so.1", type=type@entry=2, trace_mode=trace_mode@entry=0, mode=mode@entry=-1879048191, nsid=) at dl-load.c:2342

4 0x00007ffff7dec577 in dl_open_worker (a=a@entry=0x7fffffffd240) at dl-open.c:237

5 0x00007ffff7de7564 in _dl_catch_error (objname=objname@entry=0x7fffffffd230, errstring=errstring@entry=0x7fffffffd238, mallocedp=mallocedp@entry=0x7fffffffd22f, operate=operate@entry=0x7ffff7dec4d0 <dl_open_worker>, args=args@entry=0x7fffffffd240) at dl-error.c:187

6 0x00007ffff7debda9 in _dl_open (file=0x7ffff7b99646 "libgcc_s.so.1", mode=-2147483647, caller_dlopen=0x7ffff7b22b81 <__GI___backtrace+193>, nsid=-2, argc=, argv=, env=0x7fffffffdf48) at dl-open.c:660

7 0x00007ffff7b5056d in do_dlopen (ptr=ptr@entry=0x7fffffffd460) at dl-libc.c:87

8 0x00007ffff7de7564 in _dl_catch_error (objname=0x7fffffffd450, errstring=0x7fffffffd458, mallocedp=0x7fffffffd44f, operate=0x7ffff7b50530 <do_dlopen>, args=0x7fffffffd460) at dl-error.c:187

9 0x00007ffff7b50624 in dlerror_run (args=0x7fffffffd460, operate=0x7ffff7b50530 <do_dlopen>) at dl-libc.c:46

10 __GI___libc_dlopen_mode (name=name@entry=0x7ffff7b99646 "libgcc_s.so.1", mode=mode@entry=-2147483647) at dl-libc.c:163

11 0x00007ffff7b22b81 in init () at ../sysdeps/x86_64/backtrace.c:52

12 __GI___backtrace (array=array@entry=0x7fffffffd4c0, size=size@entry=64) at ../sysdeps/x86_64/backtrace.c:105

13 0x00007ffff7a2c9f5 in backtrace_and_maps (do_abort=, do_abort@entry=2, written=, fd=fd@entry=3) at ../sysdeps/unix/sysv/linux/libc_fatal.c:47

14 0x00007ffff7a847e5 in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7ffff7b9de98 "*** Error in `%"...) at ../sysdeps/posix/libc_fatal.c:172

15 0x00007ffff7a8d37a in malloc_printerr (ar_ptr=, ptr=, str=0x7ffff7b9df60 "double free or "..., action=3) at malloc.c:5006

16 _int_free (av=, p=, have_lock=0) at malloc.c:3867

17 0x00007ffff7a9153c in __GI___libc_free (mem=) at malloc.c:2968

18 0x0000000000400594 in main ()

19 0x00007ffff7a2d830 in __libc_start_main (main=0x400566
, argc=1, argv=0x7fffffffdf38, init=, fini=, rtld_fini=, stack_end=0x7fffffffdf28) at ../csu/libc-start.c:291

20 0x0000000000400499 in _start ()

pwndbg> x /10g $rip
0xcc: Cannot access memory at address 0xcc

{% endhighlight %}

Looking at the backtrace again, I noticed that it succesfully hit __malloc_hook (the current rip is pointing at cc,which is the breakpoint I set earlier) and __strdup function was called before.

__strdup function returns the pointer to the duplicated string and sure enough it internally uses malloc to allocate the new buffer for a copy of the string:

{% highlight c %}

char *
__strdup (const char *s)
{
size_t len = strlen (s) + 1;
void *new = malloc (len); //here!

if (new == NULL)
return NULL;

return (char *) memcpy (new, s, len);
}

{% endhighlight %}

__strdup function gets called by _dl_load_cache_lookup, which is responsible for looking up the passed in name in ld.so and returning the stored filename there:

{% highlight c %}

-- snip --
char *temp;
temp = alloca (strlen (best) + 1);
strcpy (temp, best);
return __strdup (temp);
}

{% endhighlight %}

Does __fortify_fail call malloc internally?

Since __fortify_fail calls backtrace_and_maps internally, it should theoretically reach the similar path. However, when I ran my toy bof program, it was aborted as soon as it printed out an error message. After reading through the source code, I realized the first argument to libc_message is the flag that tells whether it requires to do backtrace. In this case, it had the value of 1, which is to do_abort. The rest of the value can be found in stdio.h:

{% highlight c %}

--- snip