This is a fmt challenge using the scanf function. i didnt find any scanf with a fmt when i looked online for resources so this is quite an interesting challenge.
Weirdly enough, when I looked online to see if scanf has any positional arguments support, nothing popped up, so I just didn’t take it into consideration. That was until I wrote a simple test C program—and yes, scanf does support positional format specifiers.
Alright, back to the challenge. We are given the scanner.c file above.
1
#include<stdio.h>
2
#include<stdlib.h>
3
4
voidscan() {
5
charscanner[0x50] = {0};
6
while (1) {
7
fprintf(stdout, "What do you want to scan?\n");
8
scanf("%50s\n", scanner);
9
scanf(scanner);
10
11
}
12
}
13
14
15
intmain() {
16
setvbuf(stdin, NULL, _IONBF, 0);
17
setvbuf(stdout, NULL, _IONBF, 0);
18
19
printf("Welcome to The Scanner (TM)!!!\n");
20
printf("A hint for you. Just a byte, no more [0x%hhx]\n", (((unsignedlong)stdout) >>16) &0xFF);
21
scan();
22
}
It asks us to insert a 50-character string, then passes it to scanf. This means if the input string contains format specifiers, the second scanf will interpret them.
So, we can potentially write something onto the stack. If we can partially overwrite some address with a specific value and then use that value as an index in a subsequent write operation, we can achieve arbitrary write on the stack.
for more details here is a explanation from the discord user gfelber
we use the query buffer as a stack position oracle (if we write a scanf format string into it we can use it as an oracle)
because we could hit the query buffer at multiple offsets we move up to the ret ptr back to scan from scanf
we now know the first 2 bytes of the stack location of the ret address of scan which is good enought for future exploitation
Now with that out of the way, the challenge also leaks the third byte of stdout.
Combined with the ability to partially overwrite an address on the stack, this gives us almost arbitrary write into libc.
I thought that leaking the third byte wouldn’t really break ASLR, but Linux allocates 2MB pages for system libraries.
So instead of having just a byte and a half (0x1000) of offset, we actually have almost 3 bytes (0x200000) of offset.
Combined with the ability to write to any address on the stack, this gives us an arbitrary write in libc.
But first, we need to find a writable libc address to make it point to where we want.
Now comes the second trick with scanf: we can use %ms to write an arbitrarily sized string into an automatically allocated memory region. libc will allocate memory the size of the input string and copy the string into it—it uses realloc for that.
Overwrite the GOT entry of realloc with system, then call scanf("%{idx}$ms") with /bin/sh. This is equivalent to realloc("/bin/sh"), which gives us a shell thanks to the GOT overwrite.
Find a writable libc address first. For that, use scanf("%{idx}$ms") with a large string. libc will use mmap for the allocation. (For those who don’t know: mmap addresses are close to libc.)
Then make that allocated address point to libc.got.realloc by overwriting a pointer on the stack.