Random IRC quote :      < inocraM> si, despues de eso kernighan se intento cortar las venas

Depurando manzanas a ciegas

Hace algunos días, cayó en las manos de Victor un DVD con el MacOSX para instalar en maquinas intel «normales», MacOSXVictor andaba rondando la idea de comprarse el flamante Mac que tiene hoy y decidimos probar el sistema operativo que para nosotros era un gran desconocido. Procedimos a instalar el susodicho DVD en dos máquinas diferentes y cuando acabó la instalación, apareció la bonita interfaz del MacOSX, despues de pelearnos un rato con algunos conceptos de manejo del sistema, seguramente heredados de nuestras manías windowseras, vimos que lo único que no funcionaba era la red. La máquina al no ser un Mac nativo no venía con el correspondiente driver para la tarjeta de red, mirando un poco por internet, acabamos en la web del proyecto osx86, en los foros habían colgado un driver, que se ve que era de otra tarjeta con un chipset parecido, y en el cual habían modificado entre otras cosas el «Device ID» y habían insertado algun parchecillo por problemas que daba leyendo la NVRam de la tarjeta. Bueno, procedimos a instalar el driver en una de las máquinas y funcionó, pero cuando lo instalamos en la otra nos dimos cuenta de que la MAC estaba hardcodeada en el driver!, con lo cual nuestras dos máquinas no podían compartir la misma red 🙁 . Buscamos por el mismo foro y encontramos que había gente que ya se había dado cuenta del problemilla y comentaban como arreglarlo con un editor hexadecimal, bueno pues manos a la obra con nuestro hiew (queremos un port para macosx!) fuimos y parcheamos a ciegas donde la gente comentaba, nuestro intento acabó frustrado por un kernel panic … despues de un rato mirando posts, llegamos a la conclusión de que lo mejor era pasar a abrir el driver con el IDA y aquí es donde empieza la verdadera historia de depurar manzanas a ciegas …

El primer problema que tuvimos fue cuando vimos «mach-o», aunque habíamos oido hablar de él, no teníamos ni idea de como iba el formato, así que echamos un pequeño vistazo a la documentación de apple, el driver estaba compilado en lo que llaman «binario universal«, basicamente se trata de que en el mismo fichero está embebido el código para para las plataformas ppc e intel. Al abrir con el IDA vimos que este todavía no parece soportar completamente este formato y había muchas cosas en las que se hacía un lío, no se resolvían símbolos, nos aparecía código mezclado de las dos arquitecturas, etc …, como pequeño truco por si teneís que lidiar algun día con algun binario de este tipo y Ilfak todavía no se ha puesto las pilas ( cada día me sorprende más 😀 ), deciros que si cortaís el binario con vuestro editor hexadecimal favorito, por la segunda cabecera identificada por el magic 0xCEFAEDFA el IDA empieza a enterarse de todo :-).

Empezamos a inspeccionar el código del driver y vimos lo que parecían haber sido los parches de otras personas, había una función, de la cual ahora me arrepiento de no haber copiado el desensamblado, donde al iniciarse el driver se obtenía la MAC de la tarjeta, era probable que al no funcionar exactamente igual el hardware, la funcion hubiera sido parcheada para que no se llamase y en su lugar se establecía la mac a piñón en una estructura, el driver que teníamos no funcionaba y no veíamos nada raro, aparentemente parecía que tenía que funcionar, nos pusimos a poner parches por aquí y por allá. Todo esto sin debugger, simplemente probando el driver y esperando a ver que pasaba, hasta que dimos con una combinacion en la que parecía funcionar, todo era excesivamente extraño, no habíamos hecho nada que fuera diferente a lo que ya estaba hecho.

Pese a que teníamos un driver funcional, no podíamos parar de darle vueltas al asunto, además el driver que teníamos tenía un problema que queríamos solucionar: se colgaba si intentabas cambiar la MAC con ifconfig, solamente funcionaba al iniciarse el driver. Total que seguimos navegando por el código del driver, hasta que paramos con lo siguiente:

