📃
Writeups
Blog
  • â„šī¸whoami
  • 👩‍đŸ’ģBinary Exploitation
    • Basic Binary Protections
    • ROP
    • Format String Bug
    • Stack Pivoting
    • Partial Overwrite
    • Symbolic Execution
    • Heap
      • Heap Basics
      • Heap Overflow
      • Heap Grooming
      • Use After Free / Double Free
      • Fast Bin Attack
      • One By Off Overwrite
      • House of Force
  • 🎮HackTheBox
    • Challenges
      • Baby Website Rick
      • Space pirate: Entrypoint
    • Boxes
      • Analysis
      • DevOops
      • Celestial
      • Rebound
      • CozyHosting
      • Authority
  • 📄CTF Writeups
    • CTF Writeups
      • USCTF 2024
        • Spooky Query Leaks
      • HackTheVote
        • Comma-Club (Revenge)
      • HeroCTF 2024
        • Heappie
      • Buckeye 2024
        • No-Handouts
      • TetCTF 2024
        • TET & 4N6
      • PatriotCTF 2023
        • ML Pyjail
        • Breakfast Club
    • Authored Challenges
      • Team Rocket
Powered by GitBook
On this page
  • First Fit
  • Fast Bins
  • Example 1 - PicoCTF AreYouRoot
  • Example 2 - Swamp19 Heap Golf
  1. Binary Exploitation
  2. Heap

Heap Grooming

Manipulating the heap in a certain way

First Fit

Freeing a memory in prorgram, different bins have different ways they approach re-allocation

Fast Bins

Since Fast Bins follow LIFO (Last In First Out), the allocator will return the most recently freed chunk from the head of the fastbin (considering that the size is the same)

// Example
char *a = malloc(20); // 0x55bd0a0ac670
char *b = malloc(20); // 0x55bd0a0ac690
char *c = malloc(20); // 0x55bd0a0ac6b0
free(a);
free(b);
free(c);
b = malloc(20);   // c - 0x55bd0a0ac6b0
c = malloc(20);   // b - 0x55bd0a0ac690
d = malloc(20);   // a - 0x55bd0a0ac670

On older versions of libc, malloc does not clear the memory that has been freed, we can use this to exploit the fact that the new allocation and freed size are the same.

Example 1 - PicoCTF AreYouRoot

int main(int argc, char **argv){
  char buf[512];
  char *arg;
  uint32_t level;
  struct user *user;

  setbuf(stdout, NULL);

  menu();

  user = NULL;
  while(1){
    puts("\nEnter your command:");
    putchar('>'); putchar(' ');

    if(fgets(buf, 512, stdin) == NULL)
      break;

    if (!strncmp(buf, "show", 4)){
      if(user == NULL){
	puts("Not logged in.");
      }else{
	printf("Logged in as %s [%u]\n", user->name, user->level);
      }

    }else if (!strncmp(buf, "login", 5)){
      if (user != NULL){
	puts("Already logged in. Reset first.");
	continue;
      }

      arg = strtok(&buf[6], "\n");
      if (arg == NULL){
	puts("Invalid command");
	continue;
      }

      user = (struct user *)malloc(sizeof(struct user));
      if (user == NULL) {
	puts("malloc() returned NULL. Out of Memory\n");
	exit(-1);
      }
      user->name = strdup(arg);
      printf("Logged in as \"%s\"\n", arg);

    }else if(!strncmp(buf, "set-auth", 8)){
      if(user == NULL){
	puts("Login first.");
	continue;
      }

      arg = strtok(&buf[9], "\n");
      if (arg == NULL){
	puts("Invalid command");
	continue;
      }

      level = strtoul(arg, NULL, 10);

      if (level >= 5){
	puts("Can only set authorization level below 5");
	continue;
      }

      user->level = level;
      printf("Set authorization level to \"%u\"\n", level);

    }else if(!strncmp(buf, "get-flag", 8)){
      if (user == NULL){
	puts("Login first!");
	continue;
      }

      if (user->level != 5){
	puts("Must have authorization level 5.");
	continue;
      }

      give_flag();
    }else if(!strncmp(buf, "reset", 5)){
      if (user == NULL){
	puts("Not logged in!");
	continue;
      }

      free(user->name);
      user = NULL;

      puts("Logged out!");
    }else if(!strncmp(buf, "quit", 4)){
      return 0;
    }else{
      puts("Invalid option");
      menu();
    }
  }
}

// User Structure
struct user {
  char *username;
   double authorization_level;
}

Solve

  • We need authorization level of 5 to get the flag but we can only set it to 4 using the legitimate method

  • The chances of the allocator reallocating the user buffer after freeing is high as the user struct is not freed upon reset

  • We can just send a specially crafted username to see if the user struct will be reused upon reset

pwndbg> x/20gx 0x603410
0x603410:       0x0000000000000000      0x0000000000000021
0x603420:       0x0000000000603440      0x0000000000000000
0x603430:       0x0000000000000000      0x0000000000000021
0x603440:       0x0000000000603460      0x3837363534333231
0x603450:       0x0000000000000000      0x0000000000000021
0x603460:       0x73616c6f6863696e      0x0000000000000000
0x603470:       0x0000000000000000      0x0000000000020b91
0x603480:       0x0000000000000000      0x0000000000000000
0x603490:       0x0000000000000000      0x0000000000000000
0x6034a0:       0x0000000000000000      0x0000000000000000
0x0000000000400d8c in main ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
──────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────
*RAX  0x34333231
 RBX  0
 RCX  1
 RDX  8
 RDI  0x7fffffffe250 ◂— 'get-flag\n'
 RSI  0x401201 ◂— je 0x401232 /* 'get-flag' */
 R8   0
 R9   0x400
 R10  0x7ffff7946560 (__strncmp_sse42+2768) ◂— pslldq xmm2, 5
 R11  0
 R12  0x4008c0 (_start) ◂— xor ebp, ebp
 R13  0x7fffffffe540 ◂— 1
 R14  0
 R15  0
 RBP  0x7fffffffe460 —▸ 0x400e70 (__libc_csu_init) ◂— push r15
 RSP  0x7fffffffe220 —▸ 0x7fffffffe548 —▸ 0x7fffffffe89e ◂— '/home/kali/Desktop/nightmare-master/modules/26-heap_grooming/pico_areyouroot/auth_patched'
