Éste será el primero de varios artículos que enviaré relacionados con el análisis forense de memoria física y también de disco duro. Al final los reuniré todos en un mismo macro-artículo que publicaré en el sitio. Éste se centrará más en la reconstrucción de estructuras para su posterior estudio y en el primer método de reconstrucción de tablas de procesos vía lista enlazada. Todas las rutas a ficheros de cabecera toman como raíz en directorio de las fuentes del núcleo.
Reconstrucción de estructuras.
Éstos datos los necesitaremos para conseguir interpretar adecuadamente las salidas del depurador. Existen ciertas partes que debemos saber interpretar para la reconstrucción entre las que destaco las siguientes.
Estructuras:
Básicamente se tratan de planillas. Voy a hacer un símil con un coche. Digamos que tenemos un renault XX, los renault y los no renault (seat, ford...) comparten ciertas características. Todas esas características comunes se juntan en una estructura. Aquí un ejemplo un poco chapucero:
struct coche_struct {
int ruedas; #número de ruedas que tendrá
char color_carrocería[10]; # color de la carrocería
unsigned short int cilindrada; #cilindrada del motor en centrímetros cúbicos
signed char abs; #tiene abs
signed char dirección_asistida; #tiene dirección asistida?
struct list_head *otro_coche;
}
Las estructuras siempre se definen de esta manera:
struct nombre_estructura {
contenido
}
Lo que haya entre las llaves son el contenido de la estructura.
Aquí tenemos un ejemplo de nuestra estructura coche_struct, vamos a utilizarla:
Con ésto podemos utilizar la misma planilla para los diferentes coches/camiones/tractores/motos que queramos.
Variable:
En nuestro caso variable es cada una de las partes de nuestra estructura es decir, todo aquello que puede variar entre los distintos tipos de marcas de coche/tractores etc..., en nuestro ejemplo variables serían: ruedas, color, cilindrada, abs, dirección_asistida y otro_coche.
Tipos:
Definen cómo van a ser los datos que van a entrar en nuestra variable, es decir, pueden ser arrays de caracteres (char[tamaño]) en nuestro caso de tamaño de 10 bytes (uno por char), valores enteros (int) cuyo valor depende de la arquitectura (32 bits en ia32, es decir, puede representar valores entre 0 y 2-1) etc....
Los tipos pueden ser con o sin signo, supongamos que queremos representar un valor que oscila entre -127 y 127, podemos entonces declarar una variable de esta forma: ``signed char variable'', con ésto nuestra variable podrá tener un valor entre -127 y 127 (2-1 en valor absoluto), si queremos que sea entre 0 y 255 puede ser de la siguiente forma: ``unsigned char variable''.
Existen más tipos, muchos más, como enteros largos (long int), enteros cortos (short int) cuyo valor oscila, long int es como mínimo siempre 32 bits, ``short int'' siempre es como mínimo 16 bits. También existen enteros largos largos (long long int) cuyo valor es siempre 64 bits (8 bytes), al igual que variables asignadas u64 o s64.
Antes coloqué una estructura como variable, ``struct list_head otro_coche'', ésto es muy común en el código fuente de Linux. Puede contener punteros (referencias a direcciones de memoria) a una parte de una estructura situada en otro lado o bien puede ser necesario sustituirlo por la estructura completa. En este caso, nuestra estructura contiene dos punteros a la siguiente estructura next.coche_struct o del next.coche_struct previo, es decir, a la dirección donde se encuentra el ``struct list_head'' de otra estructura coche_struct. Dichos punteros son siempre de 32 bits (en IA32).
El kernel linux tiene creados otros tipos propios, compatibles con posix que están definidos en include/asm/posix_types.h, los base están en en el fichero de cabecera types.h.
Task_struct
Definido en include/linux/sched.h. Define la estructuras de los procesos en Linux.
Aquí está el fichero de cabecera a analizar (por lo menos la sección relativa a sched.h:
struct task_struct {
Comienzo de la estructura.
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
Aquí van los primeros 16 bytes.
STATE *STACK USAGE FLAGS
el estado es del tipo long, que sabemos se le reserva 4 bytes, *stack es un puntero, es decir, apunta a una dirección en la memoria física, en este caso a la pila del proceso. Sabemos que todos los punteros son de 4 bytes ( direcciones de 32 bits) en la arquitectura ia32 (y además little endian).
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
int oncpu;
#endif
#endif
int prio, static_prio, normal_prio;
- Que CONFIG_SMP esté activado en vuestro kernel (zless /proc/config.gz | grep CONFIG_SMP).
- Que se use __ARCH_WANT_UNLOCKED_CTXSW, ésto es un macro que se encuentra en las arquitecturas MIPS, SPARC y IA64, desgraciadamente tendréis que buscar dicho macro en las fuentes para saber donde está (grep sigue siendo vuestro amigo).
Puede ser que vuestro .config tenga CONFIG_SMP activado, pero para que ``oncpu'' aparezca en la estructura de tareas se requiere también de __ARCH_WANT_UNLOCKED_CTXSW, que no es nuestro caso, por tanto no lo usamos. La siguiente fila quedaría así:
Más cosas interesantes, ahora vemos una estructura. List_head está definida en include/linux/list.h y contiene dos punteros, al siguiente de lo que sea el primero y al previo de lo que sea después.
La lista run_list utiliza a esta estructura para crear una lista enlazada de los procesos que están en estado RUNNING por motivos de eficiencia. La siguiente fila quedará de la siguiente manera:
Como ya vimos en arquitecturas de 32 bits, los punteros son de 32 bits, por tanto nos quedará como antes. Normal_prio es de tipo int que son otros 32 bits.
struct sched_entity se;
Más tela ahora se nos incrusta una estructura de tipo sched_entity de nombre se, que se define también en sched.h. Contiene lo siguiente:
struct sched_entity {
long wait_runtime;
unsigned long delta_fair_run;
unsigned long delta_fair_sleep;
unsigned long delta_exec;
s64 fair_key;
struct load_weight load; /* for load-balancing */
struct rb_node run_node;
unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime;
u64 prev_sum_exec_runtime;
u64 wait_start_fair;
u64 sleep_start_fair;
#ifdef CONFIG_SCHEDSTATS
u64 wait_start;
u64 wait_max;
s64 sum_wait_runtime;
u64 sleep_start;
u64 sleep_max;
s64 sum_sleep_runtime;
u64 block_start;
u64 block_max;
u64 exec_max;
unsigned long wait_runtime_overruns;
unsigned long wait_runtime_underruns;
#endif
#ifdef CONFIG_FAIR_GROUP_SCHED
struct sched_entity *parent;
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq;
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q;
#endif
};
Bien comenzamos a desglosar:
long wait_runtime;
unsigned long delta_fair_run;
unsigned long delta_fair_sleep;
unsigned long delta_exec;
Wait_runtime es de tipo long, por tanto se le reservan cuatro bytes, delta_fair_run, delta_fair_sleep y delta_exec son del tipo unsigned long. Por tanto:
Otra estructura, primero vemos un s64 que es de 8 bytes, tras ello otra estructura de tipo load_weight llamada load. Si hacemos grep load_weight include/linux/* vemos que está definida en sched.h y contiene dos long sin signo, weight e inv_weight. Así que:
FAIR_KEY FAIR_KEY WEIGHT INV_WEIGHT
struct rb_node run_node;
unsigned int on_rq;
Aquí viene otra estructura, llamada run_node de tipo rb_node, haciendo un grep vemos muchas coincidencias, una de ellas en el fichero de cabecera rbtree.h que es el que examiné dado el parecido del nombre. La estructura contiene lo siguiente: una variable unsigned long rb_parent_color, y dos punteros a estructuras rb_node llamados rb_right y rb_left. Así que:
Después viene u64 sleep_start_fair que es de 8 bytes y dos condicionales que al no estar definidas en mi .config se omiten (CONFIG SCHEDSTATS por ejemplo) por tanto finalizamos la estructura que es de 88 bytes.
Continuamos:
#ifdef CONFIG_PREEMPT_NOTIFIERS
/* list of struct preempt_notifier: */
struct hlist_head preempt_notifiers;
#endif
unsigned short ioprio;
#ifdef CONFIG_BLK_DEV_IO_TRACE
unsigned int btrace_seq;
#endif
unsigned int policy;
Ninguna de las dos condicionales están en el .config, así que se omite tanto hlist_head preempt_notifiers como btrace_seq. Como curiosidad, hlist_head es parecido a list_head con la salvedad de usar sólo un puntero al anterior.
Varias cosas, cpumask_t está definido como tipo en el fichero de cabecera linux/cpumask.h, como un DECLARE_BITMAP, que contiene un array de longs sin signo, pienso que en un sistema de un microprocesador, es de uno. Por tanto 4 bytes. No tengo activado CONFIG_SCHEDSTATS O TASK_DELAY_ACCT, por tanto sched_info se omite. Tasks apunta hacia el next.task_struct del próximo proceso o del previo (lista enlazada)
CPUS_ALLOWED TIME_SLICE *TASKS.NEXT *TASKS.PREV
/*
* ptrace_list/ptrace_children forms the list of my children
* that were stolen by a ptracer.
*/
struct list_head ptrace_children;
struct list_head ptrace_list;
*PTRACE_CHILDREN.NEXT PTRACE_CHILDREN.PREV PTRACE_LIST.NEXT PTRACE_LIST.PREV
struct mm_struct *mm, *active_mm;
/* task state */
struct linux_binfmt *binfmt;
int exit_state;
*MM *ACTIVE_MM *BINFMT EXIT_STATE
Aquí tenemos tres punteros, *MM y *ACTIVE_MM suelen ser el mismo aunque lo veremos más adelante.
int exit_code, exit_signal;
int pdeath_signal; /* The signal sent when the parent dies */
/* ??? */
unsigned int personality;
EXIT_CODE EXIT_SIGNAL PDEATH_SIGNAL PERSONALITY
unsigned did_exec:1;
pid_t pid;
pid_t tgid;
#ifdef CONFIG_CC_STACKPROTECTOR
/* Canary value for the -fstack-protector gcc feature */
unsigned long stack_canary;
#endif
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->parent->pid)
*/
struct task_struct *real_parent; /* real parent process (when being debugged) */
DID_EXEC PID TGID *REAL_PARENT
El valor de pid_t lo podéis sacar de include/asm/posix_types.h que es tipo int, por tanto 32 bits.
struct task_struct *parent; /* parent process */
/*
* children/sibling forms the list of my children plus the
* tasks I'm ptracing.
*/
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
*PARENT *CHILDREN.NEXT CHILDREN.PREV SIBLING.NEXT
struct task_struct *group_leader; /* threadgroup leader */
/* PID/PID hash table linkage. */
struct pid_link pids[PIDTYPE_MAX];
struct list_head thread_group;
Pid_link pids se sustituye por: struct_hlist_node node; struct pid *pid o lo que es lo mismo: hlist_node *next hlist_node **pprev *pid. El valor de PIDTYPE_MAX es 3, dado a que PIDTYPE_MAX pertenece al tipo pid_type definido en pid.h y es un tipo enum, por tanto PIDTYPE_PID es 0, PGID es 1 SID 2 y MAX es 3., dado ésto sabemos que se reservan 36 (12x3) bytes para la estructura, por tanto queda así:
SIBLING.PREV *GROUP_LEADER *HLIST_NODE.NEXT *HLIST_NODE.PREV
*PID *HLIST_NODE_NEXT *HLIST_NODE_PPREV *PID
*HLIST_NODE_NEXT *HLIST_NODE_PPREV *PID THREAD_GROUP_NEXT
struct completion *vfork_done; /* for vfork() */
int __user *set_child_tid; /* CLONE_CHILD_SETTID */
int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */
unsigned int rt_priority;
*VFORK_DONE *SET_CHILD_TID CLEAR_CHILD_TID RT_PRIORITY
cputime_t utime, stime;
unsigned long nvcsw, nivcsw; /* context switch counts */
cputime_t está definido como tipo en include/asm-generic/cputype.h y es de tipo unsigned long.
UTIME STIME NVCSW NIVCSW
struct timespec start_time; /* monotonic time */
struct timespec real_start_time; /* boot based time */
Aquí hay una estructura timespec que se usa por duplicado, dicha estructura se define en varios sitios, uno en coda.h que estará relacionado con el sistema de archivos coda y que no nos interesa. Otro en include/time.h que es el que nos interesa (que además es la que está incluida en sched.h). Timespec equivale a: time_t tv_sec; long tv_usec; time_t está localizado en types.h, y es de tipo __kernel_time_t :), el cuál se localiza en asm/posix_types.h como tipo unsigned long. Por tanto:
TV_SEC TV_USEC TV_SEC TV_USEC
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
unsigned long min_flt, maj_flt;
cputime_t it_prof_expires, it_virt_expires;
MIN_FLT MAJ_FLT IT_PROF_EXPIRES IT_VIRT_EXPIRES
unsigned long long it_sched_expires;
struct list_head cpu_timers[3];
unsigned long long es de 64 bit, cpu_timers es un array de tres estructuras list_head (24 bytes), así queda:
uid_t y gid_t están definidos en linux/types.h que deriva a __kernel_uid_t __gid_t en asm/posix_types.h que los marca como unsigned short, por tanto 4 bytes para cada:
kernel_cap_t está definido en linux/capability.h, aquí algo me desorientó, si no estoy equivocado el chequeo que se realiza, chequea si está definido __KERNEL__ para incluir asm/current.h y en ese caso si está definido STRICT_CAP_T_TYPECHECKS se define como __u32 cap; al no ser así el tipo es __u32 kernel_cap_t, que es de 4 bytes. Desconozco la razón de por qué hacen ésto:
*GROUP_INFO CAP_EFFECTIVE CAP_INHERITABLE CAP_PERMITTED
unsigned keep_capabilities:1;
struct user_struct *user;
#ifdef CONFIG_KEYS
struct key *request_key_auth; /* assumed request_key authority */
struct key *thread_keyring; /* keyring private to this thread */
unsigned char jit_keyring; /* default keyring to attach requested keys to */
#endif
/*
* fpu_counter contains the number of consecutive context switches
* that the FPU is used. If this is over a threshold, the lazy fpu
* saving becomes unlazy to save the trap. This is an unsigned char
* so that after 256 times the counter wraps and the behavior turns
* lazy again; this to deal with bursty apps that only use FPU for
* a short time
*/
unsigned char fpu_counter;
int oomkilladj; /* OOM kill score adjustment (bit shift). */
Mi .config no tiene definido CONFIG_KEYS por tanto:
KEEP_CAPABILITIES *USER FPU_COUNTER OOMKILLADJ
char comm[TASK_COMM_LEN]; /* executable name excluding path
- access with [gs]et_task_comm (which lock
it with task_lock())
- initialized normally by flush_old_exec */
TASK_COMM_LEN está marcado en sched.h como 16, es decir, COMM es un array de 16 caracteres (un byte por carácter).
COMM COMM COMM COMM
/* file system info */
int link_count, total_link_count;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
struct sysv_sem sysvsem;
#endif
/* CPU-specific state of this task */
struct thread_struct thread;
Tengo definido CONFIG_SYSVIPC, la estructura sysv_sem se define en linux/sem.h y contiene un puntero a una estructura tipo sem_undo_list, por tanto:
LINK_COUNT TOTAL_LINK_COUNT *UNDO_LIST A.
En el offset 480 comienza la estructura thread_struct, definido en asm/processor.h (por tanto arquitectura dependiente):
struct thread_struct {
/* cached TLS descriptors. */
struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES];
unsigned long esp0;
unsigned long sysenter_cs;
unsigned long eip;
Primera en la frente :), tls_array es un... array de estructuras desc_struct de tamaño GDT_ENTRY_TLS_ENTRIES, dicha estructura está definida en processor.h y contiene dos variables de tipo unsigned long a y b. GDT_ENTRY_TLS_ENTRIES está definido en asm/segment.h y tiene valor 3, por tanto:
B A B A
B ESP0 SYSENTER_CS EIP
unsigned long esp;
unsigned long fs;
unsigned long gs;
ESP FS GS DEBUGREG0
/* Hardware debugging registers */
unsigned long debugreg[8]; /* %%db0-7 debug registers */
DEBUGREG1 DEBUGREG2 DEBUGREG3 DEBUGREG4
DEBUGREG5 DEBUGREG6 DEBUGREG7 CR2
/* fault info */
unsigned long cr2, trap_no, error_code;
/* floating point info */
union i387_union i387;
TRAP_NO ERROR_CODE CWD SWD
union i387_union i387 está definido en processor.h también y contiene tres estructuras unidas de tipo i387_fsave_struct, fxsave y soft_struct, contienen lo siguiente:
Aquí hay varias variables de tipo unsigned char, al que sólo le reservan 1 byte, salvo a alimit que imagino que lo hacen como ``padding''.
/* virtual 86 mode info */
struct vm86_struct __user * vm86_info;
unsigned long screen_bitmap;
*INFO ENTRY_EIP *VM86_INFO SCREEN_BITMAP
unsigned long v86flags, v86mask, saved_esp0;
unsigned int saved_fs, saved_gs;
/* IO permissions */
unsigned long *io_bitmap_ptr;
V86FLAGS V86MASK SAVED_ESP0 *IO_BITMAP_PTR
unsigned long iopl;
/* max allowed port in the bitmap, in bytes: */
unsigned long io_bitmap_max;
};
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
IOPL IO_BITMAP_MAX *FS *FILES
/* namespaces */
struct nsproxy *nsproxy;
/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked, real_blocked;
sigset_t saved_sigmask; /* To be restored with TIF_RESTORE_SIGMASK */
*NSPROXY *SIGNAL *SIGHAND BLOCKED
BLOCKED REAL_BLOCKED REAL_BLOCKED SAVED_SIGMASK
struct sigpending pending;
struct sigpending está definida en include/linux/signal.h y contiene una estructura list_head list y una variable tipo sigset_t llamada signal, por tanto:
SAVED_SIGMASK *LIST.NEXT *LIST.PREV SIGNAL
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
SIGNAL SAS_SS_SP SAS_SS_SIZE *NOTIFIER
void *notifier_data;
sigset_t *notifier_mask;
void *security;
struct audit_context *audit_context;
*NOTIFIER_DATA *NOTIFIER_MASK *SECURITY *AUDIT_CONTEXT
seccomp_t seccomp;
/* Thread group tracking */
u32 parent_exec_id;
u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings */
spinlock_t alloc_lock;
/* Protection of the PI data structures: */
spinlock_t pi_lock;
#ifdef CONFIG_RT_MUTEXES
/* PI waiters blocked on a rt_mutex held by this task */
struct plist_head pi_waiters;
PARENT_EXEC_ID SELF_EXEC_ID PRIO_PI_WAITERS_NEXT PRIO_PI_WAITERS_PREV
La estructura plist_head está definida en el fichero de cabecera plist.h
/* Deadlock detection and priority inheritance handling */
struct rt_mutex_waiter *pi_blocked_on;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
/* mutex deadlock detection */
struct mutex_waiter *blocked_on;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
unsigned int irq_events;
int hardirqs_enabled;
unsigned long hardirq_enable_ip;
unsigned int hardirq_enable_event;
unsigned long hardirq_disable_ip;
unsigned int hardirq_disable_event;
int softirqs_enabled;
unsigned long softirq_disable_ip;
unsigned int softirq_disable_event;
unsigned long softirq_enable_ip;
unsigned int softirq_enable_event;
int hardirq_context;
int softirq_context;
#endif
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 30UL
u64 curr_chain_key;
int lockdep_depth;
struct held_lock held_locks[MAX_LOCK_DEPTH];
unsigned int lockdep_recursion;
#endif
/* journalling filesystem info */
void *journal_info;
rt_mutex_waiter está definida en $KERNEL_DIR/kernel/rtmutex_common.h sólo apunta a una posición dentro de la estructura por tanto 4 bytes. Las condicionales presentes en el anterior fragmento son negativas, por tanto las omito. Por último *journal_info de tipo void es un puntero (4 bytes). Por tanto:
PRIO_NODE_WAITERS_NEXT PRIO_NODE_WAITERS_PREV *PI_BLOCKED_ON *JOURNAL_INFO
/* stacked block device info */
struct bio *bio_list, **bio_tail;
/* VM state */
struct reclaim_state *reclaim_state;
struct backing_dev_info *backing_dev_info;
La estructura bio está definida en include/linux/bio.h son dos punteros de 4 bytes cada uno.
reclaim_state está definido en include/linux/swap.h y contiene una variable de tipo unsigned long llamada reclaimed_slab, por tanto otros 4 bytes. Backing_dev_info se localiza en include/linux/backing_dev.h es otro puntero a la estructura total, por tanto 4 bytes más:
BIO_LIST BIO_TAIL RECLAIM_STATE BACKING_DEV_INFO
struct io_context *io_context;
unsigned long ptrace_message;
siginfo_t *last_siginfo; /* For ptrace use. */
/*
* current io wait handle: wait queue entry to use for io waits
* If this thread is processing aio, this points at the waitqueue
* inside the currently handled kiocb. It may be NULL (i.e. default
* to a stack based synchronous wait) if its doing sync IO.
*/
wait_queue_t *io_wait;
Ahora tenemos tres punteros y una variable unsigned_long. io_context está definido en include/linux/blkdev.h, siginfo_t está definido en asm-generic/siginfo.h, wait_queue_t está definido en include/linux/wait.h:
Todas las condicionales son negativas. Robust_list está definida en include/linux/futex.h y futex_pi_state en kernel/futex.c Así queda:
*ROBUST_LIST PI_STATE_LIST_NEXT PI_STATE_LIST_PREV *PI_STATE_CACHE
atomic_t fs_excl; /* holding fs exclusive resources */
struct rcu_head rcu;
/*
* cache last used pipe for splice
*/
struct pipe_inode_info *splice_pipe;
#ifdef CONFIG_TASK_DELAY_ACCT
struct task_delay_info *delays;
#endif
#ifdef CONFIG_FAULT_INJECTION
int make_it_fail;
#endif
};
Y acabamos, ambas condicionales son falsas por lo tanto sólo tenemos que:
fs_excl tiene 4 bytes reservados, la estructura rcu_head está definida en include/linux/rcupdate.h y tiene 8 bytes, pipe_inode_info son 4 bytes
FS_EXCL *RCU *RCU *SPLICE_PIPE
Habréis observado que existen dos variables de tipo spinlock_t, hasta mi entendimiento tienen reservados 8 bytes por si se obtiene el lock, el problema es que en sistemas monoprocesador sin preemptibilidad no se usan (ya que no hay dos micros distintos intentando acceder al mismo recurso). Es decir oficialmente mi task_struct tiene 1320 bytes + 8 de spinlocks.
Reconstrucción de la tabla de procesos en base a su task_struct
Para ésto conviene que tengáis a mano varios terminales. Como root acceded al depurador (debe coincidir la versión del núcleo que estáis corriendo con el vmlinux:
#gdb /usr/src/linux-2.6.23-1/vmlinux /proc/kcore
Primero necesitaréis la dirección del proceso swapper, también llamado ``init_task'' presente en /usr/src/linux-2.6.23-1/System.map
Sabemos que la estructura de tareas (en mi caso) es de 1328 bytes (en caso de ser smp, se usan todos, al no serlo los ocho últimos no se usan), 332 palabras. Por tanto es la cantidad de pondremos en gdb para que nos muestre:
Información relevante a destacar en base a mi estructura:
Offset 0: estado, indica si el proceso se encuentra, (en este caso 0, ``runnable''),
Offset 152: dirección a la siguiente tarea. (0xc20fcb28)
Offset 156: tarea previa. (0xf6eeec08)
Offset 176: puntero a la estructura mm del proceso (nulo si es un hilo del kernel (0)
Offset 212: Pid del proceso. (0)
Offset 392: UID efectivo (0)
Offset 408: GID efectivo (0)
Offset 424: Privilegios efectivos (0xfffffeff, todos salvo CAP_SETPCAP)
Offset 452: Nombre del proceso (70617773 00726570 00000000 00000000, que traducido tabla ascii en mano y teniendo en cuenta que es little endian se traduce como swapper)
Offset 1136: Puntero a estructura fs (0xc06d29c0)
Offset 1140: Puntero a estructura files (0xc06d29e0)
Por brevedad sólo colocaré la tarea previa, para que veáis su mm_struct, pid y demás.
Notad algo, solo imprimo 294 bytes, la razón es que empiezo en task_struct+152 que es la dirección a donde apuntan tanto next_task como prev_task. Para ver la información previa (como el estado deberéis restar a la dirección de next_task 152 y apuntar allí el gdb).
Por tanto aquí en offset 0 está la dirección de la siguiente tarea, 0xf79025f8 y en offset 4 la de la previa 0xc23f60c8.
El puntero mm está ahora en el offset 24 (176-152) que ahora apunta a una estructura mm que contiene información acerca de su mapa de memoria. (0xf6da1040).
Pid del proceso (ae7) que convertido a decimal es 2791
UID efectivo ahora en el offset 240 (392-152), ahora 3e8, en decimal 1000. Podéis hacer un x 0xf6bba0c8+240 si os resulta más cómodo.
GID efectivo: en offset 256, su valor es 3e8 o 1000 en decimal.
Privilegios efectivos, haciendo las cuentas vemos que está en el offset 272, su valor actual es 0, dado a que sólo el superusuario las tiene concedidas.
COMM: el nombre del proceso, su valor es 0078796c 65740000 6e656d6e 006d0074 que traducido al castellano tabla ascii en mano es: NULxyl etNULNUL nemn NULmNULt, desconozco el por qué de los últimos bytes (tenment), supongo que será algún leak, fijáos en los primeros bytes, es xyl, que al ser little endian es lyx, el software con el que estoy escribiendo este artículo.
Por último pongo en negrita tanto el puntero al fs_struct como el del files_struct del proceso lyx.
Con ésto ya podéis seguir reconstruyendo la tabla de procesos, lo más natural es hacerlo partiendo del init_task ir al siguiente proceso (init) y después al siguiente hasta acabar llegando otra vez al inicio (es una lista circular), esta técnica detecta troyanos en ring 3 como aquellos que se basan en (por ejemplo) la modificación de llamadas al sistema. Aún así no es perfecta ya que no detecta desenlazados de la lista circular (que el siguiente no apunte al siguiente sino dos más etc). Para ésto ya encontraremos solución, como la reconstrucción de la tabla en base a sus ``slabs''.
Muyyy interesante, de verdad que seguire muy de cerca tus articulos, entendi bastante bien las estructuras propias de los procesos y los punteros, lo que no sabia es que fuese circular es decir que el ultimo proceso volviese a apuntar al primero, empezaré con pruebas basicas, en una debian virtualizada, ver los procesos en memoria y hacer un dump de la memoria fisica a archivo y hacer un miniprograma para reconstruir la lista de procesos, me ha encantado el articulo
Felicidades Javi ;)
---
La seguridad es solo un estado mental.
Entre la satisfacción y la total decepción hay solo una acción.
Solo se que se todo lo que no se
Phrack escribió un artículo de como saltarse ésto, es decir, quitar un proceso de la lista enlazada y visible sólo por el scheduler. Hay otro método que documentaré que se basa en el task_struct_cachep reconstruyendo la lista enlazada en base a los slabs (almacenes de estructuras del mismo tipo) que sí los detecta (o por lo menos eso pienso). Vamos que no te fíes al 100% de la lista enlazada.
---
"Don't accept that what's happening;
Is just a case of others' suffering;
Or you'll find that you're joining in"
-Pink Floyd- On the turning away.
si no me equivoco, ¿quieres decir no hacer caso a los punteros? y leer espacios de memoria contigua?, es decir, leyendo estructuras slab, que creo que pueden tener 3 estados (aparte de la informacion que contengan) vacio, parcial y lleno, si no me equivoco esto esta relacionado con la memoria cacheada y con una mejora de los kernels 2.6.24 (aunque existia de mucho antes), siempre me ha resultado una estructura muy compleja, aunque no imposible de entender.
---
La seguridad es solo un estado mental.
Entre la satisfacción y la total decepción hay solo una acción.
Solo se que se todo lo que no se
si no me equivoco, ¿quieres decir no hacer caso a los punteros? y leer espacios de memoria contigua?
Cada técnica es capaz de detectar más o menos cosas, por ejemplo:
Puedes reconstruir toda la lista de procesos en base a su mm_struct, ya que cada proceso tiene una asignada, salvo en el caso de los hilos del kernel que no (por tanto su límite es ese), la lista enlazada puede ser modificada sacando de la lista un proceso, tal como dicen los de phrack.
Te voy a hacer una comparación de como funcionan los slabs:
Tienes 5 cajones en tu escritorio. A cada cajón le metes una rejilla que lo subdivide en muchos minicompartimentos de tamaño fijo, aunque las rejillas de cada cajón son de tamaños distintos (unas más grandes que otras) sabes que puedes meter en ellas x objetos de su tamaño. Existen filas que están llenas de objetos (todas las casillas ocupadas), otras llenas a medias y otras en las que no has puesto ningún objeto, pero que pueden ser usadas en un futuro. Los slabs se basan en eso.
Tú sabes que cada objeto llama al siguiente por la lista enlazada aunque eso puede ser cambiado (llamando a dos procesos más en vez de uno), lo que es más complejo es que se saque ese objeto del cajón en el que se encuentra. En ese cajón están todas las estructuras tipo task.
--- "Don't accept that what's happening;
Is just a case of others' suffering;
Or you'll find that you're joining in"
-Pink Floyd- On the turning away.
Ummm y esto esta en el kernel?, tengo que buscarlo pues no lo se, pero tiene buena pinta (SLUB en lugar de SLAB), parece es la respuesta a un posible problema con SLAB, me informo mejor y comento.
Me sigue sorprendiendo que u tío que no es informático ni de lejos como tú (o Colivas, por ejemplo) se dediquen a estas tareas de chinos y yo que vivo de esta mierda de negocio pase completamente del tema.
A veces pienso si no me hubiese ido mejor dejar la informática como hobby y dedicarme a reparar electrodomésticos para ganarme la vida.
En fins, enhorabuena Javi.
Si quieres otra charla trascendental sobre procesos y memoria del SO llámame y nos tomamos un algo :)
Hombre yo cuando hablé del artículo de análisis forense por primera vez dije que podríamos hacerlo conjunto, mi oferta sigue en pie.
PD: conmigo cerca la reparación de lavadoras tiene futuro.
---
"Don't accept that what's happening;
Is just a case of others' suffering;
Or you'll find that you're joining in"
-Pink Floyd- On the turning away.
2 elevado a 32 -1 y 2 elevado a 8 -1
---
"Don't accept that what's happening;
Is just a case of others' suffering;
Or you'll find that you're joining in"
-Pink Floyd- On the turning away.