Variables

Size of variables

#include <stdio.h>

struct foo{
    char c1;
    short int c2;
};

int main(void){
    printf("Size char: %ld\n", sizeof(char));
    printf("Size short int: %ld\n", sizeof(short int));
    printf("Size of the struct: %ld\n", sizeof(struct foo));
    return 0;
}

String

Loop a string

$ cat main.c
#include <stdio.h>

int main(void){
    char b[128] = "Hello world";
    char *p;
    p = b;

    while (*p != '\0'){
        printf("%c", *p);
        *p++;
    }
    /*printf("%c", *p);
    printf("%c", *(++p));
    printf("%c", *(++p));
    printf("%c", *(++p));*/

    return 0;
}
$ gcc main.c -o main && ./main
Hello world

Arguments

Manipulate arguments in function

$ cat test-va.c
#include <stdio.h>
#include <stdarg.h>

// Important: size need to be the last argument
void test_va(int a, int size, ...) {
    printf("Size: %d\n", size);
    printf("a: %d\n", a);
    va_list v;
    va_start(v, size);
    for (int i = 0; i < size; i++){
        int x = va_arg(v, int);
        printf("Args %d: %d\n", i, x);
    }
    va_end(v);
}
int main(void){
    test_va(0, 3, 11, 12, 13);
    return 0;
}
$ gcc -o test-va test-va.c && ./test-va
Size: 3
a: 0
Args 0: 11
Args 1: 12
Args 2: 13

Pointers

Increment pointer

#include <stdio.h>

int inc(int *p){
    // First way
    int a = *p;
    a++;
    *p = a;
    // Second way
    *p += 1;
}

int main(void){
    int a = 5;
    inc(&a);
    printf("%d\n", a);
    return 0;
}

void *ptr

Void *ptr means no data is associated, we can just cast the pointer to the correct data. It’s very used for dynamic allocation like malloc.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

struct book{
    int id;
    char author[64];
    char title[64];
};
struct book* initialize_book(void *ptr){
    struct book *book = (struct book*)(ptr);
    book->id = 0;
    memset(book->author, 0, 64);
    memset(book->title, 0, 64);
    return book;
}
int main(int argc, char *argv[]){
    struct book *book = (struct book*)malloc(sizeof(struct book));

    if (book == NULL){
        printf("Failed to init the struct book\n");
        return -1;
    }

    initialize_book(book);

    book->id = 1;
    strcpy(book->author, "Victor Hugo");
    strcpy(book->title, "Les trois mousquetaires");

    printf("Author: %s\n", book->author);
    printf("Title: %s\n", book->title);

    free(book);
    return 0;
}

Double pointer

$ cat double_pointer.c
#include <stdio.h>

int main(void){
    int a = 25;
    int *b = &a;
    int **c = &b;

    printf("%d %d\n", a, &a);
    printf("%d %d\n", b, &b);
    printf("%d %d\n", c, &c);
    printf("Value of a: %d\n", a);
    printf("Value of b: %d\n", *b);
    printf("Value of c: %d\n", **c);
}
$ gcc double_pointer.c -o double_pointer && ./double_pointer
25 580638908
580638908 580638896
580638896 580638888
Value of a: 25
Value of b: 25
Value of c: 25

With char:

$ cat double_pointer.c
#include <stdio.h>

int main(void){
    char *foo = "Hello";
    char **ptr1 = &foo;

    printf("%s %d\n", foo, &foo[0]);
    printf("%d %d\n", ptr1, &ptr1);
    printf("Value of foo: %s\n", foo);
    printf("Value of ptr1: %s\n", *ptr1);
}
$ gcc double_pointer.c -o double_pointer && ./double_pointer
Hello -490110972
1792915400 1792915392
Value of foo: Hello
Value of ptr1: Hello

Malloc from other function

$ cat test_malloc.c
#include <stdio.h>
#include <stdlib.h>

int foo(char **ptr){
    *ptr = malloc(sizeof(char) * 5);

    if (*ptr == NULL){
        *ptr = NULL;
        printf("Failed malloc\n");
        return -1;
    }

    return 0;
}
int main(void){
    char *c = NULL;
    int err;

    err = foo(&c);
    if (err == -1){
        printf("NULL\n");
        return -1;
    }

    for (int i = 0; i < 5; i++){
        c[i] = 'A' + i;
        printf("%c\n", c[i]);
    }
    free(c);
}
$ gcc test_malloc.c -o test_malloc && ./test_malloc
A
B
C
D
E

