libc > 2.25 이후부터 적용된 heap 관리 방법이며, 2.29부터는 패치 되었다고 합니다.
두번의 32 byte heap 영역 할당을 통해 아래와 같이 데이터가 들어있다고 가정하자.
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 주소가 저장된다.
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만 변조하고
gef➤ x/40gx 0x602010
...
0x6022d0: 0x6363636363636363 0x000000000060200a
...
다시 free해보면 free가 가능한 것을 확인 할 수 있고, tcache 첫번째 8byte 내에 2가 입력되어 있는 것으로 청크 2개가 free된 것으로 인식하고 있는 것을 알 수 있습니다.
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 한 뒤 상태는 아래와 같습니다.
gef➤ x/40gx 0x602010
0x602010: 0x0000000000010000 0x0000000000000000
...
0x602090: 0x0000000000000000 0x00000000006022a0
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x0000000000000602 0x0000000000602010
0x6022b0: 0x6161616161616161 0x0061616161616161
0x6022c0: 0x0000000000000000 0x0000000000020d41
...
이후 bk 영역의 값을 수정하고
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6262626262626262 0x6363636363636363 #여기 8byte가 bk?
0x6022b0: 0x616161616161610a 0x0061616161616161
0x6022c0: 0x0000000000000000 0x0000000000020d41
다시 heap 할당을 해보면
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 모양은 아래와 같습니다.
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 모양은 아래와 같습니다.
gef➤ x/40gx 0x602010
0x602010: 0x0000000000010000 0x0000000000000000
...
0x602090: 0x0000000000000000 0x6262626262626460 #여긴가..?
...
0x602290: 0x0000000000000000 0x0000000000000031
0x6022a0: 0x6363636363636363 0x6363636363636363
0x6022b0: 0x6363636363636363 0x0063636363636363
0x6022c0: 0x0000000000000000 0x0000000000020d41
여기서 만약 heap 할당을 한번 더 하면, tcache에 값이 남아있기에 해당 주소에 값을 쓰게 될 것입니다.