Tags: format-string fmtstr

Rating:

[Original Writeup](https://gist.github.com/kiror0/c39d0df167405c606ebd52fa48b0fb86)

The challenge itself doesn't have any source attached, but when you solve the challenge you'll notice there's src/ folder which I have included for simplicity.


Fill your jars, then start the game and see if your friends can guess where the prize is!
2. Remove Jar
3. View Jars
4. Modify Jar
5. Start Game
Choice:



The program almost looks like a heap note challenge where there's add, delete, edit, and view. There are 2 added functionality, which is set answer and start the game. Set Answer basically malloc a struct which contain functions pointer and set the index of jar which will be the key to correct answer when start the game. Start Game is just execute the function from the pointer with the parameter in it.

By analyzing the main function from binary in ghidra, you'll notice there's an inlined strcpy at the beginning, It turns out the string will be used for printf as format string.

c
...
format_string._0_8_ = 0x746e6f432072614a;
format_string._8_8_ = 0x7325203a73746e65;
format_string[16] = '\0';
...
case 3:
i = 0;
while ((uint)i < njars) {
printf(format_string,jars[i],jars[i]);
i = i + 1;
}
break;
...

This is useful for later (fmt string) if we chain this with another bug, which is buffer overflow.

c
...
char buffer [32];
char format_string [17];
...
default:
LAB_004008ae:
puts("Choice: ");
fgets(buffer,0x28,stdin);
choice = atoi(buffer);
goto LAB_004008e5;
}
...


Notice that our buffer is $32$ while the input is 0x28 or 40 in decimal. This is perfect because format_string is aligned next to our buffer.

Ok, that's a cool 8 byte controlled fmt string payload. But, the thing is our input uses fgets which include a null byte at the end of input, :| Now we only have 7 byte controlled fmt string payload, is that enough? **absolutely**.

The idea is to use %s%..$hn and %c%..$hn instead of %Nc%..\$hhn (where N is our desired target data), also, because printf is called with
c
printf(format_string,jars[i],jars[i]);

This is easier for us because we don't need any addr leak to be used for our %s payload. Just edit the last jar with a string length match our desired target data.

We do need a libc leak for system though, and again since we have fmt string just locate the __libc_start_main_ret offset in the stack then we are good to go.

Although we have fmt string, we only have 7 byte as the payload which only enough for a %s%9hn, a 2 byte wide write and our jar only fit to 0xF8 bytes which is 1 byte wide with %s. Even if we have an overwrite it's 1 only byte with a null byte appended. To visualize what is happening with write64, suppose we want to overwrite a value to 0xdeadbeef at some address


0x603250 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10

... write8(0x603250, 0xef)
0x603250 ef 00 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10

... write8(0x603251, 0xbe)
0x603250 ef be 00 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10