본문 바로가기

취약점 분석/힙(Heap)

힙 손상 탐지(2-1) - 힙 오버런, 언더런 (Normal PageHeap)

반응형

힙 손상 탐지(2-1) - 힙 오버런, 언더런 (Normal PageHeap)


힙 버퍼 오버플로우. 참 많이 접해 본 단어이다.

할당된 공간을 넘어서 인접한 다른 힙 청크를 덮어쓰는 것 뿐인데 어떤 문제를 야기할 수 있을까?

예를 들어보자.

  • 덮어써진 힙에서 참조한 값으로 출발지 힙의 크기를 계산하여 소스힙을 할당한다던지 -> 또 다른 버퍼오버플로우 유발 가능.
  • 덮어써진 영역이 가상함수 테이블이라면..? -> 가상함수 호출 시 EIP가 제어됨. 띠용?!
  • 메타데이터가 변조되어 링크가 엉뚱한 곳을 가리키고, 이후 힙 할당/해제 시에 문제를 유발할 수 있음.

결론적으로 취약점을 정교하게 잘 악용한다면 프로그램의 실행흐름(EIP)도 가져올 수 있다는 얘기다.


힙 버퍼 오버플로우는 스택과 다르게 원인이 되는 부분과 한참 떨어진 부분에서 문제가 발생할 수도 있다.

(이미 덮어쓰였는데, 이 값을 사용하는 부분은 한참 뒤에 있다던지 등등)


그러므로 Crash부터 back trace 하며 원인분석을 하는 것은 상당히 미련한 짓이 될 수 있다.

(실제로 겪었다..-_- exploit 까지 6개월.. 변조 과정이 많았던 것도 있지만..)


분석하는 입장에서, 쓰여진 후에 발생하는 일들을 보는 것 보다 범위를 넘어서 메모리에 쓰여질 때 트랩하여 그 부근을 분석하는 것이 훨씬 효율이 좋을 것이다.


그렇다면 어떻게?

Gflags, Application Verifier 등의 Tool을 통해 PageHeap을 동작시킨 후 분석하는 것이다.


PageHeap이란?

: 보호계층으로 힙 블록을 둘러 싸 힙 블록들을 서로 격리시킴.


PageHeap의 종류

- Normal Page Heap,

- Full Page Heap 

보호 계층의 강도에 따라 두 가지로 분류된다.

보통 Full Page Heap을 많이 사용함. ( 넘어서서 쓰는 그 순간에!!! 바로 잡아주므로 - 오 홀리몰리 ! )


풀 페이지 힙의 단점은 자원을 많이 쓴다는 것.

따라서 먼저 풀 페이지 힙으로 실행시키고 너~~무 느리다면 그때 노말 페이지 힙으로 분석하면 됨.


1) Normal Page Heap

gflags를 이용한 노말 페이지힙 적용 방법

gflags.exe /p /enable <프로그램>

/p : 페이지 힙

/enable : 특정 옵션을 킴


0x10 만큼의 힙을 할당/해제 하는 프로그램을 작성하였다.


[노말 페이지 힙 적용 - 힙 할당]

요청 크기 0x10을 가지고 힙 할당 요청.

힙을 할당하고 결과로 받은 EAX실제 유저 사용 영역의 시작주다.


명령어 : !heap -x <조회하고자 하는 힙 주소>

힙 청크 기본 정보를 확인하고, 힙 엔트리 주소를 얻었다. 

(여기서 User 주소는 실제 유저 영역의 주소가 아니라 따라가 보면 보호 계층의 시작 주소이다.)


알아낸 힙 엔트리 주소로 힙 엔트리 정보 조회.

명령어 : !heap -i <힙 엔트리 주소>

분명 소스코드에서 0x10만큼만 할당요청을 줬는데, +0x28만큼 더 늘어난 크기를 요청했다.

늘어난 요청 크기 +0x28은 노멀 페이지 힙 때문에 추가된 보호계층의 크기이다.

사용자 영역 앞의 보호 계층(32bytes = 0x20)

사용자 영역

사용자 영역 뒤의 보호 계층(8bytes)


