201 lines
9.5 KiB
Markdown
201 lines
9.5 KiB
Markdown
# 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.
|