|=---------------=[ Balade a travers les processus sous linux ]=--------------=| |=----------------------------------------------------------------------------=| |=-----------------------=[ pouik ]=----------------------=| --[ Sommaire 1 - Introduction 2 - Recuperer init_task 2.1 - proc_root 2.2 - proc_root_operations 2.3 - proc_root_readdir 2.4 - proc_pid_readdir 2.5 - get_tgid_list 2.6 - init_task 3 - Let's rock 3.1 - nom 3.2 - adresse suivante 3.3 - pid 3.4 - uid 4 - Conclusion --[ 1 Introduction Cet article a pour but d'offrir une introduction au parcours des processus sous linux 2.6 par la memoire kernel. Il est simple de parcourir les taches avec un lkm par la fonction for_each_process(). En userland nous passons donc par les peripheriques /dev/kmem ou /dev/mem. On peut utiliser les headers du kernel pour obtenir la structure task_struct mais cela pose souvent des problemes de compilation et les sources ne sont pas toujours disponibles. La principale difficultee reside dans la recherche des adresses ainsi que des offsets des champs des structures. Tous les tests ont ete effectué sous architecture i386 avec kernel 2.6.16. Cet article est volontairement en ascii 7 bits aligne sur 80 colonnes. Le code correspondant a chaque point est disponible en [1]. Memoire kernel sera ici utilisee en abus de language pour designer aussi bien /dev/kmem que /dev/mem. --[ 2 Recuperer init_task #define for_each_process(p) \ for (p = &init_task ; (p = next_task(p)) != &init_task ; ) init_task est le premier process crée par le kernel. Il existe divers moyens de le récuperer : 1 - System.map 2 - lkm 3 - Memoire kernel Les deux premiers n'ayant aucun interet, nous laisserons le lecteur se demerder :p Une idee simple est de se dire que les processus etant affiches dans /proc, le kernel se sert donc a un moment donne de la variable init_task. pouik@atlantis /usr/src/linux/fs/proc $ grep -R init_task *.c base.c: p = next_task(&init_task); base.c: for ( ; p != &init_task; p = next_task(p)) { proc_misc.c: cputime_t idletime = cputime_add(init_task.utime, init_task.stime); init_task est present deux fois dans la fonction get_tgid_list. Il faut donc trouver l'adresse de get_tgid_list. pouik@atlantis /usr/src/linux/fs/proc $ grep get_tgid_list *.c base.c:static int get_tgid_list(int index, unsigned long version, unsigned int *tgids) base.c: nr_tgids = get_tgid_list(nr, next_tgid, tgid_array); Cette fonction est appelee par proc_pid_readdir, allez on recommence : pouik@atlantis /usr/src/linux/fs/proc $ grep proc_pid_readdir *.c base.c:int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) root.c: ret = proc_pid_readdir(filp, dirent, filldir); Cette fonction est appelee par proc_root_readdir, allez zou : pouik@atlantis /usr/src/linux/fs/proc $ grep proc_root_readdir *.c root.c:static int proc_root_readdir(struct file * filp, root.c: .readdir = proc_root_readdir, Cette fonction est le champ readdir de la structure proc_root_operations de type struct file_operations. pouik@atlantis /usr/src/linux/fs/proc $ grep proc_root_operations *.c root.c:static struct file_operations proc_root_operations = { root.c: .proc_fops = &proc_root_operations, Elle meme est aussi un champ de la structure proc_root de type struct proc_dir_entry. Dans son article [2], c0de a fournit la methode pour retrouver l'adresse de proc_root. En resume nous devons proceder de la maniere suivante pour recuperer init_task : => proc_root => proc_root_operation => proc_root_readdir => proc_pid_readdir => get_tgid_list => init_task --[ 2.1 proc_root Voir [2] --[ 2.2 proc_root_operations struct proc_dir_entry proc_root = { .low_ino = PROC_ROOT_INO, .namelen = 5, .name = "/proc", .mode = S_IFDIR | S_IRUGO | S_IXUGO, .nlink = 2, .proc_iops = &proc_root_inode_operations, .proc_fops = &proc_root_operations, .parent = &proc_root, }; struct proc_dir_entry { unsigned int low_ino; unsigned short namelen; const char *name; mode_t mode; nlink_t nlink; uid_t uid; gid_t gid; unsigned long size; struct inode_operations * proc_iops; struct file_operations * proc_fops; get_info_t *get_info; struct module *owner; struct proc_dir_entry *next, *parent, *subdir; void *data; read_proc_t *read_proc; write_proc_t *write_proc; atomic_t count; /* use count */ int deleted; /* delete flag */ void *set; }; int + unsigned short + 6 + int + int + int + int + unsigned long + struct inode_operations * = 4 + 2 + 6 + 4 + 4 + 4 + 4 + 4 = 32 L'adresse de proc_root_operations peut etre obtenue en lisant dans la memoire a proc_root+32. --[ 2.3 proc_root_readdir static struct file_operations proc_root_operations = { .read = generic_read_dir, .readdir = proc_root_readdir, }; struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); [...] }; L'adresse de proc_root_readdir se trouve a proc_root_operations + 6 * 4, donc a proc_root_operations+24. --[ 2.4 proc_pid_readdir (gdb) disassemble proc_root_readdir Dump of assembler code for function proc_root_readdir: 0xc0180bf9 : push %ebp 0xc0180bfa : push %edi 0xc0180bfb : push %esi 0xc0180bfc : push %ebx [...] 0xc0180c4d : jmp 0xc0182d3f [...] Il suffit ici de trouver le premier jmp dans proc_root_readdir pour trouver l'adresse de proc_pid_readdir. --[ 2.5 get_tgid_list (gdb) disassemble proc_pid_readdir Dump of assembler code for function proc_pid_readdir: 0xc0182d3f : push %ebp 0xc0182d40 : push %edi 0xc0182d41 : push %esi 0xc0182d42 : push %ebx [...] 0xc0182db3 : call 0xc0182c46 [...] Pour recuperer l'adresse de get_tgid_list dans proc_pid_readdir, il faut la aussi rechercher le premier call. --[ 2.6 init_task (gdb) disassemble get_tgid_list Dump of assembler code for function get_tgid_list: 0xc0182c46 : push %ebp 0xc0182c47 : mov $0xc0446c00,%eax 0xc0182c4c : push %edi 0xc0182c4d : push %esi [...] 0xc0182c97 : cmp $0xc03dbb60,%edx [...] Ici on doit reperer le premier type de cmp pour recuperer l'adresse de init_task , qui a pour adresse 0xc03dbb60. --[ 3 Let's rock Maintenant que nous disposons de l'adresse du premier processus, nous devons definir certains offsets dans la structure. Nous nous interesserons à 4 champs importants pour le besoin de zeppoo : - nom - pid - uid/gid - adresse du prochain processus struct task_struct { [...] struct list_head tasks; struct list_head ptrace_children; struct list_head ptrace_list; [...] pid_t pid; pid_t tgid; [...] uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; [...] char comm[TASK_COMM_LEN]; [...] }; --[ 3.1 nom struct task_struct { [...] char comm[TASK_COMM_LEN]; [...] }; (gdb) print init_task.comm $1 = "swapper\000\000\000\000\000\000\000\000" (gdb) x/4x init_task.comm 0xc0358ca4 : 0x70617773 0x00726570 0x00000000 0x00000000 La recherche de l'offset du nom du processus se fait en se basant sur le premier process. Ce process a comme nom "swapper", donc une recherche de la string "\x73\x77\x61\x70\x70\x65\x72" permettra de trouver l'offset en procedant a une soustraction. --[ 3.2 adresse du prochain processus struct task_struct { [...] struct list_head tasks; struct list_head ptrace_children; struct list_head ptrace_list; [...] }; struct list_head { struct list_head *next, *prev; }; (gdb) print &init_task $1 = (struct task_struct *) 0xc0358b00 (gdb) print init_task.tasks $2 = {next = 0xcbec9ab0, prev = 0xcbbc2600} (gdb) x/32x 0xc0358b00+40 0xc0358b28 : 0x00000000 0x00000000 0x00000000 0x5108a200 0xc0358b38 : 0x003d0dd7 0x5108a200 0x003d0dd7 0x30805400 0xc0358b48 : 0x003d0ddd 0x00000000 0x00000000 0x00000001 0xc0358b58 : 0x0000007d 0x00000000 0xcbec9ab0 0xcbbc2600 0xc0358b68 : 0xc0358b68 0xc0358b68 0xc0358b70 0xc0358b70 0xc0358b78 : 0x00000000 0xc12b56c0 0x00000000 0x00000000 0xc0358b88 : 0x00000000 0x00000000 0x00000000 0x00000000 0xc0358b98 : 0x00000000 0x00000000 0x00000000 0xc0358b00 Pour trouver la position de la structure tasks, on peut se baser sur le reperage des adresses de ptrace_children et ptrace_list qui ont leurs champs next et prev respectif de meme adresse. Ici ils ont la valeur 0xc0358b68 et 0xc0358b70. (gdb) x/32x 0xcbec9ab0 0xcbec9ab0: 0xcbec95a0 0xc0358b60 0xcbec9ab8 0xcbec9ab8 0xcbec9ac0: 0xcbec9ac0 0xcbec9ac0 0xc12b56c0 0xc12b56c0 0xcbec9ad0: 0xc0360a10 0x00000000 0x00000000 0x00000000 0xcbec9ae0: 0x00000000 0x00000000 0x00000001 0x00000001 0xcbec9af0: 0x00000001 0xc0358b00 0xc0358b00 0xcbec95f4 0xcbec9b00: 0xcbb4a124 0xc0358bac 0xc0358bac 0xcbec9a50 0xcbec9b10: 0x00000001 0x00000000 0xc117f9e0 0xcbec9b1c 0xcbec9b20: 0xcbec9b1c 0x00000001 0x00000000 0xc11809e0 L'adresse du processus init est 0xcbec9a50 (trouvable avec un lkm), pour trouver son offset on peut donc se baser sur le fait que juste avant il y a deux adresses 0xc0358b00 et 0xc0358bac qui apparaissent deux fois et sont assez rapprochees. --[ 3.3 pid struct task_struct { [...] pid_t pid; pid_t tgid; [...] }; A partir du processus init : (gdb) x/32x 0xcbec9a50+96 0xcbec9ab0: 0xcbec95a0 0xc0358b60 0xcbec9ab8 0xcbec9ab8 0xcbec9ac0: 0xcbec9ac0 0xcbec9ac0 0xc12b56c0 0xc12b56c0 0xcbec9ad0: 0xc0360a10 0x00000000 0x00000000 0x00000000 0xcbec9ae0: 0x00000000 0x00000000 0x00000001 0x00000001 0xcbec9af0: 0x00000001 0xc0358b00 0xc0358b00 0xcbec95f4 0xcbec9b00: 0xcbb4a124 0xc0358bac 0xc0358bac 0xcbec9a50 0xcbec9b10: 0x00000001 0x00000000 0xc117f9e0 0xcbec9b1c 0xcbec9b20: 0xcbec9b1c 0x00000001 0x00000000 0xc11809e0 Une methode peut consister a chercher deux 0x00000001 consecutifs à partir de l'adresse de init + l'offset de tasks. --[ 3.4 uid struct task_struct { [...] uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; [...] }; (gdb) x/100x 0xcbec9a50+156 0xcbec9aec: 0x00000001 0x00000001 0xc0358b00 0xc0358b00 0xcbec9afc: 0xcbec95f4 0xcbb4a124 0xc0358bac 0xc0358bac 0xcbec9b0c: 0xcbec9a50 0x00000001 0x00000000 0xc117f9e0 0xcbec9b1c: 0xcbec9b1c 0xcbec9b1c 0x00000001 0x00000000 0xcbec9b2c: 0xc11809e0 0xcbec9b30 0xcbec9b30 0x00000000 0xcbec9b3c: 0x00000000 0x00000000 0xcbec9634 0xc0358bf4 0xcbec9b4c: 0x00000000 0x00000000 0x00000000 0xcbec9648 0xcbec9b5c: 0xc0358c08 0x00000000 0x00000000 0x00000000 0xcbec9b6c: 0x00000000 0x00000001 0x000000f1 0x00000abb 0xcbec9b7c: 0x0000038e 0x00000000 0x1f72b478 0x000000e0 0xcbec9b8c: 0x0000000a 0x00000000 0x00000000 0x00000000 0xcbec9b9c: 0x00000000 0xcbec9ba0 0xcbec9ba0 0xcbec9ba8 0xcbec9bac: 0xcbec9ba8 0xcbec9bb0 0xcbec9bb0 0x00000000 0xcbec9bbc: 0x00000000 0x00000000 0x00000000 0x00000000 0xcbec9bcc: 0x00000000 0x00000000 0x00000000 0xc035e480 0xcbec9bdc: 0xfffffeff 0xfffffeff 0xffffffff 0x00000000 0xcbec9bec: 0xc035e3e0 0x00000000 0x74696e69 0x00726500 0xcbec9bfc: 0x00000000 0x00000000 0x00000000 0x00000000 0xcbec9c0c: 0x00000000 0x3aa0ffff 0xb7dff2e4 0x00000000 0xcbec9c1c: 0x00000000 0x00000000 0x00000000 0xcbecfff8 0xcbec9c2c: 0x00000060 0xc02f39ec 0xcbecfe7c 0x00000000 0xcbec9c3c: 0x00000033 0x00000000 0x00000000 0x00000000 0xcbec9c4c: 0x00000000 0x00000000 0x00000000 0x00000000 0xcbec9c5c: 0x00000000 0x00000000 0x00000000 0x00000000 0xcbec9c6c: 0x00000000 0xffff037f 0xffff0000 0xffffffff Pour le processus init, les champs uid,euid,suid,fsuid,gid,egid,sgid,fsgid sont tous les 8 huits a zero, ce qui est permet donc une recherche assez simple de 8 zeros à partir de init + l'offset du pid. Ce qui ici donne l'adresse 0xcbec9bb8 --[ 4 Conclusion La recherche d'offset de champ dans une structure n'est pas compliquee, mais elle peut varier assez sensiblement d'une branche du kernel a une autre. Concernant les autres offsets, la methode reste la meme, mais en utilisant kgdb(utilise ici) les recherches sont grandement simplifiees. References : [1] http://www.zeppoo.net [2] http://www.ouah.org/p61_BONUS_BONUS.txt Remerciements : Yperite, bl, n0name, et tous contre DADVSI !