Terminé escribiendo mi shellcode de una manera diferente. Como no sabía cómo regresar, dejé que el kernel hiciera el trabajo pesado por mí, al regresar a la zona de usuarios. La idea era ejecutar mi bit de escalada de privilegios y volver a donde se suponía que debía regresar la función vulnerable, con los registros y la pila arreglados.
Tan pronto como el núcleo regresó de la función vulnerable (cuando no se desbordó), noté algo a través de gdb
. (Las direcciones son imaginarias, pero explican el concepto de todos modos).
(gdb) x/i $eip
0xadd1: ret
(gdb) x/xw $esp
0xadd1: 0xadd2
(gdb) x/6i 0xadd2
0xadd2: add esp,0x40
0xadd3: pop ...
0xadd4: pop ...
0xadd5: pop ...
0xadd6: pop ...
0xadd7: ret
Entonces, inmediatamente después de la devolución, 0x40 bytes de la pila no se usan y simplemente desaparecería con el add esp
instrucción. Por lo tanto, aprovechando este hecho, construí una cadena ROP (ya la había construido mientras escribía esta pregunta, su trabajo era deshabilitar SMEP y saltar a mi shellcode de usuario), que tenía 24 bytes de largo. 4 bytes sobrescribirían el EIP en el momento de la devolución, y los 20 restantes (0x14
) lo seguiría directamente a la pila no utilizada. Esto nos deja con 0x2c
bytes de pila sin usar todavía.
Pero si volviéramos a 0xadd2
, nos arriesgaríamos a perder otros 0x14
bytes de valiosa información de registro de la pila y llenando los registros con datos no válidos. Podríamos add 0x2c
a esp
en nuestro shellcode de usuario, y salta directamente a 0xadd3
, omitiendo el add
real instrucción.
También tomando nota de la sesión de depuración, todo excepto eax
y ebx
estaban siendo restaurados correctamente. Como mi desbordamiento los había destruido a ambos, tuve que restaurarlos con valores similares a los casos en que la función hizo un retorno limpio. (Hacer esto fue simple:establecí un punto de interrupción en 0xadd2
y extrajo los valores de info reg
)
Entonces, mi shellcode final de usuario fue este:
Execute privesc payload -> add esp,0x2c -> register fixup -> jump to 0xadd3
Al hacer esto, la ruta del código volvió perfectamente limpia y el kernel hizo la tarea de volver al modo de usuario por mí.