Tags: fsb
Rating:
# Blind Shot
One dprintf() call with format-string bug, no output.
## Solution
Recall the `argv` parameter of `main` function. Assuming that the given binary is not manually `exec`-ed as `argv = NULL`, `argv` points to the array of arguments given at binary execution, which the first one `argv[0]` would be char pointer pointing to the binary executable's name. Let us now refer to the position of `argv` as `A`, and `argv[0]` as `B`.
Since `A` is located at lower memory address than `B`, we can use `%hhn/%hn` at `A` to partially overwrite the value of `B` to return address of `service()`. Then use `%hhn/%hn` at `B` to partially overwrite the return address to address inside `main` function, just before `service()` parameters are loaded and called (offset 0x128E). This is the "Return" primitive that we can use to do exploit the FSB once more, and can trivially be extended to leak data before returning ("Leak+Return" primitive).
Since there aren't any other useful values in the stack, one can think of using `A` and `B` to do the following:
1. Write from `A` to `B` such that the value at `B` changes to some stack address `C` (from now on represented as notation `A->B=C`)
1. `B->C=D` for some value `D`
1. `A->B=RET` for address of return address `RET`
1. `B->RET=(before service() call)`
The above 4 steps, if possible, creates a "Write+Return" primitive. This is very useful since we can write any value to stack, then return back to `main` and exploit FSB with our written value accessible as argument.
However, the above is impossible due to how positional argument is implemented in glibc `printf_positional`. `printf` processes the format strings from the start, fetching arguments one by one when it sees a new format specifier. Therefore, steps 1 and 2 are possible. When `printf` sees a positional argument specifier, it immediately resorts to `printf_positional` which starts by caching all arguments to be used. Thus, if we perform step 3 using positional argument, the value written at `B` will be cached as `C` which result in step 4 being `B->C=(start of main)`.
With a clear understanding of how argument caching in `printf_positional` works and with some novel ideas, we can think of setting `C` as `A`, `D` as `RET`, and `RET` as `(before service() call)`. Let's write the steps once more, now with `$` representing use of positional argument:
1. `A->B=A`
1. `B->A=RET`
1. `$A->RET=(before service() call)`
This idea isn't just some random magic. The key point of this idea is to **change the pointer direction of `A` and `B` from `A->B` to `B->A`** with the "Return" primitive. This "Flip+Return" primitive will enable the "Write+Return" primitive as shown below:
1. `A->RET=(before service() call)`
1. `B->A=C`
1. `$A->C=D`
1. `$B->A=RET`
With this primitive, the rest can be done with the aforementioned primitives and simple ROP. The complete steps are as below:
1. "Flip+Return", check liveliness (1.5 byte stack addr bruteforce)
1. "Write+Return", overwrite `fd = open("/dev/null")` saved at stack frame of `main()` to 1
1. "Leak+Return" -> Leak libc address from `__libc_start_main` saved at stack
1. "Write+Return" -> Write ROP chain at return address of `main()`
1. "Return" to (pop-)*ret gadget -> ROP chain triggered
I found this technique by auditing vfprintf-internal.c code, but there are some [references](https://j00ru.vexillium.org/slides/2015/insomnihack.pdf#page=98) to work out the technique.
## Exploit Code
See [solver.py](https://github.com/leesh3288/CTF/blob/master/2020/TWCTF_2020/blindshot/solver.py)