Attacking OpenBSD with LKM - part 1

Attacking OpenBSD with LKM - part 1

Бытует мнение что OpenBSD не поддерживает LKM, что совершеннно неверно. OpenBSD во всей своей мощи поддерживает LKM (Loadable Kernel Module). Данная статья расскажет про написание собственых LKM под OpenBSD платформу.

by __blf, RST/GHC

http://rst.void.ru

Бытует мнение что OpenBSD не поддерживает LKM, что совершеннно неверно. OpenBSD во всей своей мощи поддерживает LKM (Loadable Kernel Module). Данная статья расскажет про написание собственых LKM под OpenBSD платформу.

Очень часто LKM просто не заменимы, осбенно когда требуется быстро и скрыто произвести какие то действия на машине (как чужой так и своей). К сожалению есть очень мало документов посвященных созданию LKM для OpenBSD. Единственно что удалось найти - это парочку статей на английском и статью от российского хакера v1pee. Хочу выразить благодарность людям помогавшим мне в работе - grange и Inck-Vizitor.

Замечу сразу, что программирование на C под OpenBSD платформу довольно сильно отличается от программирования под другие unix-like платформы, а программирование частей ядра OpenBSD в корне отличается от привычного. Статья рассчитана на людей уже знакомых с LKM и ядром OpenBSD. Работать будем под OpenBSD 3.6.

Начнем с простейшего - запретим пользователям с uid отличным от 0, создавать директории. Пример в реальной жизни нам мало чем поможет, но даст хорошее представление возможностей LKM. Самый простой и грубый вариант - заменить /bin/mkdir на свой, но тут же последует последует новый перевод мануала "Использование tripwire". Поэтому вместо подмены бинарника мы подменим системный вызов mkdir.

Для этого можно пересобрать все ядро, но не проще ли написать динамически подгружаемый модуль выполняющий эту операцию? Для начала нам надо сохранить подлинный системный вызов mkdir, чтобы иметь возможность все восстановить в случае необходимости. Для этого в коде нашего модуля создадим функцию:

Код:

int (*pure_mkdir) (struct proc * p, void * v, register_t retval);
Далее нам нужно чтобы при загрузке модуля наш системный вызов заменял собой оригинальный, для этого в обработчик загрузки модуля добавляем следующий код:

Код:

case LKM_E_LOAD:
(sy_call_t *)pure_mkdir = sysent[SYS_mkdir].sy_call; 
                  sysent[SYS_mkdir].sy_call = (sy_call_t *)my_mkdir;
Теперь мы так же имеем возможность вернуть на место подлинный системный вызов. При выгрузке модуля все должно вставать на свои места, для этого снова смотрим обработчик:

Код:

case LKM_E_UNLOAD:
sysent[SYS_mkdir].sy_call = (sy_call_t *)pure_mkdir;
Теперь надо написать код подменнего вызова mkdir, который будет просто возвращать оригинальный системный вызов для root, и ошибку всем остальным. Все это займет несколько строк:

Код:

	int my_mkdir(struct proc *p, void * v, register_t retval) {
        if(suser(curproc, 0) == 0)
        {
        return(pure_mkdir(p, v, retval));
        }
        else
        {
        return EACCES;
        }
}


Вот что мы получим после загрузки модуля:

Код:

localhost:blf {103} mkdir nsd
mkdir: nsd: Permission denied
localhost:blf {104} su
Password:
localhost# mkdir nsd
localhost#
Далее несколько усложним задачу, запретим всем пользователям кроме рута создавать директории в /tmp.

Для этого нужно немного подправить системный вызов mkdir. Вот как будет выглядеть наш вызов теперь:

Код:

