poison null byte는 heap 영역 뿐아니라 굉장히 다양한 곳에서 사용할 수 있는 개념이다.
이 취약점은 off-by-one 에러를 이용한다.
off-by-one 이란 한바이트 혹은 두바이트 정도의 overflow를 허용하므로써 취약점으로 연결 시키는 방식을 말한다.
보통 overflow 공격에서 잘 막아두는데, for 문이나 코딩을 하다가 간혹 한바이트 정도를 더 받게 되는 경우가 있다. 대표적으로 string이다. 문자열을 입력받으면 맨 마지막에 null이 따라온다.
예를 들어 char buf[10] 이라고 하자.
여기에 우리가 overflow 개념이 있어서 이것을 막고자 글자수를 10자만큼만 받겠어! 라고 조건문을 걸었다. 그렇다면 15글자 등등 10글자를 넘는 조건은 우리가 막을 수 있다. 하지만 딱 10글자가 들어온다면? -> 뭐 괜찮지라고 생각할 수 있지만, 문자열은 맨 마지막에 null이 따라온다.
즉, 10글자 string을 입력받게 되면 맨 우리의 buf에서 한바이트가 overflow가 일어난다.
한바이트 overflow정도야 라고 생각하겠지만 이것이 return 주소의 한바이트를 덮어쓰므로써 return 주소를 조작할 수 있고 큰 취약점으로 연결 될 수 있다.
off-by-one 개념은 여기까지 간단히 알아보고
poison null byte 자료를 분석해보자.
이 자료를 이해하는데 어려웠다. 이 포스트 글을 끝까지 읽어보면 아다리가 맞듯 이해가 될 것이다.
결과적으로 이 자료에서는 중복된 영역의 heap 주소를 할당받았다.
할당 받은 heap 영역 안에 기존에 이미 할당받았던 heap 영역이 있어서 덮어쓸 수 있는 상황으로 만들었다. 그렇다면 시작! 해보자!
malloc으로 시작한다.
(malloc)
(실제 사용 가능한 사이즈)
실제 사용 가능한 사이즈는 어떻게 될까?
(실제 사용 가능한 사이즈)
실제 사용 가능한 사이즈는 0x8 바이트 더 쓸수 있게 나와있는데, 이는 이전 포스트 글에서 설명했으므로 넘어갈 것이다. ( malloc의 사용가능영역 이라는 글에 정리해 두었다. )
(추가 malloc)
위의 과정을 마치면 크게
a ( 0x100)
b ( 0x200)
c ( 0x100)
의 모양으로 heap영역에 할당 되어있다.
여기서 b 영역의 사이즈를 확인해보기 위해 포인터를 이용했다.
(b chunk size 포인터)
(gdb 메모리 확인)
우리는 0x200을 할당했는데? 0x211?
사이즈(0x200) + 헤더 크기(0x10) | flag
의 결과이다.
원래 자료에서는 b 영역 안에 0x200의 크기를 적어주는 부분이 있는데 순서를 바꾸어서 설명하겠다. 그것이 더 이해하기 편할거 같다.
그 후 b를 free해준다.
(free(b))
c chunk의 header에서 PREV_SIZE 영역에 이전 chunk가 b이므로 b가 해제된 순간 b의 사이즈가 c chunk header에 적히게 된다.
(PREV_SIZE)
그 다음에 할 작업의 목적을 이해해야한다.
우리는 지금 작성된 0x210, c chunk에 있는 PREV_SIZE를 건들이고 싶지 않다.
그대로 잘 0x210으로 보존하고 싶다. ( 그 이유는 끝까지 보면 이해가 될 것이다. 지금은 아 그래야하는 구나라고 생각하고 쭉 읽어보자. )
그렇기에 우리는 free된 b chunk의 사이즈영역에 overwrite를 하여 0x210을 0x200으로 만들것이다.(a chunk에서 덮어써서, 한바이트 overflow로)
후에 unlink가 일어나는데, 이 때 검사하는 조건이 있다.
chunksize(P) != prev_size(next_chunk(P))
우리는 b의 헤더에서 한바이트 덮어써서 0x210을 0x200으로 만들 것이다.
그렇다면 지금 b주소 + 0x200 에 들어있는 0x00 값이 있는데 위의 조건에 걸려서 에러가 난다.
그렇기에 b주소 + 0x200 의 위치에 0x200 을 적어줘야한다.
(check 우회)
해당 위치에 0x200을 넣는다.
(확인)
그리고 b chunk 헤더에 있는 chunk size를 확인해보면
(b chunk size)
이제 여기서 한바이트를 덮어쓰면
(덮어쓰기)
여기서는 편하게 배열의 인덱스로 접근하여 적었다. 실제에서는 문자열을 이용해 null을 입력한다.
(덮어쓴 모습)
이제 여기서 b1에 다시 malloc 한다.
아까 b chunk 크기보다 작은 크기로
(malloc)
여기서 아까 위에서 우리가 우회한 조건을 검사하고
해당 PREV_SIZE 위치(우리가 조작한) 에 값이 0x200이었는데
우리가 0x100을 요청했으므로 실제 청크 사이즈는 0x110이고
이 값을 뺀 값!
0x200 - 0x110 = 0xf0
이 값이 우리가 조작한 PREV_SIZE 위치에 적히게 된다.
(조작된 PREV_SIZE)
b1이 쓰고 남은 그 뒤를 할당받는다.
(b2 malloc)
a ( 0x100 )
b1 ( 0x100 )
b2( 0x80 )
c ( 0x100 )
이 된다.
여기서 공격대상의 chunk는 b2이다.
b2의 내용이 원래 B로 채워진 값들이었다고 치자.
(B로 채우기)
(현재 b2의 값)
여기서 b1과 c를 free한다.
(free)
b1을 free한다.
그렇게 되면 b1은 free chunk가 된다.
그리고 c를 free할 때 사건이 일어난다.
c를 free하면 c의 PREV_SIZE를 확인하는데 이 때 0x210으로 적혀져 있다. (지금 이 상황을 만들기 위해 아까 이 값을 보존하고 싶었던 것이다.)
그렇기에 free를 할 때 c chunk의 이전 청크 사이즈인 0x210을 고려하여
c chunk주소 - 0x210 의 주소에 가봐서 free인지 확인한다.
확인해보니. 얼래! 아까 우리가 b1을 free했기 때문에 free 된 영역이다.
그렇기에 glibc는 안타깝게도 b2를 보지 못하고 b1과 c를 병합하게 된다.
그리고 그 주소를 freelist에 넘기게 된다.
그 후 다시 malloc을 통해 0x300( 원래 b 사이즈 + 원래 c 사이즈)의 크기로 요청을 한다면
(malloc)
사이즈는 0x300만큼
그렇게 되면
d가 할당받은 영역은 b2를 포함하고 있게 된 것이다.
그러면 d를 D 라는 문자로 덮어쓰고
그 후에 b2의 값을 확인해보면?
(b2의 내용)
D로 가득찬 모습을 확인할 수 있게 된다.
즉, 다른 chunk안에 있는 내용을 조작할 수 있다는 것이다.
이것이 구조체여서 함수값을 조작할 수도 있고 가능성은 넓다.
'Vulnerability_Tech > About Heap' 카테고리의 다른 글
(how2heap) - overlapping_chunks (0) | 2018.02.22 |
---|---|
(how2heap) - house of lore (0) | 2018.02.21 |
malloc의 사용가능 영역(HEAP) (0) | 2018.02.20 |
(how2heap) - house_of_spirit (0) | 2018.01.17 |
(how2heap) - unsafe_unlink (0) | 2018.01.13 |