본문 바로가기

공부/보안

[dreamhack] System Exploitation 2강

반응형

2강 Memory Corruption - C (1)

2.1 버퍼오버플로우란?

버퍼 : 지정된 크기의 메모리 공간

 

버퍼오버플로우 : 버퍼가 허용할 수 있는 양의 데이터보다 더 많은 값이 저장돼 버퍼가 넘치는 취약점

 

분류(발생위치)

- 스택 버퍼오버플로우 : 지역 변수가 할당되는 스택 메모리에서 오버플로우가 발생

ex) 8 바이트의 버퍼 A, B가 메모리에 순차적으로 할당되었다. 만약에 버퍼 A에 16바이트 데이터를 복사하면 데이터는 버퍼 A를 넘어 뒤의 데이터 영역인 B에 쓰여진다.

이는 프로그램의 정의되지 않은 동작(Undefined Behavior)을 이끌어낸다.

 

이를 악용한다면 기계어 코드를 삽입한 후 함수 포인터를 공격자의 코드 주소로 덮어 코드를 실행할 수 있다.

 

스택 오버플로우 예제 1)

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    char buf[16];
    gets(buf);
    
    printf("%s", buf);
}
cs

16바이트 buf 변수를 스택에 할당한 후, gets 함수로 사용자로부터 데이터를 입력받아 이를 그대로 출력하는 코드

gets 함수는 사용자가 개행을 입력하기 전까지 입력한 모든 내용을 첫 번째 인자로 전달된 버퍼에 저장하는 함수이다. 그러나 gets 함수는 별도 입력 길이 제한이 없어 16 바이트 넘는 데이터를 입력하면 스택 버퍼 오버플로우가 발생한다.

 

스택 오버플로우 예제 2)

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
// 스택 오버플로우 예제 2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int check_auth(char *password) {
    int auth = 0;
    char temp[16];
    
    strncpy(temp, password, strlen(password));
    
    if(!strcmp(temp, "SECRET_PASSWORD"))
        auth = 1;
    
    return auth;
}
int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: ./stack-1 ADMIN_PASSWORD\n");
        exit(-1);
    }
    
    if (check_auth(argv[1]))
        printf("Hello Admin!\n");
    else
        printf("Access Denied!\n");
}
cs

main 함수argv[1]check_argc 함수의 인자로 전달한 후 리턴 값이 0이 아니라면 "Hello Admin!"을, 맞다면 "Access Denied!" 문자열을 출력한다.

 

check_auth 함수는 16바이트 크기의 temp 버퍼에 입력받은 패스워드를 복사한 후, "SECRET_PASSWORD" 문자열을 비교한다. 문자열이 같으면 auth를 1로 하고, 리턴한다.

 

그러나 strncpy 함수를 통해 temp 버퍼를 복사할 때, temp의 크기인 16 바이트가 아닌 전달된 password 문자열의 길이만큼 복사한다. 따라서 argv[1]에 16바이트가 넘는 문자열을 전달한다면 길이 제한 없이 문자열이 복사돼 스택 버퍼 오버플로우가 발생한다.

 

temp 버퍼 뒤 auth 값이 존재하여 오버플로우가 발생해 공격자의 데이터가 auth 값을 바꾼다면 auth가 0이 아닌 다른 값이 된다.

 

→ 실제 인증 여부와는 상관없이 if(check_auth(argv[1])) 문은 항상 참을 반환한다.

1
2
3
    char buf[32] = {0, };
    read(0, buf, 31);
    sprintf(buf, "Your Input is: %s\n", buf);
cs

위 코드는 32바이트 크기 buf를 초기화한 후 데이터를 31바이트 입력받고, sprintf() 함수를 통해 출력할 문자열을 저장한 뒤 출력한다.

 

read() 함수에서 받는 입력이 32바이트를 넘지 않지만, sprintf() 함수를 통해 버퍼에 값을 쓸 때 "Your Input is: "문자열을 추가해줄 때 만일 buf에 31바이트를 다 채운다면 "Your Input is: "문자열 앞에 붙어 총 길이가 32바이트를 넘는다.

 

- 힙 버퍼오버플로우

 

힙 오버플로우 예제 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// heap-1.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    char *input = malloc(40);
    char *hello = malloc(40);
    
    memset(input, 040);
    memset(hello, 040);
    
    strcpy(hello, "HI!");
    read(0, input, 100);
    
    printf("Input: %s\n", input);
    printf("hello: %s\n", hello);
}
cs

크기가 40바이트의 힙 버퍼 input과 hello 을 할당한 후, hello에는 "HI!" 문자열을 복사하고 read 함수를 통해 input 에 데이터를 입력받는다. 그러나 read() 함수를 통해 입력받는 길이 100바이트가 input 버퍼의 크기인 40바이트보다 크기에 힙 오버플로우가 발생한다.

 

힙 오버플로우가 발생해 input 영역에서 버퍼 오버플로우가 발생해 hello의 메모리 영역까지 침범할 경우, hello 메모리를 출력할 때 "HI!" 문자열이 아닌 오염된 데이터를 출력한다.

 

 

 

2.2 Out-of_boundary

