Linux - Memory Mapping

김왕구·2023년 12월 4일
0

이번 블로그에서는 메모리 매핑과 보안에 대해 설명하고자 합니다.

mmap

메모리 매핑이란, 특정 파일(또는 특정 영역)을 가상 주소에 할당하는 작업을 뜻합니다.
메모리 매핑을 하는 이유는 다음과 같습니다.

  • 빈번한 I/O 접근으로 인한 성능 향상 필요성
  • 프로세스 간 통신(IPC)을 위한 방법이 필요한 경우

mmap 함수의 사용법은 다음과 같습니다.

  • void *addr : 매핑될 메모리 주소를 설정합니다.
    addr0으로 설정할 경우 Kernel에서 적절한 주소를 선택합니다.

  • size_t length : 할당할 메모리의 길이를 설정합니다.
    lengthsize_t 타입이므로 사용하는 시스템에 따라 크기가 달라질 수 있습니다.

  • int prot : 사용할 메모리 영역의 보호 모드를 설정합니다.
    지정할 수 있는 보호 모드는 다음과 같습니다.
    --> PROT_READ(1) : 메모리 영역에 읽기 권한을 부여합니다.
    --> PROT_WRITE(2) : 메모리 영역에 쓰기 권한을 부여합니다.
    --> PROT_NONE(3) : 메모리 영역에 접근할 수 없도록 설정합니다.
    --> PROT_EXEC(4) : 메모리 영역에 실행 권한을 부여합니다.

  • int flags : 메모리 매핑의 속성을 설정합니다.
    지정할 수 있는 속성은 다음과 같습니다.
    --> MAP_SHARED : 매핑된 메모리를 여러 프로세스가 공유할 수 있도록 설정합니다.
    --> MAP_PRIVATE : 매핑된 메모리를 현재 프로세스만 접근할 수 있도록 설정합니다.
    --> MAP_ANONYMOUS : 파일이 아닌 익명의 메모리를 매핑을 생성하도록 설정합니다.

  • int fd : 매핑될 파일의 fd를 설정합니다.
    익명의 메모리를 생성하고 싶은 경우 fd-1로 설정합니다.

  • off_t offset : 매핑된 메모리가 시작될 주소를 설정합니다. 타입이 off_t이므로 시스템에 따라 접근 가능한 메모리의 주소가 달라질 수 있습니다.

mmap에서 반환되는 값은 지정된 void *addr입니다.

메모리 권한 관리의 중요성

mmap을 통해 메모리와 파일 또는 익명의 메모리를 할당하는 것이 가능하다고 이전 섹션에서 설명하였습니다.
메모리를 할당하게 될 때, 보호 모드의 권한 중 PROT_EXEC 권한을 부여하는 것은 다소 위험할 수 있습니다. 만약 메모리에 실행 권한이 부여되었을 때 다른 공격 방법을 통해 악의적인 코드가 실행될 수 있습니다.

기타 함수

munmap

  • 매핑된 메모리를 해제하는 함수입니다.

mprotect

  • 매핑된 메모리의 보호 모드를 변경하는 함수입니다.

mremap

  • 매모리 매핑을 변경하는 함수입니다.

msync

  • 매핑된 메모리를 동기화(file <==> memory) 하는 함수입니다.

예제코드

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

#define MAP_ANONYMOUS 0x20

void init();
void show_file_info(char *filename, struct stat *fileinfo);
void menu();
void read_memory(void *addr, size_t len);
void write_memory(void *addr, size_t len);
void execute_memory(void *addr, size_t len);

int main(int argc, char **argv)
{
    size_t len;
    int select, size = 0, fd;
    void *buf = NULL;
    char *filename = argv[1];
    init();

    if (filename == NULL)
    {
        printf("Input memory size: ");
        scanf("%d", &size);
        len = (size + 4095) & ~4095;
        buf = mmap(
            0,
            len,
            PROT_NONE,
            MAP_PRIVATE | MAP_ANONYMOUS,
            -1,
            0);
        printf("Memory allocated at %p successfully!\n", buf);
    }
    else
    {
        if ((fd = open(filename, O_RDWR)) == -1)
            perror("open");
        struct stat fileinfo;
        if (fstat(fd, &fileinfo) == -1)
        {
            perror("fstat");
            exit(1);
        }
        len = fileinfo.st_size;
        buf = mmap(
            0,
            fileinfo.st_size,
            PROT_NONE,
            MAP_PRIVATE,
            fd,
            0);
        printf("Memory allocated at %p successfully!\n\n", buf);
        show_file_info(filename, &fileinfo);
    }
    while (1)
    {
        menu();
        scanf("%d", &select);
        switch (select)
        {
        case 1:
            read_memory(buf, len);
            break;

        case 2:
            write_memory(buf, len);
            break;

        case 3:
            execute_memory(buf, len);
            break;

            // case 4:
            //     msync(buf, len, O_SYNC);
            //     break;

        default:
            munmap(buf, len);
            exit(0);
        }
    }

    return 0;
}

void menu()
{
    printf("\nSelect Options\n");
    printf("1. Read Memory\n");
    printf("2. Write Memory\n");
    printf("3. Execute Memory\n");
    // printf("4. Sync File <==> Memory\n");
    printf("4. Exit\n");
    printf("Select: ");
}

void show_file_info(char *filename, struct stat *fileinfo)
{
    printf("=============================================\n");
    printf("FILE NAME: %s\n", filename);
    printf("FILE MODE: %d\n", fileinfo->st_mode);
    printf("FILE SIZE: %ld\n", fileinfo->st_size);
    printf("=============================================\n");
}

void init()
{
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
    setvbuf(stderr, 0, 2, 0);
}

void read_memory(void *addr, size_t len)
{
    if (mprotect(addr, len, PROT_READ) == -1)
        perror("mprotect");
    printf("DATA: ");
    fwrite(
        (char *)addr,
        sizeof(char),
        strlen((char *)addr),
        stdout);
    putchar('\n');
}

void write_memory(void *addr, size_t len)
{
    init();
    if (mprotect(addr, len, PROT_WRITE) == -1)
        perror("mprotect");
    printf("INPUT DATA: ");
    fgets((char *)addr, len, stdin);
}

void execute_memory(void *addr, size_t len)
{
    void (*code)(void);
    if (mprotect(addr, len, PROT_EXEC) == -1)
        perror("mprotect");

    printf("Execute Memory...");
    code = addr;
    code();
}
profile
시스템 보안과 운영체제 개발, Rust에 관심이 많은 학생

0개의 댓글