SYSTEM] mprotect

노션으로 옮김·2020년 4월 13일
3

Study

목록 보기
17/33
post-thumbnail

개요

스택에 메모리 권한을 변경하여 쉘코드를 실행시키기 위해 mprotect를 사용하려 했으나 에러가 발생하여, 원인을 찾기위해 함수에 대해 자세히 알아보게 되었다.


정의

메모리의 접근권한을 변경시키는 함수이다.

다음과 같은 형태로 사용된다.

int mprotect(void *addr, size_t len, int prot);

반환

성공할 경우 0, 에러가 발생할 경우 -1을 반환한다.

매개변수

PROT_EXEC형태의 상수값을 사용하고, 다수의 상수값을 전달할 경우에 or 연산을 이용해 매개변수를 전달해야 한다.


주의할 점

manpage의 설명을 자세히 보면 다음과 같은 내용이 적혀있다.

mprotect() changes the access protections for the calling process's
memory pages containing any part of the address range in the interval
[addr, addr+len-1]. addr must be aligned to a page boundary.

  1. mprotect[주소값]부터 [주소값+길이-1]를 포함하는 메모리 페이지의 접근 권한을 수정하는 것이며
  2. 전달되는 매개변수 addr은 반드시 정렬된 페이지 boundary여야 한다

두 번째 사항을 무시할 경우, mprotect는 에러를 발생시킨다.

page boundary

페이지 경계란, 페이지 크기 단위로 나누어 떨어지는 주소값을 말한다.

시스템 아키텍쳐마다 다르지만, 일반적으로 본인 시스템에 맞는 메모리 페이지 크기를 구하는 방법은 다음과 같다.

pagesize = sysconf(_SC_PAGE_SIZE);
   if (pagesize == -1)
       handle_error("sysconf");

sysconf는 인자로 전달된 값의 지원 유무나, 상수값이라고 하면 그 값을 반환해준다.

http://man7.org/linux/man-pages/man3/sysconf.3.html
POSIX allows an application to test at compile or run time whether
certain options are supported, or what the value is of certain
configurable constants or limits.

구한 페이지 크기로 내가 원하는 주소의 page boundary를 구하기 위해서는 다음과 같은 방법을 사용한다.


extern int foo(void);

int
main (int argc, char *argv[])
{
  printf("Before modification: foo = %d @ %p\n", foo, (void *)&foo);

  size_t pagesize = sysconf(_SC_PAGESIZE);
  void *foo_page = (void *) (((uintptr_t)&foo) & ~(pagesize - 1));

  if (mprotect(foo_page, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC)) {
    perror("mprotect");
    return 1;
  }

foo 함수 주소 영역을 변경하기 위한 주소를 구할 때

void *foo_page = (void *) (((uintptr_t)&foo) & ~(pagesize - 1));

이런 연산을 수행하는데

pagesizepageboundary을 말하며 일반적으로 4096의 값을 가진다.

pageboundary가 4096일 때 정렬된 주소값은 0xaaaaa000, 0xbbbbb000처럼 마지막 24비트가 0인 형태를 띄게되므로

위와 같은 연산을 통해서 마지막 구하려는 대상 주소값의 마지막 24비트를 0으로 맞춰주어 정렬된 주소값을 구하는 것이다.

memalign

memalign은 정렬된 주소값을 반환하기 때문에

따로 계산해줄 필요가 없다.

void *memalign(size_t alignment, size_t size);

확인

http://man7.org/linux/man-pages/man2/mprotect.2.html 샘플 코드를 수정하여 확인하였다.

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>

#define handle_error(msg) \
   do { perror(msg); exit(EXIT_FAILURE); } while (0)

static char *buffer;

static void handler(int sig, siginfo_t *si, void *unused)
{
   /* Note: calling printf() from a signal handler is not safe
      (and should not be done in production programs), since
      printf() is not async-signal-safe; see signal-safety(7).
      Nevertheless, we use printf() here as a simple way of
      showing that the handler was called. */

   printf("Got SIGSEGV at address: 0x%lx\n",
           (long) si->si_addr);
   exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
   char *p;
   int pagesize;
   struct sigaction sa;

   sa.sa_flags = SA_SIGINFO;
   sigemptyset(&sa.sa_mask);
   sa.sa_sigaction = handler;
   if (sigaction(SIGSEGV, &sa, NULL) == -1)
       handle_error("sigaction");

   pagesize = sysconf(_SC_PAGE_SIZE);
   if (pagesize == -1)
       handle_error("sysconf");

   /* Allocate a buffer aligned on a page boundary;
      initial protection is PROT_READ | PROT_WRITE */

   buffer =(char*) memalign(pagesize, 4 * pagesize);
   char *buf =(char*) malloc(4 * pagesize);
   if (buffer == NULL)
       handle_error("memalign");

   printf("Start of buf:        0x%lx\n", (long) buf);
   printf("Start of region:        0x%lx\n", (long) buffer);

   char* addr_align = (char*)((unsigned long long)buf & ~(pagesize-1));
   printf("addr_align: %lx\n", addr_align);
   if (mprotect(addr_align, 4096, PROT_READ | PROT_EXEC | PROT_WRITE) == -1)
       handle_error("mprotect");

   for (p = buffer ; ; )
       *(p++) = 'a';

   printf("Loop completed\n");     /* Should never happen */
   exit(EXIT_SUCCESS);
}

원래는 memalign()으로 받아온 주소값을 전달하지만, 테스트를 위해 malloc()으로 할당받은 주소값을 사용하였다.

할당받은 주소값buf를 그대로 전달할 경우

mprotect: invalid arguments

에러가 발생한다.

하지만 위 코드처럼, 정렬된 메모리 주소값을 계산 후에 전달하면
mprotect는 성공할 것이다.


참조

https://kldp.org/node/107446

https://www.joinc.co.kr/w/man/2/sigaction

https://www.lazenca.net/pages/viewpage.action?pageId=19300589

0개의 댓글