Linux - Memory Mapped File - c

I am trying to implement the caesar cipher with mmap. I think the cipher works so fine but the problem is, the mmap. The idea of it is to update the file, if there was a change.
So far it isn't working. I can just read the memory mapped file and print it. But if I make any modificaiton, I get a segmentation fault. Unfortunately, I couldn't solve the problem myself. So, I would appreciate it, if you could help me with it.
Here is the code.
int main (int argc, char *argv[]) {
if(argc != 5)
fprintf(stdeer, "usage: ./cipher (encrypt|decrypt) <file name> (casar| vigenere) <key>\n");
// (encrypt / decrypt) can be found in argv[1]
// filename in argv[2]
// encryption method in argv[3]
// key in argv[4]
int fd = open(argv[2], O_RDWR, S_IWRITE | S_IREAD);
if (fd < 0)
hanle_error("open");
off_t len = lseek(fd, 0, SEEK_END);
if (len == (off_t)-1)
handle_error("lseek");
unsigned char* data = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // Add PROT_WRITE
if (data == MAP_FAILED)
handle_error("mmap");
char c = *argv[4];
int key = 0;
if(strcmp(argv[3], "caesar") == 0) {
key = c - 48;
if(strcmp(argv[1], "decrpyt") == 0)
key = -key;
int num = 0;
for(int size_t i = 0; i < (size_t)len; i++) {
if(data[i] >= 97 && data[i] <= 122) {
num = data[i];
num +=key;
if(num > 'z') {
num -= 26;
data[i] = num + '0';
} else if (num < 'a') {
num += 26;
data[i] = num + '0';
} else {
data[i] = num + '0';
}
} else {
continue;
}
}
}
return 0;
}
A possible input can be anything e.g.
SsWd asdas
qwmkfd aw.
The algorithm above should just modify the lower case letters and leave the rest as it is.
I hope someone might be able to help me.
Also, I only implemented the caesar cipher.
EDIT: The seg fault is gone after I added PROT_WRITE. But know I get weird question marks for the modified lower case letters. Does anyone know why?

If you want to write to the file too, then
unsigned char* data = mmap(0, len, PROT_READ, MAP_SHARED, fd, 0);
should be
unsigned char* data = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
That may not be the only issue, but would explain the seg fault.
The reason is self explanatory!

Related

Printing a number of lines using low level i/o in C

I am new to C and I'm trying to get familiar with low level I/O such as read() and write(), and I'm trying to print lines to standard out from a file using it, but I can't figure out how to do it while still only using low level functions.
Heres what I have so far, any suggestions would be super appreciated
I tried to iterate through the char array I have and checking for '\n' but it doesn't seem to be doing the trick for me.
EDIT: Sorry, here's the code to be copied
int lineCounter = 0;
char* filename = argv[3];
int fd = open(filename, O_RDONLY);
//off_t size = lseek(fd, 0, SEEK_END);
if (fd == -1) perror("open");
int n;
char buffer[BUFFSIZE];
while ((n = read(fd, buffer, 1)) > 0) {
write(STDOUT_FILENO, buffer, 1);
}
for (int i = 0; i < BUFFSIZE; i++) {
if (buffer[i] == '\n') {
lineCounter++;
}
}
corrections
int lineCounter = 0;
char* filename = argv[3];
int fd = open(filename, O_RDONLY);
//off_t size = lseek(fd, 0, SEEK_END);
if (fd == -1) perror("open");
int n;
char buffer[BUFFSIZE];
while ((n = read(fd, buffer, BUFFSIZE)) > 0) { <<<=== read uoto BUFFSIZE
write(STDOUT_FILENO, buffer, n); <<<<=== write out the same number of read bytes
for (int i = 0; i < n; i++) { <<<<=== put line counter inside loop to read file && loop over read number of chars
if (buffer[i] == '\n') {
lineCounter++;
}
}
}

Is there a way to make this code concise?