!heap -x 에서 사용자 영역이라고 했던 부분을 dd로 확인해 보자.

실제 사용자 영역은 힙 할당을 받고 반환된 EAX주소 (빨간 부분 영역)이 맞다.

노멀 페이지 힙을 적용시킨 덕에 실제 사용자 영역 앞뒤로 보호 계층이 둘러싸였다.


보호 계층 정보는 다음 명령어를 통해 조회가 가능하다.

명령어 : dt _DPH_BLOCK_INFORMATION <보호계층 시작주소>

여기서 주목해야할 부분은 StackTrace 부분이다.

힙 할당까지의 모든 스택 트레이스가 저장돼있다.


명령어 : dds <스택 트레이스 주소>

전체 콜스택 확인을 통해 쉽게 어디서 힙을 할당하는 지 추적할 수 있다.


정리해 보자.

노멀 페이지 힙 적용 후 할당되는 힙 블럭의 전체 구조는 다음 그림과 같다.


[노말 페이지 힙 적용 후 - Verifier에서 감지하는 모습]

[시나리오]

1) 20bytes(0x14)만큼 힙을 할당하고

2) Unicode 문자열을 사용자로부터 입력받아서 씀. ("ohl_world_is_my_world")

유니코드는 한 문자당 2bytes로 처리하므로, 10글자를 넘기면 0x20크기가 초과됨.

3) 힙 버퍼 오버플로우 발생

4) Application Verifier가 감지.


[ nstd debugger ]

-g (ignore initial breakpoint)

-x (set second-chance break on access violation exceptions) 


[ windbg ] 

힙 버퍼 오버플로우가 발생하는 프로그램을 실행했더니 Application Verifier에서 잡았음.

Heap handle : 해당 힙의 핸들

Heap Block : 사용자 영역 시작주소

corruption address : 보호 계층이 덮어쓰인 지점의 주소.


문자열을 입력하고 힙 BOF가 발생했으니, corruption address를 du 명령어로 살펴봤음.
"is_my_world" 라는 입력 문자열의 일부가 들어갔음을 확인.


실제 메모리 창을 열어 확인해 보자.

Heap Block에 쓰인 유저 영역의 시작주소에서 0x20을 빼면 Normal PageHeap 보호계층 시작지점부터 볼 수 있음.

1) 앞의 보호 계층의 마지막 부분인 채움패턴 DCBAAAAA 이후,

2) 노란색 부분의 유저 영역 0x14(20bytes)가 위치하고

3) 후위 채움 패턴으로 0xA0A0A0A0 0xA0A0A0A0이 위치해야하는데 없음!

4) 인자로 준 문자열의 뒷부분의 값이 들어가 후위 채움 패턴을 덮어쓴 모습 확인.

5) 결론 : 크기를 넘어서 힙 오버런이 발생했구나!



[노말 페이지 힙 적용 - 힙 해제]

노말 페이지 힙이 적용된 상태에서 HeapFree를 호출해 보자.

eax에 있는 0x04f45018은 유저 사용 영역의 시작주소이다.


힙 Free 이후의 힙 엔트리 모습이다.

  • 사용자 영역 시작주소 : 04f45018

  • 노말 페이지 힙 때문에 추가된 앞의 보호 계층 크기 : 0x20

  • 일반 힙 엔트리 메타데이터 크기 : 0x08

==> 04f45018 - 0x20 - 0x08 = 힙 엔트리 시작주소.


아래 그림으로 구조를 한번 더 정확히 짚어보자.

노말 페이지 힙 적용 후 할당한 힙 엔트리 구조랑 모습은 차이가 없다. 다만 값에서 차이가 있을 뿐이다.


 할당 시  

 해제 시 

 채움 패턴

: ABCDAAAA CDBAAAAA

 채움 패턴

: ABCDAAA9 CDBAAAA9

 사용자 접근 부분 채움 패턴

: E0

 사용자 접근 부분 채움 패턴

: F0



2) Full Page Heap

힙 손상 탐지(2-2) - 힙 오버런, 언더런 (Full PageHeap) 링크를 참조하자.


반응형