*RIP  0x400d8c (main+743) ◂— cmp eax, 5
───────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────
   0x400d82 <main+733>    mov    rax, qword ptr [rbp - 0x220]     RAX, [0x7fffffffe240] => 0x603440 —▸ 0x603460 ◂— 'nicholas'
   0x400d89 <main+740>    mov    eax, dword ptr [rax + 8]         EAX, [0x603448] => 0x34333231
 â–ē 0x400d8c <main+743>    cmp    eax, 5                           0x34333231 - 0x5     EFLAGS => 0x212 [ cf pf AF zf sf IF df of ]                                                                                                  
   0x400d8f <main+746>    je     main+763                    <main+763>
 
   0x400d91 <main+748>    mov    edi, 0x401218                    EDI => 0x401218 ◂— jne 0x40128e /* 'Must have authorization level 5.' */
   0x400d96 <main+753>    call   puts@plt                    <puts@plt>
 
   0x400d9b <main+758>    jmp    main+929                    <main+929>
    ↓
   0x400e46 <main+929>    jmp    main+80                     <main+80>
    ↓
   0x400af5 <main+80>     mov    edi, 0x4010e0                    EDI => 0x4010e0 ◂— or al, byte ptr [rbp + 0x6e] /* '\nEnter your command:' */
   0x400afa <main+85>     call   puts@plt                    <puts@plt>
 
   0x400aff <main+90>     mov    edi, 0x3e                        EDI => 0x3e
  1. We can see that the username pointer is pointing to 0x603460 - 0x73616c6f6863696e - "nicholas" and our input has overridden the authorization level 0x3837363534333231

  2. Since they only compare the last 4 bytes of the authorization level 0x34333231, we can simply adjust our payload to <username>\x05 and send it giving us the flag

Example 2 - Swamp19 Heap Golf

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+4h] [rbp-1BCh]
  int i; // [rsp+8h] [rbp-1B8h]
  int v6; // [rsp+Ch] [rbp-1B4h]
  _DWORD *v7; // [rsp+10h] [rbp-1B0h]
  _DWORD *v8; // [rsp+18h] [rbp-1A8h]
  void *ptr[50]; // [rsp+20h] [rbp-1A0h]
  char buf[8]; // [rsp+1B0h] [rbp-10h] BYREF
  unsigned __int64 v11; // [rsp+1B8h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  v7 = malloc(0x20uLL);
  write(0, "target green provisioned.\n", 0x1AuLL);
  ptr[0] = v7;
  v4 = 1;
  write(0, "enter -1 to exit simulation, -2 to free course.\n", 0x30uLL);
  while ( 1 )
  {
    write(0, "Size of green to provision: ", 0x1CuLL);
    read(1, buf, 4uLL);
    v6 = atoi(buf);
    if ( v6 == -1 )
      break;
    if ( v6 == -2 )
    {
      for ( i = 0; i < v4; ++i )
        free(ptr[i]);
      ptr[0] = malloc(0x20uLL);
      write(0, "target green provisioned.\n", 0x1AuLL);
      v4 = 1;
    }
    else
    {
      v8 = malloc(v6);
      *v8 = v4;
      ptr[v4++] = v8;
      if ( v4 == 48 )
      {
        write(0, "You're too far under par.", 0x19uLL);
        return 0;
      }
    }
    if ( *v7 == 4 )
      win_func();
  }
  return 0;
}

Solve

  1. Allocate 4 buffers of the same size (32), which the fourth buffer will be equivalent to 4

0x602290:       0x0000000000000000      0x0000000000000031
0x6022a0:       0x0000000000000000      0x0000000000000000
0x6022b0:       0x0000000000000000      0x0000000000000000
0x6022c0:       0x0000000000000000      0x0000000000000031
0x6022d0:       0x0000000000000001      0x0000000000000000
0x6022e0:       0x0000000000000000      0x0000000000000000
0x6022f0:       0x0000000000000000      0x0000000000000031
0x602300:       0x0000000000000002      0x0000000000000000
0x602310:       0x0000000000000000      0x0000000000000000
0x602320:       0x0000000000000000      0x0000000000000031
0x602330:       0x0000000000000003      0x0000000000000000
0x602340:       0x0000000000000000      0x0000000000000000
0x602350:       0x0000000000000000      0x0000000000000031
0x602360:       0x0000000000000004      0x0000000000000000
0x602370:       0x0000000000000000      0x0000000000000000
  1. When the chunks are freed, they are freed from index 0 to 4 and the another malloc(32) is called -> pointing to the address of the chunk 4

  2. We can allocate 4 more chunks of 32 bytes each, which will eventually lead to chunk4 restoring its old value of 4

PreviousHeap OverflowNextUse After Free / Double Free

Last updated 7 months ago

👩‍đŸ’ģ