I'm currently learning and practicing c, but the exercise I'm doing wants each functions to have 25 lines limit (without changing { } or using single-line if statements!)
Please help if there's a way to make this even shorter.
void ft_write_file(void)
{
char c;
int fd;
int i;
i = 0;
if ((fd = open("write_exam", O_WRONLY | O_TRUNC | O_CREAT, 00777)) == -1)
{
ft_putstr("map error");
return ;
}
while (read(0, &c, 1))
{
write(fd, &c, 1);
if (c == '\n')
break ;
ft_allocate_g_var(i, c, 0);
i++;
}
int j = 0;
while (j < g_line)
{
while (read(0, &c, 1))
{
write(fd, &c, 1);
if (c == '\n')
break ;
}
j++;
}
close(fd);
}
To start with, don't try to do two things in one function. And try to write your functions with sensible arguments instead of hard-coding their subjects.
For example, your function is really doing two things:
Finding and potentially creating the output file (with a hard-coded name).
Copying the entire contents of one stream (hard-coded to stdin) to another stream.
So you could break that down: (prototypes only)
/* Returns fd or -1 on error */
int open_output(const char* name);
/* Returns number of bytes copied or -1 on error */
ssize_t copy_fd(int fd_dest, int fd_source);
Then your driver could be:
ssize_t copy_stdin_to_file(const char *name)
{
int fd = open_output(name);
if (fd < 0)
{
ft_putstr("Could not open output file");
return -1;
}
ssize_t copied = copy_fd(fd, 0);
if (copied < 0) {
ft_putstr("Could not write data.");
return copied;
}
}
A simple way would be to declare all the variables at the top in one line, for exemple :
char c; int fd; int i; i = 0;
Except from that I dont know, hope it can help a bit a least !

mmap function for arrays of data

Suppose that you have a binary file. It consists of double's. Its size is enough small to be put in the memory. How to read all of these numbers by using mmap function?
I have tried to dereference output pointer. But it is only the first element of data. To use loop it's non-trivial how to control the quantity of the elements of the array.
int main(int argc, char* argv[]) { // we get filename as an argument from the command line
if (argc != 2)
return 1;
int fd = open(argv[1], O_RDWR, 0777);
size_t size = lseek(fd, 0, SEEK_END);
double m = 0;
int cnt = 0; // counter of doubles
void* mp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (mp == MAP_FAILED)
return 1;
double* data = mp;
m += *data; // we want to count the sum of these doubles
++cnt;
int ump_res = munmap(mp, sizeof(double));
if (ump_res < sizeof(double))
return 1;
printf("%a\n", (m / (double)cnt)); // we output the average number of these doubles
close(fd);
return 0;
}
I expect that in stdout we will get the average of all doubles in the file, which name is give in argv[1] .
It is possible to cast void* to double* .Then you may iterate and handle elements:
void* mp = mmap(0, length, PROT_READ, MAP_PRIVATE, fd, 0);
if (mp == MAP_FAILED) {
close(fd);
return 1;
}
double* data = (double*)mp;
size_t cnt = length / sizeof(double);
for (size_t i = 0; i < cnt; ++i) {
m += data[i];
}
Hope you'll find it helpful.

ELF - Getting a SEGFAULT when changing the entry point

