(UNDEAD) The House of Mind
The House of Mind
http://phrack.org/issues/66/10.html
https://gbmaster.wordpress.com/2015/06/15/x86-exploitation-101-house-of-mind-undead-and-loving-it/
unlink와 반대로, chunk가 free되면서 bins와 link하는 과정에서 발생하는 쓰기를 이용하는 방식이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bck = unsorted\_chunks(av); // == &av->bins[0]
fwd = bck->fd;
if (\_\_glibc\_unlikely (fwd->bk != bck)) // DEAD
{
errstr = "free(): corrupted unsorted chunks";
goto errout;
}
p->fd = fwd;
p->bk = bck;
if (!in\_smallbin\_range(size))
{
p->fd\_nextsize = NULL;
p->bk\_nextsize = NULL;
}
bck->fd = p;
fwd->bk = p;
fwd->bk = p
\*(&bins[0] + 8)->bk = p
, \*(\*(&bins[0] + 8) + 12 ) = p
이므로 \*(&bins[0] + 8) + 12
번지에 ` p`에 있는 값이 대입
따라서,&bins[0] + 8
번지에 Execution point addr - 12
주소를 넣는다.
⇒ \*(&bins[0] + 8) = Execution point addr - 12
가 되므로, Execution point addr
번지에 p
를 대입하게 된다.
원래 House of Mind는 이 포인터를 이용하는데, 지금은 patch되어 사용할 수 없다.
bck->fd = p
\*(&bins[0] + 8) = p
이므로, &bins[0] + 8
번지에 ` p에 있는 값이 대입 (
type(p) == mchunkptr 이므로 chunk의 주소가 대입된다.) \*
&bins[0] +8 == &bins[2] 이기는 하다만 arena에도 chunk가 있는 것 처럼 간주하고 동작하기 때문에 이게 맞다. 아래 그림은 조금 잘못그렸다. 이 경우 다음과 같이
.dtors의 위치에
&bins[0] + 8가 위치하도록 해야 하므로 arena를 아예
.dtors` 주변에 잡아버려야한다.
| | | | — | — | | &bins[0] + 8 && Execution point | &p | 그래서 조건을 충족시키기가 꽤나 어렵다.
[UNDEAD] fastbin method 가 이와 동일한 방법으로 동작한다.
exploit
- overflow를 이용하는 방식으로는 main_arena bins[0]에 직접 값을 쓸 수는 없다. ⇒ fake bins[0] ( fake arena )가 필요하다.
free(chunk)
시 main_arena가 아니라 fake arena를 사용하도록 해야 한다. ⇒ fake heap_info가 필요하다.free(chunk)
시 fake arena와 연결된 fake heap_info를 사용하도록 해야 한다. ⇒ chunk가 main_arena heap range 밖에 있어야 하며, NON_MAIN_ARENA가 set 되어야 한다.
Note) 그림을 조금 잘못 그렸는데, bins[0]
에서 나가는 노란색 화살표가 가리키는 chunk는 실제로는 없고, arena 내부에 있는 것으로 간주한다. 따라서 bins[2] == bins[0]'s fd
다. * consolidate를 막기 위해 P
에 PREV\_INUSE
flag도 있어야 한다. * P
를 비롯한 chunk들은 모두 heap에 정상적으로 할당된 chunk들 이며, P
이후에는 top chunk가 위치하기 때문에 free’s next size check는 따로 신경쓰지 않아도 된다. * 8 bytes A..A는 위쪽 chunk를 free 하면서 fd/bk
가 채워지는 자리이므로, 이 부분 이후에 arena가 시작된다. 사실 위쪽 chunk를 굳이 free할 필요는 없는 듯. * 이후 위치하는 8 bytes도 free 하면서 0으로 초기화되므로 뒤 쪽 4 bytes 0을 mutex로 이용한다.
1
2
3
4
5
6
free(p+8); // free(0x081002a0)
-> 0x08100298 : p's value
-> 0x08100000 : fake heap\_info AND ar\_ptr
-> 0x0804a010 : fake arena
-> 0x0804a010 + x + 8 : &ar->bins[0]
p
가 가리키는 곳은 chunk의 prev\_size(0x08100298)
이므로 여기에 shellcode를 넘기면 되는데 그 아래 field가 망가지면 안된다.
따라서 p
가 가리키는 곳에는 jmp instruction을 넣고, field 아래에 shellcode를 넣어 그리로 jmp할 수 있도록 구성한다.
DEAD
check를 통과하기 위해서는 \*(&bins[0] + 8) + 12
번지에 있는 값이, c &bins[0]
이어야만 한다. &bins[0] + 8
번지의 값이 가리키는 B chunk의 ` bk의 값이
&bins[0]`이어야 한다.(↖)
최종적으로 P
가 대입되는 곳(B chunk의 bk
)의 값이 &bins[0]
인 경우에만 사용할 수 있기 때문에, 사실상 사용할 수 없다. * 최종적으로 P
가 대입되는 곳을 &bins[0]
으로 만들어 주는 것은, 이미 target의 값을 조작할 수 있다는 것을 의미하므로 모순이다.
[UNDEAD] fastbin method
https://github.com/umbum/pwn/blob/master/how2heap/mind.c
https://github.com/umbum/pwn/blob/master/how2heap/mind_exploit.c
1
2
3
p->fd = \*fb;
\*fb = p;
* 소스가 조금 변경되어 \*fp = p
부분을 매크로에서 처리하는 것 같지만, 별다른 check가 새로 생긴 것 같지는 않아 아직 동작한다. (2.24)
fwd->bk = p
를 활용하는 House of Mind 방법과는 꽤나 다른 것이, 포인터를 한 번 덜 거치기 때문에아예 arena를 Execution point를 포함하는 영역으로 잡아버려야 한다.
* fastbin 처리 부분은 \_int\_free()
에서 두 가지 기본적인 check 이후 바로 등장하기 때문에 fastbin size라면 바로 fastbin 처리 부분을 수행하게 된다. * ar\_ptr = arena\_for\_chunk(p);
는 \_int\_free()
이전에 이미 구해져서 넘어오기 때문에 fastbin도 다른 arena를 참조하도록 하는 것이 가능하다.
사실상 arena가 다음과 같이 구성되어 있을 때 fastbins[idx]
에 값을 쓰게 되는 건데, mutex == 00000000
이어야만 하며, system\_mem
도 너무 작으면 안된다. (원래는 max\_fast
도 신경 써주어야 하지만 버전이 올라가면서 flag
로 변경되었다.)
mutex | ||
flag | max_fast ( old version ) | |
fastbinsY[0] | ||
… | ||
system_mem ( av+1848 ) |
따라서 Execution point 근처에서 두 가지 정도만 만족한다면 사용할 수 있다.
그러나 문제는 이 방법을 이용해 Execution point에 대입할 수 있는 값은 heap addr인 p
(chunk’s addr) 뿐인데 , heap에 X 권한이 없다. ( 원하는 값을 아무거나 쓸 수는 없다.“arena에 p
만” 쓸 수 있다. / NX가 해제되어 있어도 보통 stack에만 X권한 있고 heap에는 없다. ) 따라서 Execution point의 값이 p
로 변경된다고 해도 p
위치에 있는(mchunkptr) instruction을 실행할 수 없다는게 문제.
그래서 instruction까지 가기 위해 포인터를 한 번 더 거치는 Execution point에만 사용할 수 있다. GOT에는 &func
가 들어가기 때문에, p
위치에 바로 instruction이 있어야 해서 사용 불가. func@plt + 2
에 대입하는 방법도 생각해 볼 수 있겠지만, 여기는 쓰기 권한이 없다.
따라서 double function pointer 정도로 사용처가 제한 되기 때문에, 상당히 적용하기가 그렇다.