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