엄범


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 할당 순으로 진행한다.
따라서 
  1. merge 및 unlink를 유발하기 위해 chunk A의 `` PREV_INUSE`` flag를 조작할 수 있어야 한다.
  2. 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
[UNDEAD] unlink  (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