이번 블로그에서는 메모리 매핑과 보안에 대해 설명하고자 합니다.
메모리 매핑이란, 특정 파일(또는 특정 영역)을 가상 주소에 할당하는 작업을 뜻합니다.
메모리 매핑을 하는 이유는 다음과 같습니다.
mmap
함수의 사용법은 다음과 같습니다.
void *addr
: 매핑될 메모리 주소를 설정합니다.
addr을 0으로 설정할 경우 Kernel에서 적절한 주소를 선택합니다.
size_t length
: 할당할 메모리의 길이를 설정합니다.
length는 size_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
권한을 부여하는 것은 다소 위험할 수 있습니다. 만약 메모리에 실행 권한이 부여되었을 때 다른 공격 방법을 통해 악의적인 코드가 실행될 수 있습니다.
#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();
}