[6] Memory Corruption - C (1) :: Buffer Over Flow(BOF)

본 글은 DreamHack의 강의 내용을 요약한 글이므로 자세한 내용은 dreamhack.io 에서 학습하시길 바랍니다. 

 

해커들의 놀이터, Dreamhack

해킹과 보안에 대한 공부를 하고 싶은 학생, 안전한 코드를 작성하고 싶은 개발자, 보안 지식과 실력을 업그레이드 시키고 싶은 보안 전문가까지 함께 공부하고 연습하며 지식을 나누고 실력 향

dreamhack.io

 

학습목표

C언어에서 발생할 수 있는 Memory Corruption 취약점 중 Buffer Over Flow를 알아본다.

 

1. Buffer Overflow란? 

인접한 메모리를 오염시키는 취약점(Memory Corruption) 중 하나로

버퍼가 허용할 수 있는 양의 데이터보다 더 많은 값이 저장되어 버퍼가 넘치는 취약점을 말하며,

BOF가 발생하는 위치에 따라 스택 버퍼 오버플로우(Stack Buffer Over Flow) / 힙 오버플로우(Heap Over Flow) 로 구분된다.

-> 어떤 메모리가 오염되느냐에 따라 공격 기법이 달라진다.

 

- 버퍼 (Buffer) : C언어에서 지정된 크기의 메모리 공간

 

 

2. Stack Buffer Overflow

초기에 연구되었던 형태의 버퍼 오버플로우

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

 

출처 : dreamhack.io

(1) 메모리에 각각 8바이트씩 선형적으로 할당된 A, B 버퍼

 

(2) 만일, 버퍼 A에 16바이트의 데이터를 복사하는 경우

 

(3) 데이터의 뒷부분은 A 버퍼의 영역을 넘어 뒤에 있는 데이터 영역 B 버퍼에 쓰여지게 됨. -> 버퍼오버플로우 발생

 -> 프로그램의 Undefined Behavior (정의되지 않은 동작) 유발

 

(4) 이러한 상태에서 B 버퍼에 추후 호출될 함수 포인터를 저장하고 있었는데 이 값이 "AAAAAAAA"와 같은 엉뚱한 데이터로 덮게 될 경우

 

(5) Segmentation Fault(접근권한이 없는 메모리에 read / write 작업 시도 시 발생하는 예외)를 유발

 

(6) 공격자가 이를 악용 시 다음과 같은 행위가 가능하게 됨

 임의의 위치에 기계어 코드 삽입 후 함수 포인터를 공격자의 코드 주소로 덮어 삽입한 코드 실행

 

 

3. Stack Buffer Overflow 실습

 

실습 1. 버퍼 크기보다 큰 데이터 길이로 스택 버퍼오버플로우 발생시키기

[기본 코드]

 

- stack-1.c

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

 

(1) gets() 함수

사용자가 개행(CRLF)을 입력하기 전까지 입력한 모든 내용을 첫 번째 인자로 전달된 버퍼에 저장하는 함수

별도의 길이 제한이 없어 할당된 사이즈 이상의 데이터를 받는 것이 가능 -> 스택 오버플로우 발생

 

(2) 발생 원인

프로그래머가 버퍼 길이에 대한 가정을 올바르지 않게 하여 발생

보통 길이 제한이 없는 API 함수들을 사용하거나 버퍼 크기보다 입력받는 데이터 길이가 더 클 때 자주 일어난다.

 

(3) 버퍼를 오버플로우 시켜 ret 영역 0x41414141로 채워보자.

출처 : dreamhack.io

buf : 16바이트의 버퍼 영역

sfp : 스택 프레임 포인터(4바이트)

ret: Return Address (4바이트)

 

풀이

-> 16바이트 이상의 데이터를 입력을 하되, ret 위치에 들어갈 바이트는 0x41('A')로 채우면 된다.

※ ret 영역을 반드시 0x41에 해당하는 값(A)을 넣어야 Success!! 문구가 같이 출력된다.

 

실습 2. auth 영역을 스택 버퍼오버플로우로 침범하여 인증 우회하기

[기본코드]

- stack-2.c

(1) main 함수에서 check_auth함수로 argv[1] 값을 넘겨 인증 수행 후 리턴값을 받아 온다.

-> 리턴값이 0이 아니면 Hello Admin! 출력, 0일 경우 Access Denied! 출력

(2) check_auth 함수에서 password로 전달받은 argv[1] 값을 strncpy 함수를 사용하여 16바이트의 temp 버퍼로 복사

(3) strcmp 함수로 temp 버퍼와 "SECRET_PASSWORD" 문자열을 비교한다.

-> strcmp 함수는 기본적으로 두 비교값이 같을 경우 0을 반환하지만, 여기서는 !(not)으로 1을 반환하도록 함

(4) strcmp함수로부터 반환된 값이 1일 경우 if문이 참이 되므로 auth 변수를 1로 설정하게 된다.

(5) auth 값을 main 함수로 return 한 후 line 24의 if문 수행하여 결과 값에 따라 인증 결과 출력

 

풀이

strncpy 함수에서 복사할 데이터 크기가 temp 버퍼의 크기가 아닌 입력받은 데이터 값, 즉 password 크기만큼 복사하도록 설정되어 있다.

-> 16바이트가 넘는 데이터를 받으면 길이 제한 없이 문자열이 복사되므로 스택 버퍼 오버플로우가 발생

-> 메모리 스택에 temp 버퍼 뒤에 auth 영역이 존재하여 오버플로우 발생 시 auth영역이 침범당한다.

-> auth 영역의 값을 0이 아닌 값으로 변경하게 되면 line 24의 if문이 무조건 참이 되게 할 수 있다.

