(* 참조 - https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc/)


malloc은 메모리 할당을 위해 사용하는 함수이다. 그 내부에서는 실제로 메모리를 할당받기 위해 시스템콜을 사용한다.

두가지 brk와 mmap 시스템콜을 사용한다.


(malloc)


brk
: brk는 프로그램 break location을 증가시키므로써 메모리를 획득한다. 

brk 를 확인해보기 위해 아래와 같이 코드를 작성한다.


(brk 테스트)


코드에서 brk를 사용했는데, sbrk는 어떤 목적으로 사용하였을까?



(brk 메뉴얼)


brk 메뉴얼을 확인해보면, brk와 sbrk가 있다.

sbrk의 경우 인자 값으로 확장하고 싶은 사이즈를 적고, 리턴값으로는 확장되기 이전의 program break를 반환한다. 그렇기에 저 위의 코드 sbrk(0)를 사용하게 되면 확장 사이즈는 0이므로 program break의 변화는 없고, 이전(변화가 없으니 현재) break location을 알 수 있다.

실행한 결과를 확인해보자.



(brk 되기 전)


brk 되기 전 프로그램 Break Location의 주소이다.

실제 메모리 모습을 확인해보면


(메모리 모습(brk 전))


[heap] 영역의 끝이 프로그램 Break Location인 것을 확인 할 수 있다.

brk를 한 후 결과를 확인해본다.



(brk 후)


brk 후 프로그램 Break Location의 위치가 0x1000 늘어난 것을 확인 할 수 있다.

메모리 상황을 확인해보면


(메모리 (brk 후))


[heap]의 영역이 늘어난 것을 확인 할 수 있다.

마지막으로 brk를 원래 위치로 되돌리는 것 까지 확인해보자.


(원위치)



(메모리 상황)


brk를 이용해 다시 줄여보았다. 이로써 brk를 통해 메모리를 할당하는 방식을 알아보았다.

그 다음 방식은 mmap이다.
mmap
: mmap은 호출한 프로그램이 사용하고 있는 영역에 대해 메모리를 잡는 것이다. 

코드를 이용해 직접 확인해보자.



(테스트 코드)


mmap 되기 전 상황과 된 후 상황일 먼저 비교해볼 것이다.


(mmap 전)



(메모리 (mmap 전))


mmap 전 라이브러리 파일 영역이다.

그 후 mmap을 진행한다.



(mmap 실행)



(메모리 ( mmap 후))


기존에 있던 프로그램의 메모리 영역에 mmap을 통해 공간을 만든 것을 확인 할 수 있다.

munmap 후까지 확인해본다.



(munmap)



(메모리 ( munmap 후 ))


munmap 후 다시 프로그램에게 메모리가 반환되는 것을 확인 할 수 있었다.

'Hacking > System Hacking' 카테고리의 다른 글

(RootKit) Simple Rootkit  (0) 2018.04.12
(RootKit) LKM(Loadable Kernel Module)  (0) 2018.04.03
System Hacking - jmp (반복문)  (0) 2017.04.19
System Hacking - jmp (분기문)  (0) 2017.04.18
System Hacking - 어셈블리어(사칙연산)  (0) 2017.04.14
  오늘 분석한 how2heap 자료는 poison null byte 이다.
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)


0x100 사이즈를 할당 받는다.

(실제 사용 가능한 사이즈)


실제 사용 가능한 사이즈는 어떻게 될까?



(실제 사용 가능한 사이즈)


실제 사용 가능한 사이즈는 0x8 바이트 더 쓸수 있게 나와있는데, 이는 이전 포스트 글에서 설명했으므로 넘어갈 것이다. ( malloc의 사용가능영역 이라는 글에 정리해 두었다. )



(추가 malloc)


그 후 두 개의 영역을 더 할당한다.

위의 과정을 마치면 크게
a ( 0x100)
b ( 0x200)
c ( 0x100)
의 모양으로 heap영역에 할당 되어있다.

여기서 b 영역의 사이즈를 확인해보기 위해 포인터를 이용했다.



(b chunk size 포인터)



(gdb 메모리 확인)


gdb로 메모리를 확인해보면 저 위치는 위와 같다.
우리는 0x200을 할당했는데? 0x211?
사이즈(0x200) + 헤더 크기(0x10) | flag
의 결과이다.

원래 자료에서는 b 영역 안에 0x200의 크기를 적어주는 부분이 있는데 순서를 바꾸어서 설명하겠다. 그것이 더 이해하기 편할거 같다.

그 후 b를 free해준다.



(free(b))


그렇게 되면 b 영역은 free 된 chunk가 되고

c chunk의 header에서 PREV_SIZE 영역에 이전 chunk가 b이므로 b가 해제된 순간 b의 사이즈가 c chunk header에 적히게 된다.



(PREV_SIZE)


이전 청크의 사이즈인 0x210이 적히게 된다.

그 다음에 할 작업의 목적을 이해해야한다.
우리는 지금 작성된 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 우회)


b의 주소는 헤더를 넘어간 주소이므로 0x1f0을 더한 값이 b chunk 주소 + 0x200 의 위치가 된다.

해당 위치에 0x200을 넣는다.



(확인)


