Creating a forbidden information stream. 257 threads

Published on August 17, 2011

Creating a forbidden information stream. 257 threads

    This is the first article in a series of articles on creating prohibited information flows (IP). The ideas for organizing these IPs were not invented here and not by me. All I had to do was to implement for training purposes utilities that demonstrate these vulnerabilities.

    In this article I will talk about the organization of the information flow, based on the change by one of the applications of its state to the polling of the state of the first by another application. With this interaction model, a single transmitter may have multiple listeners. In subsequent articles, both program interaction environments and information exchange algorithms will differ.

    So, to the code! First, we need an array with the identifiers of an additional 256 threads. While threads are not created, identifiers are set to zero.

    #define MAX_THREADS 256
    pthread_t threads[MAX_THREADS];

    We will declare a couple of variables in which we will store information about the current number of threads and the required number of threads in the next step:

    int todayThreads    = 0;
    int tomorrowThreads = 0;

    Now look at the function to which all newly created threads will be directed:

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

    Now let's build a function that makes the number of running threads equal to the given one:

    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;
    }

    Now let's get into the main function. Suppose that when starting a program without a parameter, the program will be the transmitting side and will tell us its process identifier. And when launched with a parameter, it reads the process identifier of the transmitting program from the parameter and tries to receive data from it. In other words, first we run without parameters, we see the PID. We launch the second copy of the program with the parameter equal to the seen 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");
        }
    }

    Now let's look at the code for the runSender () transfer function . It prints the PID, after which it reads one character at a time from the standard input stream, changes the number of threads to the number equal to the code of the read character, waits for 1000 microseconds, makes the number of threads equal to zero and waits again.

    void runSender(int pid)
    {
        printf("Sender PID = %d\n", pid);
        while (true)
        {
            char ch = getchar();
            changeNumThreads(ch);
            usleep(1000);
            changeNumThreads(0);
            usleep(1000);
        }
    }

    It remains to see the host code. He is a little more complicated. It monitors the number of threads in a particular application and remembers the maximum number encountered. As soon as the number of threads becomes 0 (excluding the main thread) - it prints a character whose code is equal to the maximum value of threads seen. After printing, the stored maximum number of threads is reset.

    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);
        }
    }

    Thus, this is what happens:
    1. Launching a program without parameters
    2. We see a number on the screen, for example 1234
    3. Launch a program with parameter 1234
    4. In the first program, press the 'a' button
    5. The number of threads in process 1234 becomes 'a' + 1 = 98
    6. The receiving program observes an increase in the number of threads (this is written in the file / proc / 1234 / status)
    7. The receiving
    program fixes the maximum value equal to 98 8. The transmitting program kills all threads except the main one)
    9. The receiving program fixes the number of threads = 1 (main)
    10. The receiving program prints a character with code 98 on the screen -1 = 97 = 'a'.

    Findings:
    Thus, data is transferred from a process with a higher level of secrecy to a process with a lower level of secrecy. The ability to exchange is based on the ability of one process to monitor the state of another process without any restrictions on the level of secrecy.