Or the second way:

$ cat test_malloc.c
#include <stdio.h>
#include <stdlib.h>

char *foo(){
    char *ptr = malloc(sizeof(char) * 5);

    if (ptr == NULL){
        ptr = NULL;
        printf("Failed malloc\n");
        return NULL;
    }
    for (int i = 0; i < 5; i++)
        ptr[i] = 'A' + i;
    return ptr;
}
int main(void){
    char *c = NULL;

    c = foo();
    if (c == NULL){
        printf("NULL\n");
        return -1;
    }

    for (int i = 0; i < 5; i++)
        printf("%c\n", c[i]);
    free(c);
    return 0;
}

Array of char *

$ cat double_pointer.c
#include <stdio.h>

int main(void){
    char *s1 = "Hello";
    char *s2 = "World";
    char *s[5];
    s[0] = s1;
    s[1] = s2;
    printf("%s\n", s[0]);
    printf("%s\n", *s);
    printf("%s\n", s[1]);
}
$ gcc double_pointer.c -o double_pointer && ./double_pointer
Hello
Hello
World

Processes

Create a thread

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>

struct book{
    int id;
    char title[64];
    char author[64];
};
void* thread(void *data) {
    struct book *book = (struct book*)data;
    printf("Id: %d\n", book->id);
    printf("Title: %s\n", book->title);
    printf("Author: %s\n", book->author);

    return NULL;
}

int main(void){
    pthread_t t_thread;
    struct book book1;
    struct book book2;
    book1.id = 0;
    strcpy(book1.title, "Les trois mousquetaires");
    strcpy(book1.author,"Dumas");
    book2.id = 1;
    strcpy(book2.title, "Notre Dame de Paris");
    strcpy(book2.title, "Victor Hugo");

    pthread_create(&t_thread, NULL, thread, (void*)&book1);
    pthread_create(&t_thread, NULL, thread, (void*)&book2);

    pthread_join(t_thread, NULL);

    return 0;
}
gcc main.c -lpthread -o main 

Create a fork

$ cat fork.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void){
    pid_t pid = 0;

    // Create our fork
    pid = fork();
    if (pid == -1){
        printf("Failed to create the fork\n");
        perror("fork()");
    }

    if (pid == 0){
        printf("Pid son\n");
    }
    else{
        printf("Pid father\n");
    }

    return 0;
}

Communication inter-process

pipe

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void writestr(int fd, const char *str) {
    write(fd, str, strlen(str));
}

int main(void)
{
    int pipefd[2];
    pid_t   pid;
    char    buf;

    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid == -1){
        perror("fork");
        exit(EXIT_FAILURE);
    }
    else if (pid == 0) {
        close(pipefd[1]);
        writestr(STDOUT_FILENO, "Read pipe from process son\n");

        while (read(pipefd[0], &buf, 1) > 0) {
            write(STDOUT_FILENO, &buf, 1);
        }
        writestr(STDOUT_FILENO, "End read pipe form son process\n");
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    }
    else {
        close(pipefd[0]);
        writestr(STDOUT_FILENO, "Write to pipe\n");
        writestr(pipefd[1], "\e[33mData\e[0m\n");
        close(pipefd[1]);
        wait(NULL);
        writestr(STDOUT_FILENO, "End to write to the pipe\n");
        exit(EXIT_SUCCESS);
    }
}

mkfifo

mkfifo (make FIFO) is a named tube with a file which create a communication channel.

$ cat main.c
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(void) {
    int fd;
    char *myfifo = "./myfifo";

    mkfifo(myfifo, 0666);

    while (1) {
        char src[10];
        char dest[10];
        snprintf(src, 6, "Hello");
        printf("Write to fifo: %s\n", src);
        // Open FIFO for write only
        fd = open(myfifo, O_WRONLY);
        write(fd, src, strlen(src));

        close(fd);
        // Read the FIFO
        fd = open(myfifo, O_RDONLY);
        read(fd, dest, 6);
        printf("Received: %s\n", dest);

        close(fd);
    }
    return 0;
}

And you can compile it:

$ gcc main.c -o main && ./main

mkfifo with python