-> "SECRET_PASSWORD"와 일치하지 않더라도 Hello Admin! 이 출력된다. (인증 무효화)

 

즉, 16바이트의 temp 버퍼를 넘어가도록 그 이상의 데이터를 입력하여 auth 영역이 0이 아닌 값으로 채워지게 한다.

위 그림에서 overflowed 영역은 auth 영역을 넘어 그 이후의 메모리까지 침범한 것으로 값이 덮어 씌워진 것을 확인할 수 있다.

입력값에 대한 인증과 상관 없이 if문이 참이 되어 Hello Admin!이 출력되었다.

 

실습 3. scanf 함수를 이용해 스택 버퍼 오버플로우 발생시키기

[기본코드]

출처 : dreamhack.io

- stack-3.c

(1) scanf 함수로 size를 입력 받는다. (24바이트 할당받은 buf 보다 큰 값의 size)

 

(2) 입력한 size만큼 데이터를 read 함수를 통해 데이터를 입력받는다.

※ read 함수

첫번째 인자 : fd(file descriptor) -> 0번일 경우 표준입력

두번째 인자 : 읽을 데이터를 저장할 메모리 영역 -> buf

세번째 인자 : 읽어들일 byte 수 -> scanf에서 입력받았던 size 값만큼 두번째 인자인 buf에 데이터를 입력받는다.

 

(3) strncmp로 win배열에 있는 값이 "ABCD"와 일치하는지 비교한 후 일치하면 Redacted된 데이터를 printf로 출력하게 한다.

 

(4) 그런데 이때, 고정된 크기의 버퍼 (=buf 배열) 보다 더 긴 데이터를 입력받을 수 있게 되므로 BOF가 발생할 수 있게 된다.

 

풀이

main에서 선언한 win, size, buf 는 stack에서 인접해 있으므로 buf 크기 보다 긴 데이터를 보낼 수 있도록 size를 24보다 큰 값으로 입력하면 buf 영역을 넘어 size, win 까지 침범할 수 있다.

위의 input 창에서 win 영역에 ABCD 값이 들어가도록 했더니 redacted 되었던 부분이 노출된 것을 확인할 수 있다.

 

실습 4. sprintf 함수에서 이미 입력된 데이터를 이용해 스택 버퍼 오버플로우 발생시키기

[기본코드]

출처 : dreamhack.io

- stack-4.c

(1) buf 배열을 0으로 초기화 한 후, read 함수로 buf에 31바이트 크기의 데이터를 읽어들인다.

 

(2) sprintf 함수로 출력할 문자열을 buf에 저장한 뒤 출력하는 코드

 

(3) 이 때, read 함수에서 읽어들이는 데이터는 buf 배열 자체의 크기를 넘어서지 않지만, sprintf의 2번째 인자에 있는 "Your Input is : %s\n" 이 추가되므로 read에서 17바이트 이상의 데이터를 받게 되면 총 길이가 32바이트를 넘어서게 된다.

 

풀이

read에서 읽어들이 데이터 크기와 기존에 sprintf 함수에서 추가된 문자열 "Your Input is : "의 총 데이터 크기가 32바이트 이상이면 BOF가 발생하게 된다. 

 

stack 부분을 보면 sprintf 함수의 2번째 인자로 들어간 문자열이 buf에 저장되고, read로 buf에 읽어들였던 데이터까지 더해지며 sfp를 넘어서 ret까지 침범한 것을 확인할 수 있다.

 

Hexdump로 본 값은 위와 같다.

 

 

4. Stack Buffer Overflow 정리

  • 프로그래머가 길이에 대한 검증을 정확히 수행하지 않아 발생하는 경우가 대다수
  • attack vector로부터 데이터를 입력받고 버퍼에 저장하는 코드가 있다면 버퍼의 범위를 초과하지 않는 데이터인지 면밀히 봐야 한다.
  • 입력에 길이 제한이 없는 함수를 사용하는 것은 잠재적으로 취약하다.
  • 입력 데이터가 버퍼로 저장되기까지 흐름을 따라가 버퍼의 크기를 넘는 양을 저장할 수 있는지에 대한 가능성을 검토해야 한다
  • heap overflow 또한 발생 원인은 본질적으로 다르지 않다. (메모리 영역의 차이)

 

 

5. Heap Overflow 맛보기

Heap Overflow exploit은 Linux Exploitation & Mitigation 강의에서 배울 예정.

본 글에서는 발생 원리만 간단히 정리

 

[기본코드]

출처 : dreamhack.io

(1) 40바이트의 heap 버퍼 input과 hello를 동적 할당 (feat. malloc)

 

(2) memset 함수로 input과 hello 버퍼 초기화

 

(3) strcpy 로 hello 버퍼에는 "HI!"라는 문자열 복사, input 버퍼에는 100바이트 크기만큼의 데이터를 read 함수로 읽어들인다. 

 

(4) 이 때, read 함수에서 입력받는 100바이트의 데이터가 input 버퍼의 할당된 크기인 40바이트를 넘어서기 때문에 heap overflow가 발생할 수 있다.

출처 : dreamhack.io

힙 메모리 상태를 위의 그림으로 보면 알 수 있듯이 input에 할당된 크기는 40바이트지만, read로 읽어들인 데이터가 input의 크기보다 큰 경우 bof로 hello 영역까지 침범할 가능성이 있게 된다.

-> hello 메모리를 printf로 출력 시 처음에 copy한 "HI!" 문자열이 아닌 공격자가 의도한 corrupted data가 출력된다.

 

 

 



출처 : Memory Corruption - C (I) (DreamHack, https://dreamhack.io/learn/2/14#2)