확인해보면 현재 메모리에 200이 들어가 있는 것을 확인 할 수 있다.

그리고 b chunk 헤더에 있는 chunk size를 확인해보면



(b chunk size)


현재 0x211 크기이다.

이제 여기서 한바이트를 덮어쓰면



(덮어쓰기)


여기서는 편하게 배열의 인덱스로 접근하여 적었다. 실제에서는 문자열을 이용해 null을 입력한다.



(덮어쓴 모습)


덮어쓰면 b chunk size가 0x200이 된다.

이제 여기서 b1에 다시 malloc 한다.

아까 b chunk 크기보다 작은 크기로



(malloc)


그렇게 되면 아까 free된 b chunk의 주소가 할당된다. 이 과정에서 unlink가 일어나게 된다.
여기서 아까 위에서 우리가 우회한 조건을 검사하고
해당 PREV_SIZE 위치(우리가 조작한) 에 값이 0x200이었는데
우리가 0x100을 요청했으므로 실제 청크 사이즈는 0x110이고
이 값을 뺀 값!
0x200 - 0x110 = 0xf0

이 값이 우리가 조작한 PREV_SIZE 위치에 적히게 된다.


(조작된 PREV_SIZE)


그 후 b2를 또 다시 malloc 한다. 이번에는 0x100보다 작은 사이즈를 할당해야한다. 그래야 아까 사용하던 b chunk를 사용하기 때문이다.

b1이 쓰고 남은 그 뒤를 할당받는다.



(b2 malloc)


그렇게 되면 현재 상황은
a ( 0x100 )
b1 ( 0x100 )
b2( 0x80 )
c ( 0x100 )
이 된다.

여기서 공격대상의 chunk는 b2이다.

b2의 내용이 원래 B로 채워진 값들이었다고 치자.



(B로 채우기)



(현재 b2의 값)


현재 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)


그렇게 되면 freelist에 있는 병합된 방금 우리가 free한 b1의 주소를 받게 된다.
사이즈는 0x300만큼
그렇게 되면
d가 할당받은 영역은 b2를 포함하고 있게 된 것이다.
그러면 d를 D 라는 문자로 덮어쓰고

그 후에 b2의 값을 확인해보면?



(b2의 내용)


b2의 내용은 아까 B로 가득차 있었는데,
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
  우리는 malloc을 이용해 heap영역의 메모리 주소를 할당 받는다.
우리가 할당받은 주소는 heap영역의 chunk(덩어리)이다. 실제 덩어리에서 우리가 사용할 수 있는 공간은 어느정도 일까?

간단하게 malloc으로 0x10과 0x18 사이즈로 두개를 할당받는 코드를 만들었다.


(테스트코드)


그리고 malloc_usable_size 함수로 사용가능한 크기를 확인해보았다.



(확인 결과)


확인 결과 두개의 주소 모두 0x18로 나왔다. 어떻게 된것일까?

gdb를 통해 메모리 모습을 확인해보았다.



(메모리 모습)


우리는 분명 0x10과 0x18의 크기로 메모리 할당을 요청했음에도 불구하고 실제로 chunk 사이즈를 보면 0x21로 두개의 chunk가 할당된 것을 볼 수 있다.
여기 나와있는 사이즈는 chunk의 실제 크기이다.

0x10으로 할당을 하면 위의 예시에서 6010 주소가 반환된다. 이 chunk의 헤더는 0x10크기를 가진다.
그렇기에 실제 chunk의 크기는 0x10 큰 사이즈이다. 그렇다면 0x20이 아닌 0x21인 이유는 마지막 3비트는 flag비트로 사용되기 때문이다.

다시 글의 주제로 돌아가서 실제 사용가능한 크기가 전부 0x18이라니.
이 이유는 우리가 0x10을 요청했을 경우 실제로 0x10크기의 영역이 잡힌다. 그 후에 다음 청크가 할당되었는데 이 때 다음 청크의 헤더 앞부분까지 실제로는 사용가능하다. 위 사진에서 빨간색 테두리만큼 실제로 데이터를 쓰기가 가능하다는 것이다.

이해하기 쉽게 정리하면 청크의 헤더는 0x10크기이다.
그 중 0x8 크기는 자신의 chunk 사이즈를 담고있고 그 앞의 0x8바이트는 
PREV_SIZE 영역으로 이전 chunk의 크기를 가지고 있는데, 
-> 이전 chunk가 사용중이라면 이 영역은 이전 chunk가 사용한다.
-> 이전 chunk가 사용중이지 않다면(free됬다면) 이 영역은 이전 chunk의 size를 가지고 있게 된다.
그렇기에 실제로 사용가능한 크기는 다음 chunk의 헤더 앞부분 0x8크기만큼을 더 쓸 수 있다는 이야기이다.

'Vulnerability_Tech > About Heap' 카테고리의 다른 글

(how2heap) - house of lore  (0) 2018.02.21
(how2heap) - poison null byte  (0) 2018.02.20
(how2heap) - house_of_spirit  (0) 2018.01.17
(how2heap) - unsafe_unlink  (0) 2018.01.13
(how2heap) - fastbin_dup_into_stack  (0) 2018.01.11

+ Recent posts