__text:00000000 BCM5751Enet::BCM5751Enet(OSMetaClass const*) proc near
__text:00000000 ; CODE XREF: BCM5751Enet::BCM5751Enet(OSMetaClass const*)+14
__text:00000000 ; __text:0000003Cp …
__text:00000000
__text:00000000 var_18 = dword ptr -18h
__text:00000000 var_14 = dword ptr -14h
__text:00000000 arg_0 = dword ptr 8
__text:00000000 arg_4 = dword ptr 0Ch
__text:00000000
__text:00000000 000 push ebp
__text:00000001 004 mov ebp, esp
__text:00000003 004 push ebx
__text:00000004 008 sub esp, 14h
__text:00000007 01C mov ebx, [ebp+arg_0]
__text:0000000A 01C mov eax, [ebp+arg_4]
__text:0000000D 01C mov [esp+18h+var_14], eax
__text:00000011 01C mov [esp+18h+var_18], ebx
__text:00000014 01C call BCM5751Enet::BCM5751Enet(OSMetaClass const*)
__text:00000019 01C mov dword ptr [ebx], 0F100h
__text:0000001F 01C mov eax, ebx
__text:00000021
__text:00000021 loc_21: ; DATA XREF: __data:_mediumTableo
__text:00000021 01C add esp, 14h
__text:00000024 008 pop ebx
__text:00000025 004 pop ebp
__text:00000026 000 retn
__text:00000026 BCM5751Enet::BCM5751Enet(OSMetaClass const*) endp

Dicha función se llamaba varias veces en el driver, y es EXCESIVAMENTE recursiva, tanto que debería cascar el programa, porque la pila a dia de hoy no es infinita, nuestros dialogos en ese momento eran tal que los siguientes:

– «como ^!###! puede ser?»

– «es imposible que eso se ejecute»

– «pero no ves las cantidad de xrefs que tiene!!?»

– «que no puede ser»

– «mete una int 3 ahí i reinicia»

Ni falta hace decir que salto la int 3, con un msg que nos decía que estabamos listos para atachearnos al debugger y nosotros pensando «sí, si tuvieramos debugger aquí ibamos a estar», aunque por otro lado pensamos «oie pues igual eso de tener un debugger no está tan mal», pero claro la depuracion por red del driver de red… como que no, cable serie no teníamos, instalar en una vmware no nos valía porque el hardware es diferente, y la principal razón: no teníamos ni idea de como iba lo de depurar un driver ahí, así que tuvimos que seguir con nuestras int 3´s y nuestros casques …

De repente se nos ocurrió una feliz idea:

– «oye esto no tendrá relocs?»

– «supongo..»

– «y que pasa si llenas un buffer de nops y resulta que hay una reloc para algo que hay por el medio»

– «oxtia, pues que a saber lo que se ejecuta! xD»

– «y la función recursiva esa que hemos visto antes,,, no será que por lo que sea están aplicandose relocs sobre el call …»

– «pero ese call es relativo, no _debería_ necesitar relocs!»

Y efectivamente … el IDA no interpreta las relocs en los ficheros mach-o todavía, de hecho lo avisa al cargar el ejecutable, pero se ve que no nos sirvió de advertencia … Buscando herramientas de desensamblado, solo por quitarme la curiosidad, vi que con junto con xcode, viene una utilidad llamada otool, que sí que interpreta las relocs, y con la cual obtuve el siguiente desensamblado:

__ZN11BCM5751EnetC2EPK11OSMetaClass:
00000000 pushl %ebp
00000001 movl %esp,%ebp
00000003 pushl %ebx
00000004 subl $0x14,%esp
00000007 movl 0x08(%ebp),%ebx
0000000a movl 0x0c(%ebp),%eax
0000000d movl %eax,0x04(%esp,1)
00000011 movl %ebx,(%esp,1)
00000014 calll __ZN20IOEthernetControllerC2EPK11OSMetaClass
00000019 movl $__ZTV11BCM5751Enet,(%ebx)
0000001f movl %ebx,%eax
00000021 addl $0x14,%esp
00000024 popl %ebx
00000025 popl %ebp
00000026 ret

Perfecto, nuestra teoría era válida!, ya podíamos hacer parches!, solamente teníamos que tener cuidado de no tocar cosas que pudieran contener codigo que necesitara relocs! ( recordad esto toda vuestra vida!, yo ya lo he apuntado por las paredes de casa, «ojo, relocs!» xD ), así que continuamos con la desgracia del driver…

El siguiente paso fue mirar la documentacion de apple del kernel, a ver como se implementaban los drivers ethernet, en el enlace anterior podeís ver que los controladores de este tipo heredan de una clase y deben implementar los metodos que ahí se listan, si bien algunos de ellos, no son obligatorios como es el caso de setHardwareAddress:

