Extra Exploitation Technique 1: _dl_open

20180120003

Introduction

This is an exploitation technique used in solution for CODEBLUE CTF 2017 DEMOSCENEDB given by 217 [1]. This technique applies to the situation where magic gadget is not feasible and attacker gains ability to overwrite _dl_open with any value.
To give a better explanation of this technique, I will give more details based on source code libc-2.25 and the lib.so.6 given i CODEBLUE CTF 2017 DEMOSCENEDB.

Exploitation based on _dl_open_hook

When giving abort message in glibc, it will eventually call function backtrace_and_maps to give error message.

static void
backtrace_and_maps (int do_abort, bool written, int fd)
{
  if (do_abort > 1 && written)
    {
      void *addrs[64];
#define naddrs (sizeof (addrs) / sizeof (addrs[0]))
      int n = __backtrace (addrs, naddrs);
      if (n > 2)
        {
#define strnsize(str) str, strlen (str)
#define writestr(str) write_not_cancel (fd, str)
          writestr (strnsize ("======= Backtrace: =========\n"));
          __backtrace_symbols_fd (addrs + 1, n - 1, fd);

          writestr (strnsize ("======= Memory map: ========\n"));
          int fd2 = open_not_cancel_2 ("/proc/self/maps", O_RDONLY);
          char buf[1024];
          ssize_t n2;
          while ((n2 = read_not_cancel (fd2, buf, sizeof (buf))) > 0)
            if (write_not_cancel (fd, buf, n2) != n2)
              break;
          close_not_cancel_no_status (fd2);
        }
    }
}
#define BEFORE_ABORT		backtrace_and_maps

Later, function __backtrace will be invoked

// sysdeps/x86_64/backtrace.c
int
__backtrace (void **array, int size)
{
  struct trace_arg arg = { .array = array, .cfa = 0, .size = size, .cnt = -1 };

  if (size <= 0)     return 0; #ifdef SHARED   __libc_once_define (static, once);   __libc_once (once, init);   if (unwind_backtrace == NULL)     return 0; #endif   unwind_backtrace (backtrace_helper, &arg);   /* _Unwind_Backtrace seems to put NULL address above      _start.  Fix it up here.  */   if (arg.cnt > 1 && arg.array[arg.cnt - 1] == NULL)
    --arg.cnt;
  return arg.cnt != -1 ? arg.cnt : 0;
}
weak_alias (__backtrace, backtrace)

//  sysdeps/generic/libc-lock.h
/* Define once control variable.  */
#define __libc_once_define(CLASS, NAME) CLASS int NAME = 0

/* Call handler iff the first call.  */
#define __libc_once(ONCE_CONTROL, INIT_FUNCTION) \
  do {									      \
    if ((ONCE_CONTROL) == 0) {						      \
      INIT_FUNCTION ();							      \
      (ONCE_CONTROL) = 1;						      \
    }									      \
  } while (0)

From the code above, we can conclude that function init will be called before other operations. Let’s continue to view what happens in init function.

static void
init (void)
{
  libgcc_handle = __libc_dlopen ("libgcc_s.so.1");

  if (libgcc_handle == NULL)
    return;

  unwind_backtrace = __libc_dlsym (libgcc_handle, "_Unwind_Backtrace");
  unwind_getip = __libc_dlsym (libgcc_handle, "_Unwind_GetIP");
  if (unwind_getip == NULL)
    unwind_backtrace = NULL;
  unwind_getcfa = (__libc_dlsym (libgcc_handle, "_Unwind_GetCFA")
		  ?: dummy_getcfa);
}

At first glance, we can see that function __libc_dlopen and __libc_dlsym is invoked consecutively. Therefore next step is to study how these two functions work.

#define __libc_dlopen(name) \
  __libc_dlopen_mode (name, RTLD_LAZY | __RTLD_DLOPEN)

void *
__libc_dlopen_mode (const char *name, int mode)
{
  struct do_dlopen_args args;
  args.name = name;
  args.mode = mode;
  args.caller_dlopen = RETURN_ADDRESS (0);

#ifdef SHARED
  if (__glibc_unlikely (_dl_open_hook != NULL))
    return _dl_open_hook->dlopen_mode (name, mode);
  return (dlerror_run (do_dlopen, &args) ? NULL : (void *) args.map);
#else
  if (dlerror_run (do_dlopen, &args))
    return NULL;

  __libc_register_dl_open_hook (args.map);
  __libc_register_dlfcn_hook (args.map);
  return (void *) args.map;
#endif
}
libc_hidden_def (__libc_dlopen_mode)

void *
__libc_dlsym (void *map, const char *name)
{
  struct do_dlsym_args args;
  args.map = map;
  args.name = name;

#ifdef SHARED
  if (__glibc_unlikely (_dl_open_hook != NULL))
    return _dl_open_hook->dlsym (map, name);
#endif
  return (dlerror_run (do_dlsym, &args) ? NULL
	  : (void *) (DL_SYMBOL_ADDRESS (args.loadbase, args.ref)));
}

From the code above, we can see that if _dl_open_hook is not NULL, _dl_open_hook->dlopen_mode and _dl_open_hook->dlsym will be called. If we can control the value of _dl_open_hook, we can somehow chain multiple functions together to achieve some operation via crafting a fake virtual function table.

At binary level, the sequence of the execution looks as below:
20180120004
The binary of _libc_dl_openmode and _libc_dlsym is also given respectively.

20180120005

20180120006

I list the memory layout in the solution on DEMOSCENEDB given by 217 to give a better understanding on how _dl_open_hook works.

On its exiting path to free all the chunks in global list, it will trigger the abort routine when freeing the 12th element in memory.

0x604060:	0x00007ffde3215d20	0x0000000001c4b010
0x604070:	0x0000000001c68010	0x0000000001c68210
0x604080:	0x0000000001c68510	0x0000000001c68710
0x604090:	0x0000000001c68910	0x0000000001c68b10
0x6040a0:	0x0000000001c68e10	0x0000000001c69110
0x6040b0:	0x0000000001c6c010	0x0000000001c69410
0x6040c0:	0x00007f94d3f31e68	0x0000000001c6ce10
0x6040d0:	0x0000000001c6c120	0x0000000001c72510

At this point, _dl_open_hook has been overwritten to 0x1c4b108.

0x7f94d3f362e0 <_dl_open_hook>:	0x0000000001c4b108	0xc4a462e784dbbaf9

After viewing the memory layout at 0x1c4b108

(gdb) x/4gx 0x1c4b108
0x1c4b108:	0x00007f94d3b9aa69	0x00007f94d3bb2390
0x1c4b118:	0x67616c6620746163	0x0000000032263e20
(gdb) x/s 0x1c4b118
0x1c4b118:	"cat flag >&2"
(gdb) x/2i 0x00007f94d3b9aa69
   0x7f94d3b9aa69:	add    $0x10,%al
   0x7f94d3b9aa6b:	repz retq
(gdb) x/i 0x00007f94d3bb2390
   0x7f94d3bb2390 <system>:	test   %rdi,%rdi<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

Based on the binary given earlier, we can see that the functions are chained together to achieve system(“cat flag >&2”) in the end. That’s exactly what we want.

Conclusion

In this post, I give an introduction on exploitation technique base on _dl_open as a make-up for the write-up given in [1]. This technique can be applied when magic gadget is not feasible. According to the analysis in this post, the ability to chain multiple existing gadgets is much more powerful than simply printing the flat.

Reference

[1] https://github.com/david942j/ctf-writeups/tree/master/codeblue-2017/demo_scene_db

One thought on “Extra Exploitation Technique 1: _dl_open

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.