Bits and Bytes Security

Cyber Apocalypse 2025 - pwn_blessing

This post is part of a series where I describe some Cyber Apocalypse 2025 CTF challenges I participated in.

The list of posts is as follows:

Enjoy!

pwn_blessing

In this challenge, we can readily spot in the disassembly that it has a read_flag function. Where is it called?

.text:0000000000001723           mov     rdx, [rbp+size]
.text:0000000000001727           mov     rax, [rbp+buf]
.text:000000000000172B           add     rax, rdx
.text:000000000000172E           sub     rax, 1
.text:0000000000001732           mov     qword ptr [rax], 0
.text:0000000000001739           mov     rdx, [rbp+size]       ; n
.text:000000000000173D           mov     rax, [rbp+buf]
.text:0000000000001741           mov     rsi, rax              ; buf
.text:0000000000001744           mov     edi, 1                ; fd
.text:0000000000001749           call    _write
.text:000000000000174E           mov     rax, [rbp+var_18]
.text:0000000000001752           mov     rax, [rax]
.text:0000000000001755           test    rax, rax
.text:0000000000001758           jnz     short loc_1766
.text:000000000000175A           mov     eax, 0
.text:000000000000175F           call    read_flag             <---
.text:0000000000001764           jmp     short loc_1798

What the code does can be summarized as:

  1. Allocates a buffer of 0x30000 bytes and sets the first qword to 1.
  2. Prints the buffer pointer to stdout.
  3. Asks for a size and allocates a buffer of that size using the malloc function.
  4. Clears the first qword of this buffer.
  5. Reads from stdin into the buffer and outputs the same buffer contents.
  6. If the first qword of the 0x30000 buffer is zero, it calls read_flag.

At first glance, nothing looks buggy. However, some things seem suspicious. Why clear only the first qword? Initially, I thought it could be something like a single-byte malloc buffer overflow, messing with the malloc chunks. I assumed I needed to somehow get the allocated memory to go right behind the 0x30000 buffer, then find a way to clear the first qword. For example, if we could make it allocate the second buffer over the first one…

Given the mechanics of malloc, I could never get the resulting malloc pointer + size to end right before the 0x30000 buffer. There would always be a margin between that last byte and the 0x30000 buffer.

Lesson Learned: Would it be possible to make the second malloc buffer be right before the 0x30000 one? Probably not. I tried multiple sizes—0x30000, 0x301000, 0x2f0000—but none would make malloc return a chunk placed right before the 0x30000 buffer. In specific scenarios, we can get it close, but there is still a margin between the last byte and the first byte of the 0x30000 buffer.

To solve this challenge, we can provide that pointer as the size. For the pointer above, that would be 140737353642000. The malloc will fail with this size, resulting in a null malloc pointer, but at the 0x001723 instructions, it will do:

mov rdx, [rbp+size]		; 140737353642000 = 0x00007ffff7f87010
mov rax, [rbp+buf]		; ZERO
add rax, rdx			; ZERO + 0x00007ffff7f87010 = 0x00007ffff7f87010
sub rax, 1 				; 0x00007ffff7f87010 - 1 = 0x00007ffff7f8700f
mov qword ptr [rax], 0  ; [0x00007ffff7f8700f] = 0

We just need to provide a size in decimal that is the pointer provided plus 1.

pwn_blessing.png

The next post is about the pwn_crossbow challenge.