$ cat main.c
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(void) {
    int fd;
    char *myfifo = "./myfifo";

    mkfifo(myfifo, 0666);

    fd = open(myfifo, O_WRONLY);
    while (1) {
        char buf[80];
        snprintf(buf, 80, "execve:%d;%s", 25, "./malicious");

        write(fd, buf, strlen(buf));
        sleep(1);
    }
    close(fd);
    return 0;
}

And we can read it with python:

$ cat main.py
import os

path = "./myfifo"

while True:
    fifo = os.open(path, os.O_RDONLY)
    print(os.read(fifo, 100).decode('utf-8'))

And we can compile it:

$ gcc main.c -o main && ./main

And we read with python:

$ python3 main.py
execve:25;./malicious
execve:25;./malicious

Reading and writing to file

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

static void just_read(int fd);
static void read_and_copy(int fd_r);
int main(void){
    int fd;
    int len;
    char buf[BUFSIZ];

    if ((fd = open("main.c", O_RDONLY)) < 0){
        printf("Failed to open the file\n");
        perror("open()");
        exit(-1);
    }

    while ((len = read(fd, buf, BUFSIZ)) > 0){
        //printf("%s\n", buf);
        //memset(buf, 0, BUFSIZ);
    }

    just_read(fd);
    read_and_copy(fd);

    close(fd);

    return 0;
}
static void just_read(int fd){
    char buf[BUFSIZ];
    int len;

    // Go to the up of the file
    lseek(fd, 0, SEEK_SET);
    while ((len = read(fd, buf, BUFSIZ)) > 0){
        //printf("%s\n", buf);
        memset(buf, 0, BUFSIZ);
    }
}
static void read_and_copy(int fd_r){
    int fd_w;
    char buf[BUFSIZ];
    int len;
    int done = 0;
    int bytes;
    struct stat stat;

    // And copy to a new file
    if ((fd_w = open("backup.c", O_CREAT | O_TRUNC | O_RDWR, 00760)) < 0){
        printf("Failed to open the file\n");
        perror("open()");
        exit(-1);
    }

    // Get stat of the file
    fstat(fd_r, &stat);
    len = stat.st_size;

    memset(buf, 0, BUFSIZ);

    // Go to the begin of the file
    lseek(fd_r, 0, SEEK_SET);
    while (done < len){
        bytes = read(fd_r, buf, BUFSIZ);
        printf("%s\n", buf);
        write(fd_w, buf, bytes);
        done += bytes;
        memset(buf, 0, BUFSIZ);
        //printf("%d %d\n", bytes, done);
    }
    close(fd_w);
}

Read a file with mmap

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main(void){
    struct stat st_stat;
    int fd = open("foo.txt", O_RDONLY);

    stat("foo.txt", &st_stat);

    printf("%ld\n", st_stat.st_size);

    char *buf = mmap(NULL, st_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    for (int i = 0; i < st_stat.st_size; i++)
        printf("%c", buf[i]);

    munmap(buf, st_stat.st_size);
    close(fd);
    return 0;
}

Binary operation

Bitwise operator

// 25 -> 11001
// 20 -> 10100
// 11001 & 10100 = 10000 = 16
printf("%d\n", (25 & 20)); // AND logical
// output -> 16
// 11001 | 10100 = 11101 = 29
printf("%d\n", (25 | 20)); // OR logical
// output -> 29

Shift operator - right shift

// Right shift >> shift bit from left to right.
// The number of bits added is defined by the number on the right shift
// For instance, the number 25 (11001), we add 2 bits on the right
printf("%d\n", (25 << 2));
// output 100 -> 1100100. We added two bits on the right

Shift operator - left shift

// Left shift << shift bit from right to left.
// The number of bits added is defined by the number on the left shift
// For instance, the number 25 (11001), we add 2 bits
printf("%d\n", (25 >> 2));
// output 6 -> 00110. We added two bits on the left

XOR

    p = 4242;
    p ^= p << 3; // XOR
    //    1000010010010 -> 4242
    // 1000010010010000 -> 33936 -> left shift 3
    // 1001010000000010 -> 37890 -> XOR operation

Convert int to bytes and bytes to int

#include <stdio.h>
#include <stdlib.h>

int main(void){
    int test = 2500;
    unsigned char bytes[4];

      // Convert int to bytes
    printf("%d\n", test);
    bytes[0] = test & 0xFF;
    bytes[1] = (test >> 8) & 0xFF;
    bytes[2] = (test >> 16) & 0xFF;
    bytes[3] = (test >> 24) & 0xFF;
    printf("%d %d %d %d\n", bytes[0], bytes[1], bytes[2], bytes[3]);

        // Convert bytes to int
    test = (bytes[0]) + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24);
    printf("%d\n", test);

    // Output
    // 2500
    // 196 9 0 0
    //  2500

    return 0;
}

