printf("Hello\n")

Anuncio
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).
Descargar