[포너블] Background: ptmalloc2

Chris Kim·2024년 10월 24일

시스템해킹

목록 보기
23/33

출처 : 드림핵 강의

0. 서론

Memory Allocator: 한정된 메모리 자원을 각 프로세스에 효율적으로 배분한다. 이를 위해 사용하는 알고리즘은 운영체제마다 다르다.
리눅스 - ptmalloc2
구글 - tcmalloc
페이스북, 파이어폭스 - jemalloc

ptmalloc2는 해제된 메모리의 특징을 기억하고 비슷한 메모리의 할당 요청이 발생하면 빠르게 반환해준다. 이 점을 이용한 공격기법이 나왔고, 이를 막기위한 새로운 보호 기법이 생겨났다. 따라서 Glibc의 버전에 따라 적용할 수 있는 공격 기법에 큰 차이점이 있다. 본 문서는 Ubuntu 18.04 64-bit(Glibc 2.27버전) 환경을 기준으로 취약점과 공격 기법을 다룬다.

1. 실습환경 Dockerfile

FROM ubuntu:18.04

ENV PATH="${PATH}:/usr/local/lib/python3.6/dist-packages/bin"
ENV LC_CTYPE=C.UTF-8

RUN apt update
RUN apt install -y \
    gcc \
    git \
    python3 \
    python3-pip \
    ruby-full \
    sudo \
    tmux \
    vim \
    wget

# install pwndbg
WORKDIR /root
RUN git clone https://github.com/pwndbg/pwndbg
WORKDIR /root/pwndbg
RUN git checkout 2023.03.19
RUN ./setup.sh

# install pwntools
RUN pip3 install --upgrade pip
RUN pip3 install pwntools

# install one_gadget command
RUN gem install one_gadget

WORKDIR /root


ruby 충돌 문제로 드림핵에서 주어진 파일을 조금 수정했다. ruby-full을 설치하자.

3. ptmalloc2

3.1 개념

p2malloc2(pthread malloc 2)는 Wolfram Gloger가 개발한 Memory Allocator다. 이는 Doug Lea의 dlmalloc을 개선한 ptmalloc의 두 번째 버전이다.(개선의 개선...)
ptmalloc2의 구현 목표는 효율적인 메모리 관리다.
1. 메모리 낭비 방지
2. 빠른 메모리 재사용
3. 메모리 단편화 방지

3.2 메모리 낭비 방지

ptmalloc는 메모리 할당 요청이 발생하면 먼저 해제된 메모리 공간 중 재사용 가능한 공간을 탐색하고, 요청된 크기와 같은 메모리 공간을 재사용한다. 혹은 매우 큰 메모리 공간을 나누어주기도 한다.

3.3 빠른 메모리 재사용

해제한 메모리 공간의 주소를 tchae 또는 bin이라는 연결 리스트에 해제된 공간의 정보를 저장해둔다. tchae와 bin은 여러 개가 정의되어있으며 서로 다른 크기의 메모리 공간들을 저장하고 있다.

3.4 메모리 단편화 방지

내부 단편화: 할당한 메모리 공간의 크기에 비해 실제 데이터가 점유하는 공간이 적은 경우에 발생
외부 단편화: 할당한 메모리 공간들 사이에 공간이 많아서 발생하는 비효율을 의미

이러한 단편화를 줄이기 위해 정렬(Allignment)와 병합(Coalescence) 그리고 분할(Split)을 사용한다. 64비트 환경에서 ptmalloc2는 메모리 공간을 16바이트 단위로 할당해준다.
공간 정렬을 하지않는다면 모든 데이터가 연속적으로 할당되어 외부 단편화를 최소화 할 수 있을 것 같으나, 공간을 재사용할 때에는 완전히 같은 크기보다는 비슷한 크기의 공간을 요청받을 확률이 더 크다. 따라서 비슷한 크기 요청에 대해서는 같은 크기 공간을 반환하여 재사용률을 높이고 외부 단편화를 줄일 수 있다. 그 외에도 공간을 병합, 분할하기도 한다.

4. ptmalloc의 객체

4.1 청크(chunk)

청크란 ptmalloc이 할당한 메모리 공간을 의미한다. 청크는 헤더와 데이터로 구성된다. 헤더는 청크 관리에 필요한 정보를, 데이터 영역은 사용자 입력 데이터를 담고 있다.(flag는 size하위 3비트다)