setHardwareAddress(const IOEthernetAddress *)


Sets or changes the station address used by the Ethernet controller.

public
virtual IOReturn setHardwareAddress(
const IOEthernetAddress *addrP);
Parameters
addrP
Pointer to an IOEthernetAddress containing the new station address.
Return Value

The default implementation will always return kIOReturnUnsupported. If overridden, drivers must return kIOReturnSuccess on success, or an error return code otherwise.

Discussion

This method is called in response to a client command to change the station address used by the Ethernet controller. Implementation of this method is optional. This method is called from the workloop context.

La implementacion de setHardwareAddress era la siguiente:

__text:000012E2 BCM5751Enet::setHardwareAddress(IOEthernetAddress const*):
__text:000012E2 push ebp
__text:000012E3 mov ebp, esp
__text:000012E5 push ebx
__text:000012E6 sub esp, 14h
__text:000012E9 mov ebx, [ebp+8] ; Interfaz
__text:000012EC mov edx, [ebp+0Ch] ; IOEthernetAddress *
__text:000012EF test edx, edx ; NULL ?
__text:000012F1 jnz short SetMac ; MAC size
__text:000012F3 mov eax, 0E00002C2h ; Error code
__text:000012F8 jmp short GoOut
__text:000012FA ; —————————————————————————
__text:000012FA
__text:000012FA SetMac: ; CODE XREF: __text:000012F1j
__text:000012FA mov dword ptr [esp+8], 6 ; MAC size
__text:00001302 lea eax, [ebx+31Eh]
__text:00001308 mov [esp+4], eax ; Mac Offset in the Object
__text:0000130C mov [esp], edx ; new IOEthernetAddress *
__text:0000130F call BCM5751Enet::BCM5751Enet(OSMetaClass const*) ; Set MAC
__text:00001314 mov byte ptr [ebx+367h], 1
__text:0000131B cmp byte ptr [ebx+304h], 0
__text:00001322 jz short SetOptionFailed
__text:00001324 mov [esp], ebx
__text:00001327 call BCM5751Enet::putToSleep(void) ; Go to dream …
__text:0000132C mov [esp], ebx
__text:0000132F call BCM5751Enet::getAdapterInfo(void)
__text:00001334 mov [esp], ebx
__text:00001337 call BCM5751Enet::wakeUp(void) ; Update Object and wake up!
__text:0000133C
__text:0000133C SetOptionFailed: ; CODE XREF: __text:00001322j
__text:0000133C xor eax, eax
__text:0000133E
__text:0000133E GoOut: ; CODE XREF: __text:000012F8j
__text:0000133E add esp, 14h
__text:00001341 pop ebx
__text:00001342 pop ebp
__text:00001343 retn

Despues de aplicar un par de parches, nos dimos cuenta de que la tarjeta se quedaba frita al hacer el wakeup y pensamos ¿será necesario hacerlo?, eché un vistazo al driver de la tigon 3 que hay para linux y no parecía que «reseteara» el adaptador en ningun momento, supusimos que la mac se almacenaba en algún registro de la tarjeta, y encontramos que había una funcion que esta anterior llamaba internamente:

SetMacAddress(IOEthernetAddress *) , esta accedía a los registros de la tarjeta usando la siguiente función:

