Как стать автором
Обновить

Создание запрещенного информационного потока. 257 тредов

Время на прочтение3 мин
Количество просмотров895
Это первая статья из серии статей о создании запрещённых информационных потоков (ИП). Идеи организации этих ИП придуманы не здесь и не мной. Мне довелось лишь реализовывать в учебных целях утилиты, демонстрирующие эти уязвимости.

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

Итак, к коду! Для начала нам потребуется массив с идентификаторами дополнительных 256 тредов. Пока треды не созданы идентификаторы делаем равными нулю.

#define MAX_THREADS 256
pthread_t threads[MAX_THREADS];

Объявим пару переменных, в которых будем хранить информацию о текущем числе тредов и о необходимом числе тредов на следующем шаге:

int todayThreads    = 0;
int tomorrowThreads = 0;

Теперь посмотри на функцию, в которую будут направляться все свежесозданные треды:

void * funcParallel(void * p)
{
    long threadIndex = (long)p;
    while (true)
    {
        usleep(200);
        if (threadIndex + 1 > tomorrowThreads)
        {
            pthread_exit(NULL);
            return NULL;
        }
    }
}

Теперь соорудим функцию делающую число запущенных тредов равным заданному:

void changeNumThreads(int num)
{
    tomorrowThreads = num;
    
    if (tomorrowThreads > todayThreads)
    {
        // Creating new threads
        for (int i = todayThreads; i < tomorrowThreads; i++)
            pthread_create(threads+i, NULL, funcParallel, (void*)i);
    }
    else if (tomorrowThreads < todayThreads)
    {
        void * status;
        // Waiting for killing threads
        pthread_join(threads[tomorrowThreads], &status);
    }
    
    todayThreads = tomorrowThreads;
}

Теперь займёмся функцией main. Пусть при запуске программы без параметра — программа будет передающей стороной и сообщит нам свой идентификатор процесса. А при запуске с параметром, считывает из параметра идентификатор процесса передающей программы и пытается принимать от неё данные. Иными словами сперва запускаем без параметров, видим PID. Вторую копию программы запускаем с параметром равным увиденному PID-у.

int main(int argc, char * argv[])
{
    int pid = getpid();
    
    if (argc == 1)
    {
        runSender(pid);
    }
    else if (argc == 2)
    {
        sscanf(argv[1], "%d", &pid);
        runReceiver(pid);
    }
    else
    {
        printf("Usage:    unichat [PID]\n");
        printf("Examples: unichat\n");
        printf("          unichat 2790\n");
    }
}

Теперь посмотрим на код передающей функции runSender(). Она печатает PID, после этого считывая по одному символу со стандартного потока ввода, меняет число тредов до числа равного коду прочитанного символа, ждёт 1000 микросекунд, делает число тредов равным нулю и снова пережидает.

void runSender(int pid)
{
    printf("Sender PID = %d\n", pid);

    while (true)
    {
        char ch = getchar();
        changeNumThreads(ch);
        usleep(1000);
        changeNumThreads(0);
        usleep(1000);
    }
}

Осталось посмотреть код принимающей стороны. Он несколько сложнее. Он следит за количеством тредов в конкретном приложении и запоминает максимально встретившееся количество. Как только число тредов становится 0 (без учета главного треда) — он печатает символ, код которого равен максимально увиденному значению тредов. После печати запомненное максимальное число тредов сбрасывается.

void runReceiver(int pid)
{
    printf("Receive from PID = %d\n", pid);
    
    char line[1024];
    char filename[1024];
    snprintf(filename, sizeof(filename), "/proc/%d/status", pid);
    
    int maxThreads = 0;
    int threads = 0;
    
    while (true)
    {
        usleep(200);
        FILE * f = fopen(filename, "rt");
        if (f == NULL)
        {
            printf("Can't open file %s, error: %d!\n", filename, errno);
            return;
        }
    
        while (!feof(f))
        {
            fscanf(f, "%s", line);
            if (feof(f)) break;

            if (memcmp(line, "Threads:", 8) == 0)
            {
                fscanf(f, "%d", &threads);
                threads--;
                
                if ((threads == 0) && (maxThreads != 0))
                {
                    printf("%c", maxThreads);
                    fflush(stdout);
                    maxThreads = 0;
                }
                else
                {
                    if (threads > maxThreads)
                        maxThreads = threads;
                }
                
                break;
            }
        }
        fclose(f);
    }
}

Таким образом вот что происходит:
1. Запуск программы без параметров
2. Видим на экране число, например 1234
3. Запускаем программу с параметром 1234
4. В первой программе нажимаем кнопку 'a'
5. Число тредов в процессе 1234 становится 'a'+1 = 98
6. Принимающая программа наблюдает рост числа тредов (это записано в файле /proc/1234/status)
7. Принимающая фиксирует максимальное значение, равное 98
8. Передающая программа убивает все треды, кроме главного)
9. Принимающая программа фиксирует число тредов =1 (главный)
10. Принимающая программа печатает на экран символ с кодом 98-1 = 97 = 'a'.

Выводы:
Таким образом происходит передача данных от процесса с большим уровнем секретности к процессу с меньшим уровнем секретности. Возможность обмена основана на возможности отслеживания одним процессом состояния другого процесса без каких-либо ограничений по уровню секретности.
Теги:
Хабы:
+1
Комментарии16

Публикации

Изменить настройки темы

Истории

Ближайшие события