Random IRC quote :      <carabirubi> me averras

IA32 System Call Emulation Vulnerability

ss4.pngBuenos dias, nunca escribi una entrada para el blog asi que pienso que es buen momento para empezar. Hace un par de dias salio una vulnerabilidad en el kernel para x86_64. Al parecer mucha gente no tiene muy claro de que va el tema así que voy a intentar explicar en que consiste.

En la descripción del advisory de Wojciech Purczynski esta perfectamente claro donde están los puntos vulnerables del codigo, pero no se explica exactamente en que condiciones pueden ser explotados. Luego esta el exploit que hace uso de ptrace() para la explotacion. Mucha gente piensa que el problema esta en ptrace pero en realidad ptrace es un vector al problema (el único diria yo).
Ahora vamos a analizar algo del codigo en x86_64/ia32/ia32entry.S para ver como influye ptrace en la vulnerabilidad. Como se comenta en el advisory el codigo vulnerable es el siguiente:

—8<—
sysenter_do_call:
cmpl $(IA32_NR_syscalls-1),%eax
ja ia32_badsys
IA32_ARG_FIXUP 1
call *ia32_sys_call_table(,%rax,8)
—8<—
cstar_do_call:
cmpl $IA32_NR_syscalls-1,%eax
ja ia32_badsys
IA32_ARG_FIXUP 1
call *ia32_sys_call_table(,%rax,8)
—8<—
ia32_do_syscall:
cmpl $(IA32_NR_syscalls-1),%eax
ja ia32_badsys
IA32_ARG_FIXUP
call *ia32_sys_call_table(,%rax,8) # xxx: rip relative
—8<—

Veamos el codigo de alguno de estos casos, por ejemplo el relacionado a sysenter:

ENTRY(ia32_sysenter_target)
CFI_STARTPROC32 simple
CFI_SIGNAL_FRAME
CFI_DEF_CFA rsp,0
CFI_REGISTER rsp,rbp
swapgs
movq %gs:pda_kernelstack, %rsp
addq $(PDA_STACKOFFSET),%rsp
/*
* No need to follow this irqs on/off section: the syscall
* disabled irqs, here we enable it straight after entry:
*/
sti
movl %ebp,%ebp /* zero extension */
pushq $__USER32_DS
CFI_ADJUST_CFA_OFFSET 8
/*CFI_REL_OFFSET ss,0*/
pushq %rbp
CFI_ADJUST_CFA_OFFSET 8
CFI_REL_OFFSET rsp,0
pushfq
CFI_ADJUST_CFA_OFFSET 8
/*CFI_REL_OFFSET rflags,0*/
movl $VSYSCALL32_SYSEXIT, %r10d
CFI_REGISTER rip,r10
pushq $__USER32_CS
CFI_ADJUST_CFA_OFFSET 8
/*CFI_REL_OFFSET cs,0*/
movl %eax, %eax
pushq %r10
CFI_ADJUST_CFA_OFFSET 8
CFI_REL_OFFSET rip,0
pushq %rax
CFI_ADJUST_CFA_OFFSET 8
cld
SAVE_ARGS 0,0,0
/* no need to do an access_ok check here because rbp has been
32bit zero extended */
1: movl (%rbp),%r9d
.section __ex_table,»a»
.quad 1b,ia32_badarg
.previous
GET_THREAD_INFO(%r10)
orl $TS_COMPAT,threadinfo_status(%r10)
testl $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SECCOMP),threadinfo_flags(%r10)
CFI_REMEMBER_STATE
jnz sysenter_tracesys
sysenter_do_call:
cmpl $(IA32_NR_syscalls-1),%eax
ja ia32_badsys
IA32_ARG_FIXUP 1
call *ia32_sys_call_table(,%rax,8)

Como se puede observar en el codigo, y segun lo que dice el advisory, el problema está en la comparacion de eax con el valor maximo para un indice valido en la tabla de syscalls. Luego se realiza la llamada a la syscall correspondiente usando como indice rax . Todo indica que la falla está en que se están verificando los primeros 32 bits de rax para ver si está dentro del rango válido, pero podríamos escribir en los 32 bits altos de rax manteniendo la parte baja válida y lograr saltar a donde queramos. Hasta ahi esta clara la vuln, pero si miramos un poco antes en el codigo, encontramos la siguiente instrucción.

movl %eax, %eax

Al ejecutar esa instrucción en 64bits automaticamente los 32 bits altos de rax se ponen a cero por lo cual praceria no haber falla.

Ahora es donde entra en juego ptrace. Antes de caer sobre sysenter_do_syscall se verifican algunas flags de la tarea y en el caso de que esté bajo depuracion salta a sysenter_tracesys.

