D D ) ) 5 $ & , ! ! % = A * + 5 , 2 & & B 1 $ " & & , 6 * * & , = 0 " = " & % % * , / ; $ , ' # " / " C " + + , , " % ! ! % 7 & ! ' 1 " " * % & / + % " & % + # E + * , ' "% ! $ $ 2 ! ' ! ' % & , % & # $ > , $ / , 1 1 % % * ! ! 2 " & " & " " 5 , / * ' " , A * / % , 7 6 8 $ " & * & ( * 9 ' $ / % : ' % ; * , " . & , 1 & , % & & < & 3 / 4 $ , 4 8 ! " 9 : ? ; , % & & > $ < @ ! ? Llamadas al sistema (syscalls): G H H F F GF G F G Programa en C printf: ... ... ... int 80h libC Núcleo ... push string call printf ... I J K L J M Llamadas al sistema (syscalls): Normalmente los programas no hacen syscalls, llaman a funciones de la libC: O r P s ] Q t ^ _ u vR à Sb̀ Q Q c̀ T d Q P e f T U b̀ T V a f W d G R g̀ Q X w h x T c ^ y i Y ^ y T j Z z f a Q { i P |f d̀T [ \ b k ^ b h l m N n o ] m̀c p q open(path, flags, mode); 05h path flags mode syscall: int 80h ret open: push dword mode push dword flags push dword path mov eax, 05h call syscall open: mov eax, mov ebx, mov ecx, mov edx, int 80h 5 , B > & % + D $ ~ ~ ¡ % ~ % } & D } + ~ & % / " ~ " " " , ~ & ~ + ~ " ~ < , ~ " ~ ~ # G $ ~ ~ ~ # $ ! / $ ~ ~ $ } ~ N ~ mov eax, 05h movl $0x05, %eax mov edx, [ebp+ecx*4+040000000h] = movl $0x4000000(%ebp,%ecx,4), %edx section .text global _start msg db 'Hello, world!',0Ah len equ $ - msg _start: mov edx,len mov ecx,msg mov ebx,1 mov eax,4 int 80h mov eax,1 int 80h nasm –f elf hello.asm –o hello.o ld hello.o –o hello Ensamblado y enlazado: Ensamblar con nasm: nasm –f elf fichero.asm –o fichero.o Enlazado: ld: ld -s --dynamic-linker /lib/ld-linux.so.2 -lc fichero.o -o fichero gcc: gcc fichero.o -o fichero Programar un "hola, mundo!" con nasm. Ensamblarlo y enlazarlo. Ejecutarlo. 4 " % * + , ! & + % <+ } * 6 ' , / ! ' % # $ ! / $ 4 " % * + , ! & 4 ' + } ' + ! " , & , * , , ¢ ' ! "} = " " + $ % * + * , + ! & $ 4 1 ' "} % ' & + # $ ! " ! / " , $ & , & , * , * ! , & ¢ & $} "} temporal resb 255 contador db 0Ah ;(contador EQU 0Ah) zerobuf: times 64 db 0 mov eax, 01h ... Condiciones iniciales: Registros En kernels > 2.2: todos los registros a 0. En kernels < 2.2: ebp y ecx distintos de 0. Pila: [esp] = argc [esp+4] = argv[0] [esp+8] = argv[1] ... [esp+n*4] = envp Estructura de un ejecutable en GNU/Linux (ELF): Editores: BIEW, HTE, ELFe... Volcadores / Visualizadores: od, objdump, ELFDump, itsELF, kbview... Desensambladores: IDA (desde dosemu/wine), LDasm, DCC, Monodis, jad... Depuradores: GDB, Fenris, PrivateICE, front-ends de GDB... Trazadores: ltrace, strace, gccchecker... Shells: elfsh BIEW: Interfaz intuitivo, similar al HIEW o MC. Múltiples vistas: binario, octal, hexadecimal, desensamblado. Análisis de la cabecera de los ELF. Modificación sencilla del binario. itsELF, dos partes: Analizador de binarios ELF escrito en C++ utilizando wxWidgets. Ofrece una visión general de la estructura del binario (cabeceras, secciones, etc.). En un futuro será tambien editor hexadecimal del binario. Antivirus para UNIX Análisis mediante firmas (obtenidas de clamav db). Análisis heurísticos: Comprobación del entry_point. Comprobación de los permisos en secciones/segmentos. Comprobación del código apuntado por el entry_point. Abrir un ejecutable con BIEW. Analizar sus secciones y cabeceras. Desensamblarlo. Editarlo. Comprobar el cambio en su funcionamiento. GNU Debugger, escrito inicialmente por Richard Stallman. Orientado al debugging durante el desarrollo de software (junto con el código fuente). Optimizado para ejecutables con código fuente en C y C++ principalmente. Es un debugger de RING-3, que utiliza la syscall ptrace para funcionar. Interfaz de usuario en modo texto y mediante comandos. Syscall ptrace: Permite a un programa (típicamente un debugger), “pegarse” o “attachearse” a otro proceso ya en ejecución con el objeto de analizar su comportamiento. Debugging en modo usuario. Tiene un historial de seguridad un tanto triste (ataques DoS y rootshells). Front-ends: Xxgdb: el primero y más simple. GVD: GNU Visual Debugger, algo rústico. KDBG: wrapper para KDE del GDB. DDD: quizá el front-end más sofisticado, combinando las funcionalidades del GDB, con muchas ventajas del modo gráfico. Primeros pasos, carga del ejecutable: gdb ejecutable gdb PID gdb –args ejecutable arg1 arg2 Ejecución del programa: set / show args path / show paths show / set / unset environment var run run parámetros attach PID detach Breakpoints: info break break [TAB] [lista de símbolos] break *0x08040808 clear *0x08040808 Watchpoints: info watchpoints watch expresión (write) rwatch expresión (read) awatch expresión (acceso: r||w) Catchpoints: catch evento Ejecutando instrucciones: n, next s, step ni, nexti si, stepi finish c, continue (una línea) (una línea y entra) (una instrucción) (una instr. y entra) Examinando la pila: info frame n bt, backtrace frame n up, down Desensamblado: disassemble set disassembly-flavor intel set disassembly-flavor att Registros: info registers info all-registers Memoria: x 0x8048080 x/100 0x8048080 x/100s 0x8048080 Cargar un ejecutable con GDB. Desensamblarlo. Ejecutarlo y trazarlo. Usando la syscall PTRACE podemos crear nuestros propios debuggers o programas que se "junten" a otros (attach) para modificarlos "en caliente". Esto es bastante divertido como casi todo lo que sea juntar cosas en caliente ;-P Un trainer precisamente es eso, un debugger específico para juegos, que nos facilita cambiar aspectos del juego como número de vidas, dinero, puntuación, etc. Lamehack es un trainer para juegos en GNU/Linux y compatibles programado por ilopez, que permite modificar juegos de forma MUY sencilla. Cargar un juego. Juntarnos a el con un trainer. Buscar el número de vidas y modificarlo. £ Método 1: descriptores de ficheros. Cuando se carga un proceso en memoria, antes de que se ejecute se realizan diversas operaciones de lectura sobre varios ficheros implicados en la creación de la imagen del proceso en ejecución. El GDB abre más descriptores que los necesarios y el intento de cierre de los mismos produce un error: if(close(3)==-1) printf(“Hello shell...\n”); else printf(“Being traced...\n”); £ Método 2: argumentos y variables de entorno. La documentación de la shell bash explica que el valor de la variable de entorno “_” contiene el nombre completo de cada aplicación que se haya ejecutado. Los diferentes debuggers modifican esa variable: getenv(“_”) ./test /usr/bin/strace /usr/bin/ltrace /usr/bin/fenris (NULL) shell strace ltrace fenris gdb argv[0] ./test ./test ./test ./test /home/user/test £ Método 3: Identificación de procesos. Existe un identificador para el proceso interactivo que se llama proceso “líder” de la sesión: SID. La diferencia entre el proceso padre (PPID) y el proceso líder (SID) determinará si existe un programa intermedio desde el que se está ejecutando la aplicación: gdb 0x1968 0x3a6f 0x3a70 0x3a70 strace 0x1968 0x3a71 0x3a71 0x3a71 ltrace 0x1968 0x3a73 0x3a73 0x3a73 fenris 0x1968 0x3a75 0x3a75 0x3a75 shell getsid 0x1968 getppid 0x1968 getpgid 0x3a6e getpgrp 0x3a6e £ Metodo 4: Forzar un falso desensamblado Mediante un salto a mitad de una instrucción, dado un alineamiento, conseguimos engañar al desensamblador: jmp antidebug + 2 antidebug: db 0666h call reloc reloc: pop esi ... £ Método 5: Detectando Int 3. Cuando un debugger quiere parar en un punto determinado (breakpoint), introduce un Int 3 (0xCC) que se puede detectar: void foo(){ printf("Hello\n");} int main(){ if ((*(volatile unsigned *)((unsigned)foo + 3) & 0xff) == 0xcc) { printf("BREAKPOINT\n"); exit(1); } foo(); } £ Método 6: Introduciendo Int 3. Podemos introducir 0xCCs en el código para “molestar” al debugger: #include <signal.h> void handler(int signo){} int main(){ signal(handler, SIGTRAP); __asm__("int3"); printf("Hello\n"); } £ Método 7: Llamando a ptrace. Cuando estamos siendo debuggeados por GDB o strace/ltrace sólo es posible llamar a ptrace[PTRACE_TRACEME] una vez por proceso: if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) { printf("DEBUGGING... Bye\n"); return 1; } Este método lo utiliza Shiva, un protector/cifrador de binarios ELF, que lanza dos copias del binario que se "ptracean" la una a la otra, evitando que un debugger pueda "ptracear". Compilar ejemplos de los métodos propuestos. Intentar depurarlos y trazarlos. En ocasiones, veo bytes :-D No, en serio, dentro del Software Libre estamos muy "bien" acostumbrados a tener siempre el código fuente, pero las cosas parecen estar cambiando (drivers linuxant, etc.) y conviene seguir teniendo todo el control en nuestras manos ;-) LinuxAssembly: http://linuxassembly.org The Int80h Page: http://int80h.org List of Linux/i386 system calls: http://www.lxhp.in-berlin.de/lhpsyscal.html Phrack Magazine: http://phrack.org Agradecimientos: a todos los que me habéis ayudado a enredar a bajo nivel (en especial a ilopez y sheroc).