libc > 2.25 이후부터 적용된 heap 관리 방법이며, 2.29부터는 패치 되었다고 한다.
두번의 32 byte heap 영역 할당을 통해 아래와 같이 데이터가 들어있다고 가정하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gef➤ heap chunks
Chunk(addr=0x602010, size=0x290, flags=PREV_INUSE)
[0x0000000000602010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0x6022a0, size=0x30, flags=PREV_INUSE)
[0x00000000006022a0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa]
Chunk(addr=0x6022d0, size=0x30, flags=PREV_INUSE)
[0x00000000006022d0 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 bbbbbbbbbbbbbbbb]
Chunk(addr=0x602300, size=0x20d10, flags=PREV_INUSE)
[0x0000000000602300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0x602300, size=0x20d10, flags=PREV_INUSE) ← top chunk
gef➤ x/40gx 0x602010
0x602010: 0x0000000000000000 0x0000000000000000
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6161616161616161 0x6161616161616161
0x6022b0: 0x6161616161616161 0x0061616161616161
0x6022c0: 0x0000000000000000 0x0000000000000031
0x6022d0: 0x6262626262626262 0x6262626262626262
0x6022e0: 0x6262626262626262 0x0062626262626262
0x6022f0: 0x0000000000000000 0x0000000000020d11
- 0x602010 : tcache
- 0x6022a0 : 청크 1
- 0x6022d0 : 청크 2
이후 가장 마지막 heap 영역을 free 하면 tcache에 fd 주소가 저장된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gef➤ heap chunks
Chunk(addr=0x602010, size=0x290, flags=PREV_INUSE)
[0x0000000000602010 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0x6022a0, size=0x30, flags=PREV_INUSE)
[0x00000000006022a0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa]
Chunk(addr=0x6022d0, size=0x30, flags=PREV_INUSE)
[0x00000000006022d0 02 06 00 00 00 00 00 00 10 20 60 00 00 00 00 00 ......... `.....]
Chunk(addr=0x602300, size=0x20d10, flags=PREV_INUSE)
[0x0000000000602300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0x602300, size=0x20d10, flags=PREV_INUSE) ← top chunk
gef➤ x/40gx 0x602010
0x602010: 0x0000000000010000 0x0000000000000000
...
0x602090: 0x0000000000000000 0x00000000006022d0
0x6020a0: 0x0000000000000000 0x0000000000000000
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6161616161616161 0x6161616161616161
0x6022b0: 0x6161616161616161 0x0061616161616161
0x6022c0: 0x0000000000000000 0x0000000000000031
0x6022d0: 0x0000000000000602 0x0000000000602010
0x6022e0: 0x6262626262626262 0x0062626262626262
0x6022f0: 0x0000000000000000 0x0000000000020d11
tcache의 첫 8byte에는 free된 횟수가 입력되어 있고, 이후에는 free 된 청크(0x6022d0)의 주소가 들어있다.
그리고 해제된 청크 bk에는 tcache의 시작 주소가 입력되어 있다.
만약 heap 영역을 다시 할당받고 데이터를 넣으려고 하면 0x6022d0이 tcache에 있기 때문에 이 청크부터 할당 될 것이다. 즉, tcache는 다음 heap 영역 할당 시 사용될 청크 주소를 저장하는 곳인걸 알 수 있다.
tcache에서는 free시 double free를 확인하기 위해 tcache 영역에 저장된 해제된 청크 주소와 bk 값을 비교한다.
만일 bk 값을 변조가 가능하다면 어떻게 될까
아래와 같이 현재 bk 값인 0x602010에서 1byte만 변조하고
1
2
3
4
gef➤ x/40gx 0x602010
...
0x6022d0: 0x6363636363636363 0x000000000060200a
...
다시 free해보면 free가 가능한 것을 확인 할 수 있고, tcache 첫번째 8byte 내에 2가 입력되어 있는 것으로 청크 2개가 free된 것으로 인식하고 있는 것을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gef➤ x/40gx 0x602010
0x602010: 0x0000000000020000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
...
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x00000000006022d0
0x6020a0: 0x0000000000000000 0x0000000000000000
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6161616161616161 0x6161616161616161
0x6022b0: 0x6161616161616161 0x0061616161616161
0x6022c0: 0x0000000000000000 0x0000000000000031
0x6022d0: 0x00000000006024d2 0x0000000000602010
0x6022e0: 0x6262626262626262 0x0062626262626262
0x6022f0: 0x0000000000000000 0x0000000000020d11
만약 최초 free 및 bk 값 변경 후 다시 청크를 할당받고 값을 넣으려하면 어떻게 될까
32byte 청크 할당 및 데이터를 넣은 후 free 한 뒤 상태는 아래와 같다.
1
2
3
4
5
6
7
8
9
10
gef➤ x/40gx 0x602010
0x602010: 0x0000000000010000 0x0000000000000000
...
0x602090: 0x0000000000000000 0x00000000006022a0
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x0000000000000602 0x0000000000602010
0x6022b0: 0x6161616161616161 0x0061616161616161
0x6022c0: 0x0000000000000000 0x0000000000020d41
...
이후 bk 영역의 값을 수정하고
1
2
3
4
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6262626262626262 0x6363636363636363 #여기 8byte가 bk?
0x6022b0: 0x616161616161610a 0x0061616161616161
0x6022c0: 0x0000000000000000 0x0000000000020d41
다시 heap 할당을 해보면
1
2
3
4
5
6
7
8
9
gef➤ x/40gx 0x602010
0x602010: 0x0000000000000000 0x0000000000000000
...
0x602090: 0x0000000000000000 0x6262626262626460
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6464646464646464 0x6464646464646464
0x6022b0: 0x6464646464646464 0x0064646464646464
0x6022c0: 0x0000000000000000 0x0000000000020d41
tcache에 청크의 값이 일부 삽입된 것을 볼 수 있으며 이는 다음에 heap 영역이 할당될 주소를 가지고 있음을 의미한다.
다만 위의 값에서는 tcache의 첫 8byte가 0이기에 tcache 내에 다음에 사용할 주소 값이 있어도 사용하지는 않는다.
만일 여기서 tcache 첫 8byte에 값이 1이상으로 남아있다면 어떻게 될까
heap 할당 -> free -> bk 변조 -> free -> bk 변조 후 heap 모양은 아래와 같다.
1
2
3
4
5
6
7
8
9
gef➤ x/40gx 0x602010
0x602010: 0x0000000000020000 0x0000000000000000
...
0x602090: 0x0000000000000000 0x00000000006022a0
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6262626262626262 0x6262626262626262
0x6022b0: 0x6262626262626262 0x0062626262626262
0x6022c0: 0x0000000000000000 0x0000000000020d41
이후 heap 할당을 1회 하면 heap 모양은 아래와 같다.
1
2
3
4
5
6
7
8
9
gef➤ x/40gx 0x602010
0x602010: 0x0000000000010000 0x0000000000000000
...
0x602090: 0x0000000000000000 0x6262626262626460 #여긴가..?
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6363636363636363 0x6363636363636363
0x6022b0: 0x6363636363636363 0x0063636363636363
0x6022c0: 0x0000000000000000 0x0000000000020d41
여기서 만약 heap 할당을 한번 더 하면, tcache에 값이 남아있기에 해당 주소에 값을 쓰게 된다.