I'm trying to patch the entry point of an ELF file directly via the e_entry field:
Elf64_Ehdr *ehdr = NULL;
Elf64_Phdr *phdr = NULL;
Elf64_Shdr *shdr = NULL;
if (argc < 2)
{
printf("Usage: %s <executable>\n", argv[0]);
exit(EXIT_SUCCESS);
}
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
perror("open");
exit(EXIT_FAILURE);
}
if (fstat(fd, &st) < 0)
{
perror("fstat");
exit(EXIT_FAILURE);
}
/* map whole executable into memory */
mapped_file = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mapped_file < 0)
{
perror("mmap");
exit(EXIT_FAILURE);
}
// check for an ELF file
check_elf(mapped_file, argv);
ehdr = (Elf64_Ehdr *) mapped_file;
phdr = (Elf64_Phdr *) &mapped_file[ehdr->e_phoff];
shdr = (Elf64_Shdr *) &mapped_file[ehdr->e_shoff];
mprotect((void *)((uintptr_t)&ehdr->e_entry & ~(uintptr_t)4095), 4096, PROT_READ | PROT_WRITE);
if (ehdr->e_type != ET_EXEC)
{
fprintf(stderr, "%s is not an ELF executable.\n", argv[1]);
exit(EXIT_FAILURE);
}
printf("Program entry point: %08x\n", ehdr->e_entry);
int text_found = 0;
uint64_t test_addr;
uint64_t text_end;
size_t test_len = strlen(shellcode);
int text_idx;
for (i = 0; i < ehdr->e_phnum; ++i)
{
if (text_found)
{
phdr[i].p_offset += PAGE_SIZE;
continue;
}
if (phdr[i].p_type == PT_LOAD && phdr[i].p_flags == ( PF_R | PF_X))
{
test_addr = phdr[i].p_vaddr + phdr[i].p_filesz;
text_end = phdr[i].p_vaddr + phdr[i].p_filesz;
printf("TEXT SEGMENT ends at 0x%x\n", text_end);
puts("Changing entry point...");
ehdr->e_entry = (Elf64_Addr *) test_addr;
memmove(test_addr, shellcode, test_len);
phdr[i].p_filesz += test_len;
phdr[i].p_memsz += test_len;
text_found++;
}
}
//patch sections
for (i = 0; i < ehdr->e_shnum; ++i)
{
if (shdr->sh_offset >= test_addr)
shdr->sh_offset += PAGE_SIZE;
else
if (shdr->sh_size + shdr->sh_addr == test_addr)
shdr->sh_size += test_len;
}
ehdr->e_shoff += PAGE_SIZE;
close(fd);
}
The shellcode in this case is just a bunch of NOPs with an int3 instruction at the end.
I made sure to adjust the segments and sections that come after this new code, but the problem is that as soon as I patch the entry point the program crashes, why is that?
changing:
memmove(test_addr, shellcode, test_len);
to:
memmove(mapped_file + phdr[i].p_offset + phdr[i].p_filesz, shellcode, test_len);
Seems to fix your problem. test_addr is a virtual address belong to the file you have mapped; you cannot use that directly as a pointer. The bits you want to muck with are the file map address, p_offset and p_filesz.
I suspect that you haven't enable write-access to program's header. You can do this via something like
const uintptr_t page_size = 4096;
mprotect((void *)((uintptr_t)&ehdr->e_entry & ~(uintptr_t)4095), 4096, PROT_READ | PROT_WRITE);
ehdr->e_entry = test_addr;

read integer from file with read() c

i have a problem with file read() function. My file is like this:
4boat
5tiger
3end
Where the number is the length of the string that follows. I need to read integer and string from input file and print them out on stdoutput, using low level I/O. This is my code:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main(int argc, char *argv[]){
int *len, fd, r_l, r_s;
char *s;
fd=open(argv[1], O_RDONLY);
if(fd>=0){
do{
r_l=read(fd, len, sizeof(int));
r_s=read(fd, s, (*len)*sizeof(char));
if(r_l>=0){
write(1, len, sizeof(int));
write(1, " ",sizeof(char));
}
if(r_s>=0)
write(1, s, (*len)*sizeof(char));
}while(r_l>=0 && r_s>=0);
}
return 0;
}
But it not works =/
You did not allocate space for the poitner len, you need to allocate space for it and you can simply do it by declaring it as int len; so it gets allocated in the stack and you don't need to handle it's allocation manually, so it would be something like this
int main(void) {
int len, fd, r_l, r_s;
char *s;
fd = open(argv[1], O_RDONLY);
if (fd >= 0) {
do {
r_l = read(fd, &len, sizeof(int));
s = malloc(len); /* <--- allocate space for `s' */
r_s = 0;
if (s != NULL)
r_s = read(fd, s, len);
if (r_l >= 0) {
write(1, &len, sizeof(int));
write(1, " ", 1);
}
if ((r_s >= 0) && (s != NULL))
write(1, s, len);
free(s);
} while (r_l >= 0 && r_s >= 0);
close(fd);
}
return 0;
}
you also didn't allocate space for s which is another problem, I did allocate space for s in the corrected code above by using malloc().
And sizeof(char) == 1 by definition, so you don't need that.
Although, the code above will not have the errors your code has, which invoke undefined behavior, it will not do what you expect, because your data cannot be read with this algorithm.
The numbers in your file are not really integers, they are characters, so what you really need is this
int main(void) {
char chr;
int len, fd, r_l, r_s;
char *s;
fd = open(argv[1], O_RDONLY);
if (fd >= 0) {
do {
r_l = read(fd, &chr, 1);
len = chr - '0';
s = malloc(len); /* <--- allocate space for `s' */
r_s = 0;
if (s != NULL)
r_s = read(fd, s, len);
if (r_l >= 0) {
printf("%d ", len);
}
if ((r_s >= 0) && (s != NULL))
write(1, s, len);
free(s);
} while (r_l >= 0 && r_s >= 0);
close(fd);
}
return 0;
}

Resources