__text:00001896 BCM5751Enet::write570XRegister(unsigned long volatile*, unsigned long) proc near
__text:00001896 ; CODE XREF: __text:000029A6p
__text:00001896 ; __text:00002ADFp …
__text:00001896
__text:00001896 var_28 = dword ptr -28h
__text:00001896 var_24 = dword ptr -24h
__text:00001896 var_20 = dword ptr -20h
__text:00001896 var_1C = dword ptr -1Ch
__text:00001896 var_18 = dword ptr -18h
__text:00001896 arg_0 = dword ptr 8
__text:00001896 arg_4 = dword ptr 0Ch
__text:00001896 arg_8 = dword ptr 10h
__text:00001896
__text:00001896 push ebp
__text:00001897 mov ebp, esp
__text:00001899 sub esp, 28h
__text:0000189C mov edx, [ebp+arg_0]
__text:0000189F mov ecx, [ebp+arg_4]
__text:000018A2 mov eax, [ebp+arg_8]
__text:000018A5 cmp byte ptr [edx+0E1Ch], 0
__text:000018AC jnz short ChangeEndian
__text:000018AE mov [esp+28h+var_18], offset aWrite570xregis ; «write570XRegister – write without power»…
__text:000018B6 mov [esp+28h+var_1C], 0
__text:000018BE mov [esp+28h+var_20], eax
__text:000018C2 mov [esp+28h+var_24], ecx
__text:000018C6 mov [esp+28h+var_28], 1
__text:000018CD call BCM5751Enet::KIOLog(uchar,ulong,ulong,ulong,char *)
__text:000018D2 jmp short GoOut
__text:000018D4 ; —————————————————————————
__text:000018D4
__text:000018D4 ChangeEndian: ; CODE XREF: BCM5751Enet::write570XRegister(ulong volatile*,ulong)+16j
__text:000018D4 cmp byte ptr [edx+0E10h], 2
__text:000018DB jnz short DMAWrite
__text:000018DD bswap eax
__text:000018DF
__text:000018DF DMAWrite: ; CODE XREF: BCM5751Enet::write570XRegister(ulong volatile*,ulong)+45j
__text:000018DF mov [ecx], eax
__text:000018E1
__text:000018E1 GoOut: ; CODE XREF: BCM5751Enet::write570XRegister(ulong volatile*,ulong)+3Cj
__text:000018E1 leave
__text:000018E2 retn
__text:000018E2 BCM5751Enet::write570XRegister(unsigned long volatile*, unsigned long) endp

La escritura parecía hacerse en plan acceso directo a memoria, y el msg de «write without power» era mosqueante, al final acabamos pensando que podíamos hacer una prueba: reescribir la implementacion de setHardwareAddress, quitar el wakeup y demás y llamar directamente a SetMacAddress, aplicando también un parchecillo en la funcion de escritura del registro por si acaso no le daba la gana escribir… escribiendo asm en el notepad y parcheando de nuevo con el hiew, quedó una cosa tal que la siguiente:

newsetHardwareAddress:

mov ebx, [esp+8] ; Iface
mov esi, [esp+0c]; IOEthernet
jnz Write
mov eax, 0E00002C2h
jmp Return

Write:

push esi
push ebx

call SetMacAddress

pop ebx
pop esi

lea edi [ebx+31Eh]
mov ecx, 6
rep movsb

xor eax, eax

Return :

ret

Cargamos el driver, intentamos cambiar la MAC y el adaptador no se quedó frito (esto ya pasaba solo quitando el wakeup), la mac en la salida de ifconfig salia cambiada :-), pero no había red, si capturabas tráfico se veía que las peticiones ARP que enviaba la tarjeta llevaban de direccion de origen la nueva, pero se ve que en algun punto el driver no hacía caso a esa nueva configuración de la tarjeta, no llegaba trafico, suponemos que porque el driver pensaba que no era para él, o quizá la propia tarjeta … no ibamos a ponernos a depurar el hardware … así que tuvimos que mandar el driver a paseo, a fin de cuentas ya podíamos utilizarlo en la misma red … de cualquier modo, si alguien sabe la solución o en dónde nos hemos equivocado, me encantaría que me enviara un mail y me comentara o que lo posteara por aquí.

La parte buena es que hemos aprendido unas cuantas cosas de la arquitectura de MacOSX y compartirlas es precisamente el objetivo de este post 🙂

P.D: Perdon por los cutre desensamblados en texto, pero era lo que había guardado, para la próxima pondré imagenes como otras veces ( da gusto «leer» en el IDA 🙂 )

Un saludo y hasta otra,

Mario

3 Comentarios para “Depurando manzanas a ciegas”

  1. Comment por Zohiartze | 03/18/07 at 7:08 pm

    Plas plas plas: Good job! Nice post, thx!

    Por cierto, tengo una alcachofa a la que le falla el driver de video, te animas?

    Ahora en serio, buen post, la vieja escuela nunca morira 🙂

  2. kg
    Comment por kg | 03/27/07 at 1:04 pm

    Oso ona
    Nunca dejais de sorprenderme.

    PD: Zohiartze tiene rellocs tu alcachofa?

  3. Comment por vallekas’ avenger | 04/08/07 at 5:18 pm

    Estos tios son la pera limonera.

    Por cierto ¿sabéis cómo hackear el politono para espiar el email de la novia de mi primo? mandádme las respuestas a bixitowapo_is_not_dead@hotmail.com.

    Un saludo tunin y regueton ford eber.

Se han cerrado los comentarios