initial commit
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
# La memoria de un proceso y el sistema operativo
|
||||
|
||||
## Memoria virtual
|
||||
|
||||
Cada proceso tiene su propio **espacio de direcciones virtual**. Las
|
||||
direcciones que guarda un puntero no son direcciones de la RAM física: son
|
||||
virtuales.
|
||||
|
||||
- La **MMU**, un componente del procesador, traduce cada dirección virtual a
|
||||
una física en cada acceso, usando las **tablas de páginas** del proceso.
|
||||
- Esas tablas las crea y mantiene el **núcleo** (*kernel*), y un proceso no
|
||||
puede modificarlas.
|
||||
- La memoria se gestiona en **páginas** de 4 KB. Cada página tiene permisos:
|
||||
lectura, escritura, ejecución, y si es accesible desde modo usuario o solo
|
||||
desde el núcleo.
|
||||
|
||||
Consecuencia: la misma dirección, por ejemplo `0x7ff6a000`, en dos procesos
|
||||
distintos apunta a sitios físicos diferentes. **Un proceso no puede ni
|
||||
siquiera nombrar la memoria de otro.**
|
||||
|
||||
## Qué pasa al acceder a una dirección no válida
|
||||
|
||||
1. La MMU no encuentra la traducción, o los permisos no lo permiten (por
|
||||
ejemplo, escribir en una página de solo lectura).
|
||||
2. El procesador genera una excepción de fallo de página y cede el control al
|
||||
núcleo.
|
||||
3. El núcleo decide: o era legítimo (una página que tenía en disco, por
|
||||
ejemplo), o es un error y mata el proceso.
|
||||
- Windows: `Access violation` (código `0xC0000005`).
|
||||
- Linux: señal `SIGSEGV` (`Segmentation fault`).
|
||||
|
||||
Así fallaría escribir en `"1.0.0"`: está en una página de solo lectura.
|
||||
|
||||
## Modo usuario y modo núcleo
|
||||
|
||||
- Tu programa se ejecuta en **modo usuario** (*ring 3*). Hay instrucciones
|
||||
que no puede ejecutar, y no puede acceder a las páginas del núcleo ni al
|
||||
hardware directamente.
|
||||
- El núcleo se ejecuta en **modo núcleo** (*ring 0*), con acceso a todo.
|
||||
- Para cualquier cosa fuera de su memoria (ficheros, red, USB, otros
|
||||
procesos), el programa hace una **llamada al sistema** (*syscall*): pide al
|
||||
núcleo que lo haga por él, y el núcleo comprueba permisos.
|
||||
|
||||
Por eso los transportes del proyecto (Winsock, spooler, WinUSB, sockets
|
||||
POSIX) son todos APIs del sistema: ningún programa en modo usuario habla
|
||||
directamente con la tarjeta de red ni con el USB.
|
||||
|
||||
## Leer la memoria de otro proceso
|
||||
|
||||
Solo a través del núcleo y con permisos:
|
||||
|
||||
- **Windows**: `OpenProcess` con `PROCESS_VM_READ` y después
|
||||
`ReadProcessMemory`. Se permite sobre procesos del mismo usuario; para
|
||||
procesos del sistema hacen falta permisos de administrador
|
||||
(`SeDebugPrivilege`).
|
||||
- **Linux**: `ptrace`, `process_vm_readv` o `/proc/<pid>/mem`. Hace falta el
|
||||
mismo usuario, y además lo limita `ptrace_scope`; con `root` o
|
||||
`CAP_SYS_PTRACE`, de cualquiera.
|
||||
|
||||
Así funcionan los depuradores como gdb.
|
||||
|
||||
## Leer la memoria física o la del núcleo
|
||||
|
||||
Solo desde código que se ejecute en modo núcleo, es decir, un **driver**. En
|
||||
Windows, los drivers tienen que estar firmados. En Linux, `/dev/mem` está muy
|
||||
restringido. Los fallos de CPU como Meltdown (2018) fueron graves justo porque
|
||||
permitían saltarse esa barrera.
|
||||
|
||||
## El peligro real en C: tu propia memoria
|
||||
|
||||
C no impide que un puntero lea **cualquier parte de tu propio proceso**.
|
||||
Salirse de un array no provoca un fallo si la página de al lado está mapeada:
|
||||
lee o machaca en silencio otros datos tuyos. Es comportamiento indefinido.
|
||||
|
||||
- Heartbleed (OpenSSL, 2014) era exactamente esto: una lectura fuera de los
|
||||
límites que devolvía al atacante memoria del propio servidor, con claves y
|
||||
contraseñas incluidas.
|
||||
|
||||
## Cómo actúa el software malicioso
|
||||
|
||||
Casi nunca rompe el aislamiento de memoria. Las vías habituales son:
|
||||
|
||||
1. **Usar permisos legítimos.** Un programa que ejecutas se ejecuta con
|
||||
**tus** permisos: puede leer, cifrar o enviar tus ficheros con las APIs
|
||||
normales. El ransomware no necesita ningún truco de memoria. El sistema
|
||||
operativo aísla unos procesos de otros y al núcleo de los usuarios, pero no
|
||||
protege tus ficheros de un programa que tú mismo has lanzado.
|
||||
2. **Explotar fallos de memoria en programas legítimos.** Un servidor escrito
|
||||
en C recibe datos preparados por el atacante que desbordan un buffer, y con
|
||||
eso toma el control de ese proceso. Aquí es donde la falta de comprobaciones
|
||||
de C importa. Según Microsoft y Google, en torno al 70 % de sus
|
||||
vulnerabilidades graves son fallos de seguridad de memoria.
|
||||
3. **Escalar privilegios.** Aprovechar un fallo en el núcleo o en un driver
|
||||
para pasar de usuario a administrador o a modo núcleo.
|
||||
|
||||
Defensas que usan el procesador y el sistema operativo:
|
||||
|
||||
- **DEP/NX**: las páginas de datos no son ejecutables.
|
||||
- **ASLR**: el sistema coloca el código y los datos en direcciones aleatorias
|
||||
en cada ejecución.
|
||||
- **Canarios de pila** (`-fstack-protector`): el compilador detecta si alguien
|
||||
ha sobrescrito la pila.
|
||||
|
||||
Estas defensas dificultan el ataque, pero no corrigen el fallo de fondo.
|
||||
|
||||
**Para la librería**: tratará datos que no controla, como las respuestas de la
|
||||
impresora (`DLE EOT`, `GS I`) o el texto que llegue desde Go. Toda longitud
|
||||
que venga de fuera se comprueba antes de copiar nada.
|
||||
|
||||
### Drivers del núcleo frente a la librería del proyecto
|
||||
|
||||
"Driver" es una palabra con varios significados.
|
||||
|
||||
- **Driver del núcleo**: un módulo que se carga *dentro* del núcleo, en modo
|
||||
núcleo. Habla con el hardware directamente (registros, interrupciones, DMA)
|
||||
y ofrece el dispositivo a los programas de usuario de forma controlada.
|
||||
Ejemplos: el de la tarjeta de red, `usbprint.sys`, `winusb.sys`, `usblp` en
|
||||
Linux.
|
||||
- **La librería del proyecto**: código en **modo usuario** que conoce el
|
||||
*protocolo* de la impresora (ESC/POS) y genera y envía los bytes a través de
|
||||
lo que ofrecen los drivers del núcleo.
|
||||
|
||||
La pila completa al imprimir:
|
||||
|
||||
```
|
||||
programa en Go
|
||||
└─ librería escpos (modo usuario) ← lo que construimos: sabe ESC/POS
|
||||
├─ LAN: Winsock / sockets ──▶ syscall ──▶ pila TCP/IP ──▶ driver de red
|
||||
└─ USB: spooler / WinUSB / libusb ──▶ syscall ──▶ usbprint/winusb ──▶ controlador USB
|
||||
└──── modo núcleo ────┘
|
||||
```
|
||||
|
||||
Los drivers del núcleo solo saben mover bytes y no entienden ESC/POS. La
|
||||
librería entiende ESC/POS, pero no puede tocar el hardware.
|
||||
|
||||
No es un "driver de mentira": es otra capa. Los drivers de impresora de
|
||||
Windows que instalan los fabricantes también son, en su mayor parte, código
|
||||
en modo usuario que convierte el documento al lenguaje de la impresora;
|
||||
debajo usan los mismos drivers del núcleo. Microsoft tiene incluso un marco
|
||||
para escribir drivers en modo usuario (UMDF).
|
||||
|
||||
**En Go**: es como `net/http`. No implementa TCP, que es cosa del núcleo:
|
||||
habla HTTP sobre los sockets que le da el sistema. La librería hace lo mismo
|
||||
con ESC/POS.
|
||||
|
||||
### Caso real: CrowdStrike (19 de julio de 2024)
|
||||
|
||||
- **Qué es**: Falcon, el antivirus/EDR de CrowdStrike, tiene en Windows un
|
||||
**driver en modo núcleo** que se carga al arrancar el sistema. Está en el
|
||||
núcleo para poder vigilar todo el sistema y para que un malware no pueda
|
||||
desactivarlo.
|
||||
- **Qué pasó**: una actualización de contenido (un fichero de *datos*, el
|
||||
"Channel File 291", no código nuevo) definía 21 campos de entrada. El
|
||||
código del driver que lo procesaba solo proporcionaba 20. Al acceder al
|
||||
campo 21 hizo una **lectura fuera de límites**, en modo núcleo.
|
||||
- **Por qué fue tan grave**: en modo usuario, el núcleo mata el proceso y el
|
||||
resto del sistema sigue funcionando. En modo núcleo no hay nadie por encima
|
||||
que pueda recoger el error, así que Windows se detiene entero (pantalla
|
||||
azul). Como el driver se carga al arrancar, el equipo caía **en cada
|
||||
reinicio**. Microsoft estimó unos 8,5 millones de equipos afectados:
|
||||
aerolíneas, hospitales, bancos.
|
||||
- **Arreglo**: manual, equipo por equipo: arrancar en modo seguro y borrar el
|
||||
fichero. Con BitLocker hacía falta además la clave de recuperación de cada
|
||||
disco.
|
||||
- **Fallos encadenados**:
|
||||
1. El driver confió en que los datos de fuera tenían el tamaño esperado.
|
||||
2. El validador de contenido tenía su propio fallo y dio el fichero por
|
||||
bueno.
|
||||
3. La actualización se envió a todos los clientes a la vez, sin
|
||||
despliegue progresivo.
|
||||
- **Consecuencia**: Microsoft anunció después planes para que los antivirus
|
||||
puedan funcionar fuera del núcleo.
|
||||
|
||||
**En Go**, ese acceso habría sido un `panic: index out of range` en un
|
||||
proceso. En C dentro del núcleo, millones de pantallas azules.
|
||||
|
||||
**Para el proyecto**: todo lo que haremos (Winsock, spooler, WinUSB, libusb)
|
||||
se ejecuta en modo usuario. Un fallo en la librería tumba el programa que la
|
||||
usa, no la máquina.
|
||||
|
||||
### cgo: Go y C comparten proceso
|
||||
|
||||
Con cgo, la librería en C se ejecuta **dentro del mismo proceso** que el
|
||||
programa en Go, en el mismo espacio de direcciones. Las comprobaciones de Go
|
||||
solo protegen el código Go: el código C puede escribir en cualquier parte del
|
||||
proceso, incluidas la memoria de Go y las estructuras de su runtime.
|
||||
|
||||
- Un desbordamiento en la librería puede corromper datos de Go y provocar un
|
||||
fallo mucho más tarde, en código Go correcto, lejos de la causa.
|
||||
- Por LAN, cualquier equipo de la red puede hacerse pasar por la impresora en
|
||||
`IP:9100` y enviar respuestas preparadas. Si la librería las copia sin
|
||||
comprobar la longitud, ese equipo puede tomar el control del programa en Go.
|
||||
|
||||
**La garantía de seguridad de memoria de Go termina en la frontera de cgo.**
|
||||
|
||||
**En Go**: el modelo de procesos es el mismo, porque lo impone el sistema
|
||||
operativo y no el lenguaje. La diferencia es que Go comprueba los límites de
|
||||
cada slice y cada puntero `nil`, y provoca un `panic` **antes** del acceso
|
||||
indebido. C no comprueba nada; para detectar estos errores están
|
||||
AddressSanitizer y valgrind.
|
||||
Reference in New Issue
Block a user