CPU time

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#define MAX_ENTRIES_ENTROPY 100

void entropy_cpu_clock(double *entropy, int pos, int max);
int main(void){
    int pos = 0;
    double *pool_entropy = (double *)malloc(sizeof(double) * MAX_ENTRIES_ENTROPY);

    if (pool_entropy == NULL){
         printf("Failed to allocate variables\n");
         exit(-1);
    }

    entropy_cpu_clock(pool_entropy, 0,  MAX_ENTRIES_ENTROPY);
    pos++;

    for(int i = 0; i < MAX_ENTRIES_ENTROPY; i++){
        printf("%f\n", pool_entropy[i]);
    }

    free(pool_entropy);
    return 0;
}
void entropy_cpu_clock(double *entropy, int pos, int max){
   clock_t start_t, end_t;
   double total_t;
   double res = 0;

   start_t = clock();
   //printf("Starting of the program, start_t = %ld\n", start_t);

   //printf("Going to scan a big loop, start_t = %ld\n", start_t);
   for(int i = 0; i < 10000000; i++) {
   }
   end_t = clock();
   //printf("End of the big loop, end_t = %ld\n", end_t);

   total_t = (double)(end_t - start_t) / CLOCKS_PER_SEC;
   //printf("Total time taken by CPU: %f\n", total_t  );
   //printf("Exiting of the program...\n");

   entropy[pos] = total_t;
}

File Descriptor

Duplication

$ cat test_dup.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main(void){
    int f;
    int f2;
    char buffer[5];

    if ((f = open("test.txt", O_RDONLY)) < 0)
        return -1;

    read(f, buffer, 5);
    printf("%s", buffer);
    memset(buffer, 0, 5);

    f2 = dup(f);
    read(f2, buffer, 5);
    printf("%s", buffer);
    close(f);

    return 0;
}
$ gcc test_dup.c -o test_dup && ./test_dup
toto
titi

Structure

Function inside structure

$ cat test_struct.c
#include <stdio.h>
#include <stdlib.h>

struct test{
    void (*foo)(int a);
};

void foo(int a){
    printf("%d\n", a);
}

int main(void){
    struct test *s_test = malloc(sizeof(struct test));
    s_test->foo = foo;
    s_test->foo(25);
    free(s_test);
    return 0;
}
$ gcc -o test_struct test_struct.c && ./test_struct
25

Without a malloc of the structure:

$ cat main.c
#include <stdio.h>
#include <unistd.h>

struct foo {
    pid_t pid;
    void (*r)(struct foo);
};

void *readPid(struct foo f){
    printf("pid: %d\n", f.pid);
}

int main(void){
    struct foo f;
    f.pid = getpid();
    f.r = readPid(f);
    f.r;
    return 0;
};
$ gcc -o main main.c && ./main
pid: 18768

Library

Create our own library

https://diveintosystems.org/book/C2-C_depth/advanced_writing_libraries.html

Clock

We can identify two clocks types under Linux: realtime and monotonic. The first one represent the time since a based on a fixed source, since the Epoch, the 1st january 1970. The second time is based on a unspecified time in the past. In Linux, it's the uptime of the system. The C code below show to us the difference between realtime and monotonic times:

$ cat clock.c
#include <stdio.h>
#include <time.h>

int main() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);

    printf("Monotonic: %ld seconds, %ld nanoseconds\n", ts.tv_sec, ts.tv_nsec);
    clock_gettime(CLOCK_REALTIME, &ts);
    printf("Realtime: %ld seconds, %ld nanoseconds\n", ts.tv_sec, ts.tv_nsec);

    return 0;
}
$ gcc -o clock clock.c && ./clock
Monotonic: 37642 seconds, 189065959 nanoseconds
Realtime: 1749913995 seconds, 857867778 nanoseconds