[UNDEAD] unlink
unlink
`` unlink``는 consolidate가 발생할 때 연결되어 있던 bins list에서 chunk를 제거하기 위해 호출된다.
`` PREV_INUSE``를 체크해 호출하기 때문에 fastbin chunk에 `` PREV_INUSE`` unset한다고 해서 회피할 수 있는 것이 아니다.
glibc 2.25's unlink macro
```c
#define unlink(AV, P, BK, FD) {
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV);
FD= P->fd;
BK= P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) // DEAD
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
else {
FD->bk= BK;
BK->fd= FD;
if (!in_smallbin_range (chunksize_nomask (P))
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
....check routines....
```
```c
prev_size size|flag
P->fd P->bk
```
- ``c P->fd's value + 12`` 위치에 ``c P->bk``에 있는 값이 대입
- ``c P->bk's value + 8`` 위치에 ``c P->fd``에 있는 값이 대입
Note ) 두 작업이 모두 일어나기 때문에 `` fd/bk`` 모두 쓰기 권한이 있는 위치여야 한다.
realloc()'s unlink
``c realloc(p, bigger)`` 요청이 들어와 chunk를 확장해야 할 때 발생.
```c
/* Try to expand forward into next chunk; split off remainder below */
else if (next != av->top &&
!inuse (next) &&
(unsigned long) (newsize = oldsize + nextsize) >=
(unsigned long) (nb))
{
newp = oldp;
unlink (av, next, bck, fwd);
}
```
free()'s unlink
``c free()`` 중 인접 free'd chunk와 merge( consolidate backward / forward )하는 경우 size가 달라져 bins를 옮겨야 할 때 `` unlink``가 호출된다.
```c
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
...
/* consolidate forward */
if (!nextinuse) {
unlink(av, nextchunk, bck, fwd);
size += nextsize;
}
```
``c free(A)``하면 chunk A의 `` PREV_INUSE`` flag를 확인하고, unset이면 인접 chunk를 unlink → merge → fd/bk 할당 순으로 진행한다.
따라서
- merge 및 unlink를 유발하기 위해 chunk A의 `` PREV_INUSE`` flag를 조작할 수 있어야 한다.
- unlink되는 것은 free되는 현재 chunk인 A가 아니라 이전 인접 chunk이므로, 이전 chunk의 `` fd/bk``를 조작할 수 있어야 한다.
이전 chunk의 `` fd/bk``를 조작할 수 없는 경우 chunk A의 `` mchunkptr`` 위치에 있는 `` prev_size`` field를 조작해 fake chunk를 unlink하도록 하는 방법을 사용할 수 있다.
* 현재 chunk를 대상으로는 unlink를 진행할 필요가 없는게, 이제 막 free되었기 때문에 bins list에 속해있지 않은 상태다.
prev_size fake
```
PREV chunk's mchunkptr = A's mchunkptr - prev_size
```
따라서 `` prev_size``를 조작하면 PREV chunk 위치를 제어할 수 있다.
`` prev_size``에 음수를 넘기는 것도 가능하기 때문에 음수를 넘겨 PREV chunk를 A chunk 내부에 있는 것 처럼 잡을 수 있다.
A chunk에 대한 쓰기 권한이 있는 경우 PREV chunk's `` fd/bk``를 A chunk에 쓰는 것으로 채울 수 있다.
DEAD
check를 통과하기 위해서는 ``c FD->bk = P``이면서, ``c BK->fd = P``이어야 한다. 때문에 사용할 수 있는 환경이 매우 제한된다.
chunk P의 `` FD/BK``는 정상적으로 bins에 연결되어 있지 않기 때문에 check를 통과하려면 `` FD/BK``를 다음과 같이 설정해야만 한다.
```c
FD = &P-(sizeof(uint64_t)*3);
BK = &P-(sizeof(uint64_t)*2);
```
stack (maybe) | |
FD |
|
BK |
|
|
|
&P |
P |
... | |
heap chunks | |
P |
|
그런데 unlink는 다음과 같이 수행되기 때문에
```c
FD->bk = BK;
BK->fd = FD;
```
결국 ``c P=P->fd``를 실행한 것과 같은 결과가 나온다.
``c P->fd``의 값은 check를 통과하려면 고정이므로, 사실상 이를 이용해 GOT 등을 수정하는 것은 어렵다.
UNDEAD
그러나, `` P``는 이제 heap이 아니라 자기 자신 근처를 가리키고 있다.
stack (maybe) | |
FD |
|
BK |
|
| |
&P | FD |
따라서 ``c P[3]``으로 접근해서 자기 자신이 가리키는 곳을 다른 곳으로 변경할 수 있다.
* 실제로 가리키는 곳을 다른 곳으로 변경하는 작업을 사용자에게 제공하는 경우는 거의 없고, 대부분 가리키는 곳에 있는 값을 변경하는 인터페이스만 제공하기 때문에 단순히 가리키는 곳에 있는 값을 변경하는 것이 가리키는 곳 자체를 다른 곳으로 변경하는 작업이 가능하다면 결국 그 곳에 원하는 데이터를 쓸 수 있다는 얘기가 된다.
'Security > System Exploit' 카테고리의 다른 글
[UNDEAD] The House of Mind (0) | 2017.08.15 |
---|---|
unsorted bin attack (0) | 2017.08.15 |
The House of Lore (0) | 2017.08.15 |
fastbin attack / fastbin_dup (0) | 2017.08.15 |
The House of Spirit (0) | 2017.08.15 |