Linux - Memory Mapping

김왕구·2023년 12월 4일

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

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개의 댓글