4.2 bin

사용이 끝난 청크들이 저장되는 객체. ptmalloc에는 128개의 bin이 정의되어 있다. 62개는 smallbin, 63개는 largebin, 1개는 unsortedbin으로 사용되며, 2개는 사용되지 않는다.

4.3 smallbin

32바이트 이상, 1024 바이트 미만 크기를 갖는 청크들이 보관된다. 하나의 smallbin에는 같은 크기의 청크들만 보관되며, index가 증가하면 청크들의 크기가 16바이트씩 커진다.
smallbin은 원형 이중 연결 리스트(circular doubly-linked list)이며, 먼저 해제된 청크가 먼저 재할당 된다.(FIFO)
청크를 추가할 때에는 이 원형 연결 리스트를 끊는 과정이 필요하며 이를 ptmalloc은 unlink라고 부른다. 메모리상 인접한 두 청크가 해제되어있고, smallbin에 있으면 이 둘은 병합된다.(consolidation)

4.4 fastbin

일반적으로 작은 청크들이 큰 청크들보다 더 자주 할당되고 해제된다. 어떤 크기 이하의 청크는 smallbin이 아닌 fastbin에 저장된다. 여기에는 32바이트 이상 176바이트 이하 크기의 청크들이 보관된다. 16바이트 단위로 10개의 fastbin이 있고 리눅스는 작은 크기부터 7개의 fastbin만을 사용한다. 즉 리눅스는 32~128 바이트의 청크들을 fastbin에 저장한다.
fasbin은 단일 연결리스트다. 따라서 청크를 꺼낼때에는 unlink 과정이 필요없다. 또한 LIFO 방식으로 사용된다. 여기서 병합도 일어나지 않는다.

4.5 largebin, unsortedbin, arena

largebin에는 1024 바이트 이상의 청크들이 보관된다. 여기에는 일정범위 안의 크기를 갖는 청크들을 모두 보관한다. 이 범위는 인덱스 증가에 따라 로그적으로 증가한다. 여기에서 재할당 요청이 발생하는 경우 가장 비슷한 청크를 빠르게 꺼내기 위해, largebin 내 청크를 크기 내림차순으로 정렬한다. largebin은 이중 연결 리스트다.

unsortedbin은 분류되지 않은 청크를 보관한다. 단 하나만 존재하며 크기를 구분하지 않는다. 이중 연결 리스트이며, 내부 정렬을 일어나지 않는다.
smallbin 크기 청크가 요청되면 fastbin, smallbin, unsortedbin 순으로, largebin 크기 청크가 요청되면 unsortedbin부터 탐색한다.

arena는 fastbin, smallbin, largebin 등의 정보를 모두 담고 있는 객체다. 멀티 쓰레드 환경에서 ptmalloc은 레이스 컨디션을 막기 위해 arena 접근시 락을 적용한다.
하지만 병목현상이 일어날 수 있으므로 64개의 arena를 생성할 수 있게 만들어서 병목현상을 회피하고자 한다. 하지만 이도 64개로 제한되어 병목 현상이 발생하게 되어, tchae가 추가 되었다.
이러한 락과 관련된 자세한 내용은 이 글을 참고하면 된다.

4.6 tchae

tchae는 thread local cache의 약자다. 즉 각 쓰레드의 캐시 저장소를 말한다. 이는 glibc 2.26에 도입되었다.
각 쓰레드는 64개의 tchae를 가지며, fastbin과 마찬가지로 LIFO 방식으로 사용되는 단일 연결 리스트다. 하나의 tchae에는 같은 크기의 청크들만 보관한다. 각 tchae에는 7개의 청크가 보관될 수 있다.(리눅스 기준)
여기에는 32바이트 이상, 1024 바이트 이하의 청크들이 보관된다. 이 범위 내의 청크들은 할당/해제시, tchae를 가장 먼저 조회한다. tchae가 가득차면 적절한 bin으로 분류된다.
tchae는 레이스 컨디션을 고려하지 않아도 된다.(쓰레드 별로 독립적으로 가지고 있으므로) 하지만 보안 검사가 많이 생략되어 힙 익스플로잇의 좋은 도구로 활용된다.
tchae를 활용한 메모리 할당 해제 과정

profile
회계+IT=???

0개의 댓글