El problema es bastante complejo. Estaba investigando el código fuente de un rootkit, me parece que había tenido una duda sobre una linea complicada, y vos me ayudaste a desmenuzarla hace un tiempo, asi que gracias otra vez, jaja. Bueno, finalmente logré comprender como trabajaba y me dediqué a tomar funcionalidad de otros rootkits y adaptarlos al primero que estaba analizando, y el mayor trabajo que estuve haciendo fue el de hacer que el rootkit fuera compatible con cualquier versión de kernel desde la 2.6 hasta la 4.17, y pensé que lo había logrado pero luego me encontré con cosas inesperadas.
Para lograr la compatibilidad con todas las versiones de kernel, me fijé a través de la página
elixir.bootlin.com como iban cambiando las estructuras que mi rootkit usaba, eso junto con los errores que devuelve el compilador, me ayudaron a realizar ésta tarea. Parte de mi código es asi, por ejemplo:
struct proc_dir_entry {
unsigned int low_ino;
umode_t mode;
nlink_t nlink;
kuid_t uid;
kgid_t gid;
loff_t size;
const struct inode_operations *proc_iops;
const struct file_operations *proc_fops;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) && \
LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)
struct proc_dir_entry *next, *parent, *subdir;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) && \
LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
struct proc_dir_entry *parent;
struct rb_root subdir;
struct rb_node subdir_node;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
struct proc_dir_entry *parent;
struct rb_root_cached subdir;
struct rb_node subdir_node;
#endif
void *data;
atomic_t count; /* use count */
atomic_t in_use; /* number of callers into module in progress; */
/* negative -> it's going away RSN */
struct completion *pde_unload_completion;
struct list_head pde_openers; /* who did ->open, but not ->release */
spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
u8 namelen;
char name[];
};
Como ves, algunos campos de la estructura fueron cambiando en las distintas versiones de kernel. Ésta estructura estaba definida desde el principio en el rootkit que analicé, ya que "no está publicada en las cabeceras públicas de los nuevos kernels". Además de esa estructura, las variables que utilizo, que hacen referencia a campos de distintas estructuras tambien tienen esos macros dependientes de versiones de kernels. Luego de analizar detalladamente versión por versión, logré utilizar los macros adecuados para que el rootkit compilara sin errores en todos los linux que probé, todos con distitnas versiones de kernels. Aquí dejo la lista:
Kali Linux (Rolling) 4.17.9 x86_64
Kali Linux (Rolling) 4.16.18 x86_64
Linux Mint 19 (Tara) 4.15.0-20-generic i686
Kali Linux (Rolling) 4.14.0-kali3-amd64 x86_64
Ubuntu 16.04.4 LTS 4.13.0-36-generic x86_64
Fedora 26 4.11.8-300.fc26 x86_64
Ubuntu 17.04 4.10.0-19-generic x86_64
Debian 9 4.9.0-7-686 i686
Kali Linux (Rolling) 4.4.142 x86_64
Kali Linux (Rolling) 3.16.57 x86_64
Linux Mint 17 3.13.0-37-generic x86_64
Wifislax 3.12.36 i686
Fedora 19 3.9.5-301.fc19 x86_64
Ubuntu 12.10 3.5.0-17-generic i686
Lihuen 5.10 3.2.0-4-amd64 x86_64
Ubuntu 10.04.4 LTS 2.6.32-38-generic-pae i686
El problema fue cuando lo probé sobre openSUSE. openSUSE Leap 15.0 con el kernel 4.12.14, utiliza la estructura proc_dir_entry que se empezó a utilizar en los kernels 4.14. Es como que openSUSE en su release, modificó esa estructura, pero manteniendo el número de la versión de kernel anterior, entonces, dentro de mi código, pasa por los macros que no debería pasar y no termina funcionando, ya que las estructuras no coinciden. Si yo utilizo un ubuntu con el kernel 4.12.14 (exactamente la misma version), el rootkit funciona, pero no sobre openSUSE. Entonces si quiero que funcione universalmente, en los macros debería agregar algo como ésto:
struct proc_dir_entry {
unsigned int low_ino;
umode_t mode;
nlink_t nlink;
kuid_t uid;
kgid_t gid;
loff_t size;
const struct inode_operations *proc_iops;
const struct file_operations *proc_fops;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) && \
LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)
struct proc_dir_entry *next, *parent, *subdir;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) && \
LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) && NO_ES_OPENSUSE
struct proc_dir_entry *parent;
struct rb_root subdir;
struct rb_node subdir_node;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) || ES_OPENSUSE
struct proc_dir_entry *parent;
struct rb_root_cached subdir;
struct rb_node subdir_node;
#endif
void *data;
atomic_t count; /* use count */
atomic_t in_use; /* number of callers into module in progress; */
/* negative -> it's going away RSN */
struct completion *pde_unload_completion;
struct list_head pde_openers; /* who did ->open, but not ->release */
spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
u8 namelen;
char name[];
};
Para identificar si el SO es openSUSE, pensé en una forma, pero utilizando código que se ejecuta luego de la etapa de preprocesamientos, ya que no encontré macros que pregunten por una distribución en específico.
La solución por el momento sería crear dos rootkits distintos, uno especialmente echo para funcionar con openSUSE, y el otro para funcionar con todas las demas distros, pero yo no quería llegar a ésto, ya que mi idea era la de tener un solo rootkit universal que funcionara sobre cualquier linux.
Es bastante complicado el problema, si no fui claro en alguna parte, por favor escribime.
Gracias nuevamente MAFUS, que vos siempre estás aca para ayudar.
Saludos!