int my_mkdir(struct proc *p, void * v, register_t retval) {
        register struct sys_mkdir_args * uap = v;
        register struct vnode *vp;
        struct vattr vattr;
        int error;
        struct nameidata nd;

        if(suser(curproc, 0) != 0 && strncmp(SCARG(uap, path), "/tmp/", 5) == 0)
        {
        return EACCES;
        }
        NDINIT(&nd, CREATE, LOCKPARENT | STRIPSLASHES,
            UIO_USERSPACE, SCARG(uap, path), p);
        if ((error = namei(&nd)) != 0)
                return (error);
        vp = nd.ni_vp;
        if (vp != NULL) {
                VOP_ABORTOP(nd.ni_dvp, &nd.ni_cnd);
                if (nd.ni_dvp == vp)
                        vrele(nd.ni_dvp);
                else
                        vput(nd.ni_dvp);
                vrele(vp);
                return (EEXIST);
        }
        VATTR_NULL(&vattr);
        vattr.va_type = VDIR;
        vattr.va_mode = (SCARG(uap, mode) & ACCESSPERMS) &~ p->p_fd->fd_cmask;
        VOP_LEASE(nd.ni_dvp, p, p->p_ucred, LEASE_WRITE);
        error = VOP_MKDIR(nd.ni_dvp, &nd.ni_vp, &nd.ni_cnd, &vattr);
        if (!error)
                vput(nd.ni_vp);
        return (error);
}
Вот что мы получим теперь после загрузки модуля:

Код:

localhost:blf {105} mkdir nsd
localhost:blf {106} mkdir /tmp/nsd
mkdir: /tmp/nsd: Permission denied
localhost:blf {107} su
Password:
localhost# mkdir /tmp/nsd
localhost#
Вот полный код нашего модуля:

Код:

/*      $OpenBSD: mkdir_lkm.c ,v 0.1 2005/03/26 18:25:13 __blf Exp $        */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/exec.h>
#include <sys/lkm.h>
#include <sys/proc.h>
#include <sys/syscall.h>

#include <sys/namei.h>
#include <sys/filedesc.h>
#include <sys/stat.h>
#include <sys/vnode.h>

#include <sys/mount.h>
#include <sys/syscallargs.h>

int (*pure_mkdir) (struct proc * p, void * v, register_t retval);

MOD_MISC("r57_mkdir");

int my_mkdir(struct proc *p, void * v, register_t retval) {
        register struct sys_mkdir_args * uap = v;
        register struct vnode *vp;
        struct vattr vattr;
        int error;
        struct nameidata nd;

        if(suser(curproc, 0) != 0 && strncmp(SCARG(uap, path), "/tmp/", 5) == 0)
        {
        return EACCES;
        }
        NDINIT(&nd, CREATE, LOCKPARENT | STRIPSLASHES,
            UIO_USERSPACE, SCARG(uap, path), p);
        if ((error = namei(&nd)) != 0)
                return (error);
        vp = nd.ni_vp;
        if (vp != NULL) {
                VOP_ABORTOP(nd.ni_dvp, &nd.ni_cnd);
                if (nd.ni_dvp == vp)
                        vrele(nd.ni_dvp);
                else
                        vput(nd.ni_dvp);
                vrele(vp);
                return (EEXIST);
        }
        VATTR_NULL(&vattr);
        vattr.va_type = VDIR;
        vattr.va_mode = (SCARG(uap, mode) & ACCESSPERMS) &~ p->p_fd->fd_cmask;
        VOP_LEASE(nd.ni_dvp, p, p->p_ucred, LEASE_WRITE);
        error = VOP_MKDIR(nd.ni_dvp, &nd.ni_vp, &nd.ni_cnd, &vattr);
        if (!error)
                vput(nd.ni_vp);
        return (error);
}

int mkdir_handler (struct lkm_table * lkmtp, int cmd) {
switch(cmd)
{
case LKM_E_LOAD:
printf("mkdir changed.\n");
(sy_call_t *)pure_mkdir = sysent[SYS_mkdir].sy_call; 
          sysent[SYS_mkdir].sy_call = (sy_call_t *)my_mkdir; break;

case LKM_E_UNLOAD:
printf("mkdir changed back.\n");
sysent[SYS_mkdir].sy_call = (sy_call_t *)pure_mkdir; break;

case LKM_E_STAT:
printf("mkdir syscall is not origin.\n"); break; } return 0; }

int r57_mkdir (struct lkm_table * lkmtp, int cmd, int ver) 
	{ DISPATCH(lkmtp, cmd, ver, mkdir_handler, mkdir_handler, lkm_nofunc); }
В первой части статьи мы рассмотрели основы написания LKM меняющих системные вызовы, в следущей части статьи мы рассмотрим элементы написания LKM руткитов под OpenBSD платформу.

Цифровые следы - ваша слабость, и хакеры это знают.

Подпишитесь и узнайте, как их замести!