testl $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SECCOMP),threadinfo_flags(%r10)
CFI_REMEMBER_STATE
jnz sysenter_tracesys

Ahora veamos que hace el codigo:

sysenter_tracesys:
CFI_RESTORE_STATE
SAVE_REST
CLEAR_RREGS
movq $-ENOSYS,RAX(%rsp) /* really needed? */
movq %rsp,%rdi /* &pt_regs -> arg1 */
call syscall_trace_enter
LOAD_ARGS ARGOFFSET /* reload args from stack in case ptrace changed it */
RESTORE_REST
movl %ebp, %ebp
/* no need to do an access_ok check here because rbp has been
32bit zero extended */
1: movl (%rbp),%r9d
.section __ex_table,»a»
.quad 1b,ia32_badarg
.previous
jmp sysenter_do_call

Como lo dice el comentario: LOAD_ARGS ARGOFFSET /* reload args from stack in case ptrace changed it */

Se recargan los registros si fueron modificados por ptrace, luego se salta a sysenter_do_call. Esta vez no se pone a cero la parte alta de rax y se puede realizar el bypass.
Vamos a ver que contiene esta macro:

.macro LOAD_ARGS offset
movq \offset(%rsp),%r11
movq \offset+8(%rsp),%r10
movq \offset+16(%rsp),%r9
movq \offset+24(%rsp),%r8
movq \offset+40(%rsp),%rcx
movq \offset+48(%rsp),%rdx
movq \offset+56(%rsp),%rsi
movq \offset+64(%rsp),%rdi
movq \offset+72(%rsp),%rax
.endm

Podemos concluir que las partes de código mostradas en el advisory no estan equivocadas sino que se esperaba que la parte alta de los registros fuese extendida a cero. Seguramente el uso el cmp de 32 bits es para ahorrar espacio.

Cambiando movq \offset+72(%rsp),%rax por movl \offset+72(%rsp),%eax se solucionaría el problema. Otra solución es desoptimizar el codigo y usar la version de 64 bits del cmp. El codigo debe ser correjido para cada uno de los casos (int 80/sysenter/syscall).

En resumen, lo que hace el exploit es mapear una pagina y colocar en ella el puntero a una funcion del proceso que contiene codigo que va a ser ejecutando en el contexto del kernel (en este caso para poner uid a cero), luego crea un proceso hijo y lo pone bajo depuracion. Cuando el hijo realiza una syscall el padre altera orig_rax de forma que se logra el bypass y en lugar de apuntar a una entrada en la tabla se queda apuntando a la pagina que mapeo anteriormente, luego ocurre el call que va a salta al puntero y ejecuta la funcion que cambia el uid a cero (root).

Pueden verificar que el exploit que funciona perfectamente, y luego probarlo luego de los cambios que comente:

ergot@happybox /home/ergot $ ./t
UID 1000, EUID:1000 GID:1000, EGID:1000
sh-3.1$ id
uid=1000(ergot) gid=1000(ergot) groups=10(wheel),1000(ergot)
sh-3.1$

A salvo 😉

Espero que la explicacion sea clara, saludos.

5 Comentarios para “IA32 System Call Emulation Vulnerability”

  1. Comment por trapito | 09/27/07 at 3:57 am

    muy bueno junkie! interesante estubo mas que nada esta bueno verlo desde el punto de vista del exploit

  2. Comment por Amian | 09/27/07 at 9:35 am

    Que bueno man, por fin he conseguido chingar el advisory que antes no lo comprendi bien wey.

  3. Comment por Mario Ballano | 09/27/07 at 1:28 pm

    Bueno erg0t, te costó pero al final publicaste!!, muy bueno el análisis pero el cododrilo me da miedo :-O

    Un saludo,

    Mario

  4. Comment por straw | 09/27/07 at 2:41 pm

    El cocodrilo se parece al zaplana.

  5. Comment por erg0t | 09/28/07 at 5:24 am

    trapito, no comente mucho el exploit porque la verdad en si no tiene nigun mecanismo interesante y no queria extender mucho el post. Algo que no comente bien fue al decir que pedia una pagina y escribe un puntero, en realidad pide mas y las llena con el puntero ya que no se puede saber con exactitud en que direccion esta ia32_sys_call_table (a menos que pudiesemos leer de /proc/kallsyms), vamos algo muy parecido a lo que se hace al poner un pad de nops en las shellcodes. Un exploit que si es muy interesante de ver es el que hizo h0lyshit para la race en el procFS hace un tiempo, ese me gusto mucho analizarlo.

    Mario, con ese cocodilo (el de arriba) no puedes ni hacer un monedero.

    Amian, orale me alegro que puedas chingar, feliz chingueo wey!

    straw, quien es ese tal zaplana?

Se han cerrado los comentarios