힙 손상 탐지(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 : 보호 계층이 덮어쓰인 지점의 주소.
실제 메모리 창을 열어 확인해 보자.
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) 링크를 참조하자.
'취약점 분석 > 힙(Heap)' 카테고리의 다른 글
힙 손상 탐지(2-2) - 힙 오버런, 언더런 (Full PageHeap) (0) | 2019.03.14 |
---|---|
힙 손상 탐지(1) - 초기화 안 된 상태로 사용 (0) | 2019.03.13 |
힙의 세부 항목 조회 (0) | 2019.03.12 |
힙 블럭 합병(Coalecing), 해제 과정 (0) | 2019.03.12 |
힙 할당 메커니즘 (기초) (0) | 2019.03.09 |