Out-Of-Boundary(OOB) : 버퍼의 길이 범위를 벗어나는 인덱스에 접근할 때 발생하는 취약점

 

Out-Of-Boundary 예제 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main(void) {
    int win;
    int idx;
    int buf[10];
    
    printf("Which index? ");
    scanf("%d"&idx);
    printf("Value: ");
    scanf("%d"&buf[idx]);
    printf("idx: %d, value: %d\n", idx, buf[idx]);
    if(win == 31337){
        printf("Theori{-----------redeacted---------}");
    }
}
cs

위 코드는 10바이트의 크기인 int형 배열 buf를 선언하고 idx값을 입력받는다. 그 다음 buf[idx]에 정수를 입력받고 idx와 buf[idx]값을 출력한다.

 

여기서 buf의 길이는 10으로 buf의 인덱스로 사용되는 올바른 값은 0 이상 10 미만의 정수이다. 그러나 scanf("%d", &idx); 에서 입력받은 idx 값을 인덱스로 사용할 때 올바른 범위에 속해 있는지 별도의 경계 검사가 존재하지 않는다. 그래서 올바르지 않은 값을 사용한다면 buf의 영역 밖에 있는 값에 접근할 수 있다.

 

→ idx에 11을 넣고 buf[idx]"31337"을 넣어 win 변수를 31337로 만들수 있다.

 

Out-Of-Boundary 예제 2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main(void) {
    int idx;
    int buf[10];
    int win;
    
    printf("Which index? ");
    scanf("%d"&idx);
    
    idx = idx % 10;
    printf("Value: ");
    scanf("%d"&buf[idx]);
    printf("idx: %d, value: %d\n", idx, buf[idx]);
    if(win == 31337){
        printf("Theori{-----------redeacted---------}");
    }
}
cs

위 코드는 00b-1.c와는 달리 idx = idx % 10 가 추가되었다. → 이 코드로 OOB 취약점을 막을 수 있을까?

OOB의 발생 여부를 판단할 때 버퍼의 인덱스로 사용할 수 있는 올바른 값의 범위와 버퍼의 인덱스가 될 수 있는 모든 값의 범위를 비교하면 된다.

-buf 의 인덱스로 써야 하는 값의 범위 : 0 ~ 9

-buf의 인덱스로 쓸 수 있는 값의 범위 : int 형의 범위 % 10

피연산자가 음수라면 나머지 연산의 결과도 음수가 될 수 있다.

따라서 이 경우, buf의 인덱스로 쓸 수 있는 값의 범위가 -9 ~ 9 이기 때문에 OOB를 발생시킬 수 있다.

 

 

2.3 Off-by-one

Off-by-one : 경계 검사에서 하나의 오차가 있을 때 발생하는 취약점

- 버퍼의 경계 계산 혹은 반복문의 횟수 계산 시 < 대신 을 쓰는 실수

- 0부터 시작하는 인덱스를 고려하지 못한 실수

 

Off-by-one 예제 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// off-by-one-1.c
#include <stdio.h>
void copy_buf(char *buf, int sz) {
    char temp[16];
    
    for(i = 0; i <= sz; i++)
        temp[i] = buf[i];
}
int main(void) {
    char buf[16];
    
    read(0, buf, 16);
    copy_buf(buf, sizeof(buf));
}
cs

위 코드는 buf에 16바이트 문자열을 입력받은 후 buf sizeof(buf)의 값을 copy_buf() 함수의 인자로 전달한다. copy_buf 함수는 임시 버퍼 temp를 할당하고 반복문을 통해 buf의 데이터를 복사한다.

 

그러나 반복문은 i가 0일때부터 sz일때까지, 총 sz+1번 반복하게 된다. 따라서 sz+1만큼 데이터가 복사되고, off-by-one 취약점이 발생한다.

 

 


Review(2강 Memory Corruption - C)

스택 버퍼 오버플로우

  • 지역 변수가 할당되는 스택 메모리에서 발생하는 취약점으로 데이터를 입력받거나 복사하는 부분에 대한 길이 검증이 존재하지 않거나 미흡할 경우 발생

힙 오버 플로우

  • 동적으로 할당된 힙 메모리 영역에서 발생하는 취약점으로 데이터를 입력받거나 복사하는 부분에 대한 길이 검증이 존재하지 않거나 미흡할 경우 발생

Out-Of-Boundary

  • 버퍼의 길이 범위를 벗어나는 인덱스에 접근할 때 발생하는 취약점이다. 올바르지 않은 값이 버퍼의 인덱스로 사용될 경우 발생

Off-by-one

  • 버퍼의 경계 계산 혹은 잘못된 반복문의 연산자를 사용하는 등의 인덱스를 고려하지 않을 때 발생

 

반응형

'공부 > 보안' 카테고리의 다른 글

[dreamhack] System Exploitation 3강  (0) 2021.03.07
나 보려고 정리해두는 gdb 명령어  (0) 2021.03.07
[dreamhack] System Exploitation 1강  (0) 2021.03.04
[pwnable.kr] - collision  (0) 2021.03.01
[pwnable.kr] fd  (0) 2021.02.26