0 views
들어가며
1. 서론

- 원룸을 임대하여 거주하다가 계약이 만료될 경우, 세입자는 임대인에게 원룸 접근 권한을 반납해야 한다.
- 열쇠로 문을 열어왔다면 열쇠를 돌려주고, 도어락을 사용한다면 도어락 비밀번호를 재설정해줘야 한다.
- 그러면 임대인은 원룸을 청소하고 다시 세입자를 구한다.
- 만약 이전 세입자의 접근 권한을 회수하지 않는다면, 계약이 끝난 뒤에도 그 원룸은 무단으로 사용될 수 있다.
- 또한, 만약 방을 깨끗이 청소하지 않아서 이전 세입자의 개인 정보가 적혀있는 문서가 남는다면, 다음 세입자가 이전 세입자의 개인 정보를 알게 될 위험도 있다.
- 따라서 “접근 권한 회수”와 “깨끗한 청소”를 마치고 난 뒤에 새 임대인을 구하는 것이 바람직하다.
ptmalloc2를 이용하여 메모리를 관리할 때도 이런 과정에 주의를 기울이지 않으면 비슷한 문제가 발생할 수 있다.- 이번 강의에서 배울 Use-After-Free는 메모리 참조에 사용한 포인터를 메모리 해제 후에 적절히 초기화하지 않아서, 또는 해제한 메모리를 초기화하지 않고 다음 청크에 재할당해주면서 발생하는 취약점이다.
- 이 취약점은 현재까지도 브라우저 및 커널에서 자주 발견되고 있으며, 익스플로잇 성공률도 다른 취약점에 비해 높아 상당히 위험하다고 알려져 있다.
- 이번 강의에서는 Use-After-Free의 원인과 취약점이 발생하는 코드의 패턴, 그리고 공격자의 관점에서 해당 취약점을 이용했을 때 얻을 수 있는 효과에 대해 배워보겠다.
2. 실습 환경 Dockerfile
- Ubuntu 18.04 64-bit(Glibc 2.27) 실습 환경 구축을 위한 Dockerfile은 다음과 같다.
- 다른 버전의 우분투를 사용하는 경우, 이번 강의의 실습을 수행하는 과정이 원활하지 않을 수 있으니 반드시 우분투 18.04 64-bit 환경을 구축한 후 실습하시기를 바란다.
2.1 Ubuntu 18.04 64-bit(Glibc 2.27) 실습 환경 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 \
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
- 위 내용을
Dockerfile이라는 이름의 파일로 저장한 후, 아래의 명령어로 이미지를 빌드하고 컨테이너를 실행한 후 셸을 켤 수 있다.
2.2 도커 이미지 빌드/컨테이너 실행/셸 실행 명령어
$ IMAGE_NAME=ubuntu1804 CONTAINER_NAME=my_container; \
docker build . -t $IMAGE_NAME; \
docker run -d -t --privileged --name=$CONTAINER_NAME $IMAGE_NAME; \
docker exec -it -u root $CONTAINER_NAME bash
Use After Free
1. Dangling Pointer
- 컴퓨터 과학에서, Dangling Pointer는 유효하지 않은 메모리 영역을 가리키는 포인터를 말한다.
- 메모리의 동적 할당에 사용되는
malloc함수는 할당한 메모리의 주소를 반환한다. - 일반적으로, 메모리를 동적 할당할 때는 포인터를 선언하고, 그 포인터에
malloc함수가 할당한 메모리의 주소를 저장한다. - 그리고 그 포인터를 참조하여 할당한 메모리에 접근한다.
- 메모리의 동적 할당에 사용되는
- 메모리를 해제할 때는
free함수를 호출한다.- 그런데
free함수는 청크를ptmalloc에 반환하기만 할 뿐, 청크의 주소를 담고 있던 포인터를 초기화하지는 않는다. - 따라서
free의 호출 이후에 프로그래머가 포인터를 초기화하지 않으면, 포인터는 해제된 청크를 가리키는 Dangling Pointer가 된다.
- 그런데
- Dangling Pointer가 생긴다고 해서 프로그램이 보안적으로 취약한 것은 아니다.
- 그러나 Dangling Pointer는 프로그램이 예상치 못한 동작을 할 가능성을 키우며, 경우에 따라서는 공격자에게 공격 수단으로 활용될 수도 있다.
- 아래 코드는 Dangling Pointer의 위험성을 보이는 예제다.
1.1 Dangling Pointer
// Name: dangling_ptr.c
// Compile: gcc -o dangling_ptr dangling_ptr.c
#include <stdio.h>
#include <stdlib.h>
int main() {
char *ptr = NULL;
int idx;
while (1) {
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1:
if (ptr) {
printf("Already allocated\n");
break;
}
ptr = malloc(256);
break;
case 2:
if (!ptr) {
printf("Empty\n");
}
free(ptr);
break;
default:
break;
}
}
}
- 예제에서는 청크를 해제한 후에 청크를 가리키던
ptr변수를 초기화하지 않는다.- 따라서 다음과 같이 청크를 할당하고 해제하면,
ptr은 이전에 할당한 청크의 주소를 가리키는 Dangling Pointer가 된다.
- 따라서 다음과 같이 청크를 할당하고 해제하면,
$ gcc -o dangling_ptr dangling_ptr.c -no-pie
$ ./dangling_ptr
> 1
> 2
-
ptr이 해제된 청크의 주소를 가리키고 있으므로, 이를 다시 해제할 수 있다.$ ./dangling_ptr > 1 > 2 > 2 free(): double free detected in tcache 2 Aborted (core dumped) -
이를 Double Free Bug 라고 하는데, 프로그램에 심각한 보안 위협이 되는 소프트웨어 취약점이므로 명심해두도록 하자. 이 강의에서는 깊게 다루지는 않도록 하겠다.
2. Use After Free
- Use-After-Free (UAF)는 문자 그대로, 해제된 메모리에 접근할 수 있을 때 발생하는 취약점을 말한다.
- 앞서 살펴봤던
dangling_ptr.c와 같이 Dangling Pointer로 인해 발생하기도 하지만, 새롭게 할당한 영역을 초기화하지 않고 사용하면서 발생하기도 한다.
- 앞서 살펴봤던
malloc과free함수는 할당 또는 해제할 메모리의 데이터들을 초기화하지 않는다.- 그래서 새롭게 할당한 청크를 프로그래머가 명시적으로 초기화하지 않으면, 메모리에 남아있던 데이터가 유출되거나 사용될 수 있다.
- 아래 코드는 Use-After-Free 취약점이 있는 예제 코드다.
- 구조체
NameTag와Secret이 정의되어 있는데, 예제에서는 그 중 외부에 유출되면 안 되는Secret구조체를 먼저 할당한다. - 그리고
secret_name,secret_info,code에 값을 입력하고, 이를 해제한다.
- 구조체
2.1 Use-After-Free 취약점 예제 코드
// Name: uaf.c
// Compile: gcc -o uaf uaf.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct NameTag {
char team_name[16];
char name[32];
void (*func)();
};
struct Secret {
char secret_name[16];
char secret_info[32];
long code;
};
int main() {
int idx;
struct NameTag *nametag;
struct Secret *secret;
secret = malloc(sizeof(struct Secret));
strcpy(secret->secret_name, "ADMIN PASSWORD");
strcpy(secret->secret_info, "P@ssw0rd!@#");
secret->code = 0x1337;
free(secret);
secret = NULL;
nametag = malloc(sizeof(struct NameTag));
strcpy(nametag->team_name, "security team");
memcpy(nametag->name, "S", 1);
printf("Team Name: %s\n", nametag->team_name);
printf("Name: %s\n", nametag->name);
if (nametag->func) {
printf("Nametag function: %p\n", nametag->func);
nametag->func();
}
}
- 코드의 34 번째 줄부터는 사원의 정보를 담고 있는
nametag를 생성한다.team_name,name에 각각의 값을 입력하고, 입력한 데이터를 출력한다.- 이후에 함수 포인터
func가 NULL이 아니라면 포인터가 가리키는 주소를 출력하고, 해당 주소의 함수를 호출한다. 예제 코드의 실행 결과는 다음과 같다.
$ gcc -o uaf uaf.c -no-pie
$ ./uaf
Team Name: security team
Name: S@ssw0rd!@#
Nametag function: 0x1337
Segmentation fault (core dumped)
- 출력 결과를 살펴보면, Name으로
secret_info의 문자열이 출력되고, 값을 입력한 적 없는 함수 포인터가 0x1337을 가리키는 것을 확인할 수 있다. 이러한 결과가 나타난 이유를 자세히 알아보겠다.
3. uaf 동적 분석
ptmalloc2는 새로운 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가bin이나tcache에 있는지 확인한다.- 그리고 만약 있다면, 해당 청크를 꺼내어 재사용한다.
- 예제 코드에서
Nametag와Secret은 같은 크기의 구조체다. - 그러므로 앞서 할당한
secret을 해제하고nametag를 할당하면,nametag는secret과 같은 메모리 영역을 사용하게 된다. - 이때
free는 해제한 메모리의 데이터를 초기화하지 않으므로,nametag에는secret의 값이 일부 남아있게 된다.
- gdb를 이용하여
secret을 해제한 후secret가 사용하던 메모리 영역의 데이터를 살펴보겠다.- 먼저 gdb로
uaf바이너리를 열고,secret을 해제(free)하는 다음 명령어 부분에 중단점을 설정한 후 실행한다.
- 먼저 gdb로
$ gdb uaf
pwndbg> disass main
Dump of assembler code for function main:
...
0x0000000000400647 <+96>: mov rax,QWORD PTR [rbp-0x10]
0x000000000040064b <+100>: mov rdi,rax
0x000000000040064e <+103>: call 0x4004c0 <free@plt>
0x0000000000400653 <+108>: mov QWORD PTR [rbp-0x10],0x0
...
End of assembler dump.
pwndbg> b *main+108
Breakpoint 1 at 0x400653
pwndbg> r
Starting program: /home/dreamhack/uaf
Breakpoint 1, 0x0000000000400653 in main ()
...
───────────────────────────────────[ DISASM ]───────────────────────────────────
► 0x400653 <main+108> mov qword ptr [rbp - 0x10], 0
...
Breakpoint *main+108
-
이제
heap명령어로 할당 및 해제된 청크들의 정보를 조회해보겠다.pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x602000 Size: 0x251 Free chunk (tcachebins) | PREV_INUSE Addr: 0x602250 Size: 0x41 fd: 0x00 Top chunk | PREV_INUSE Addr: 0x602290 Size: 0x20d71 } -
총 3개의 청크가 존재하는데,
0x602250이 우리가 살펴보고자 하는secret에 해당하는 청크다.- 해제(free)되었기 때문에 tcache의 엔트리에 들어가 있는 상태다.
추가로 0x602000는 tcache와 관련된 공간으로 tcache_perthread_struct 구조체에 해당하며, libc 단에서
힙 영역을 초기화할 때 할당하는 청크다. 0x602290는 탑 청크에 해당한다.
-
다음은 이미 해제된
secret이 사용하던 메모리 영역을 출력한 모습이다.secret_name에 해당하는 부분은 적절한fd와bk값으로 초기화됐지만,secret_info에 해당하는 부분은 값이 그대로 남아있는 모습을 확인할 수 있다.
pwndbg> x/10gx 0x602250 0x602250: 0x0000000000000000 0x0000000000000041 0x602260: 0x0000000000000000 0x0000000000602010 0x602270: 0x6472307773734050 0x0000000000234021 0x602280: 0x0000000000000000 0x0000000000000000 0x602290: 0x0000000000001337 0x0000000000020d71 pwndbg> x/s 0x602270 0x602270: "P@ssw0rd!@#" pwndbg> -
다음으로,
nametag를 할당하고,printf함수를 호출하는 시점에서nametag멤버 변수들의 값을 확인해보겠다.pwndbg> b *main+207 Breakpoint 2 at 0x4006b6 pwndbg> c Continuing. Breakpoint 2, 0x00000000004006b6 in main () ... ───────────────────────────────────[ DISASM ]─────────────────────────────────── ► 0x4006b6 <main+207> call printf@plt <0x4004d0> format: 0x4007a6 ◂— 'Team Name: %s\n' vararg: 0x602260 ◂— 'security team' 0x4006bb <main+212> mov rax, qword ptr [rbp - 8] 0x4006bf <main+216> add rax, 0x10 ... Breakpoint *main+207 pwndbg> x/10gx 0x602250 0x602250: 0x0000000000000000 0x0000000000000041 0x602260: 0x7974697275636573 0x0000006d61657420 0x602270: 0x6472307773734053 0x0000000000234021 0x602280: 0x0000000000000000 0x0000000000000000 0x602290: 0x0000000000001337 0x0000000000020d71 pwndbg> x/s 0x602260 0x602260: "security team" pwndbg> x/s 0x602270 0x602270: "S@ssw0rd!@#" pwndbg> x/gx 0x602290 0x602290: 0x0000000000001337 pwndbg> -
nametag->team_name에는 “security team”이 그대로 입력되었으나,nametag->name에는 초기화되지 않은secret_info의 값이 존재하는 것을 확인할 수 있다.- 또한,
nametag->func위치에secret->code에 대입했던0x1337이 남아있는 것을 알 수 있다. - 이 값이 0이 아니므로 예제의 42번째 줄에서
nametag->func이 호출되고, Segmentation Fault가 발생한다.
- 또한,
-
예제를 통해 살펴봤듯, 동적 할당한 청크를 해제한 뒤에는 해제된 메모리 영역에 이전 객체의 데이터가 남는다.
- 이러한 특징을 공격자가 이용한다면 초기화되지 않은 메모리의 값을 읽어내거나, 새로운 객체가 악의적인 값을 사용하도록 유도하여 프로그램의 정상적인 실행을 방해할 수 있다.
마치며
1. 서론
- 이번 강의에서는 해제된 메모리의 접근을 허용함으로써 발생하는 Use-After-Free 취약점에 대해 배워보았다.
- Use-After-Free 취약점을 통해 공격자는 초기화되지 않은 메모리의 값을 읽어내거나, 새로운 객체가 악의적인 값을 사용하도록 유도할 수 있다.
1.1 키워드
- Dangling Pointer:
- 해제된 메모리를 가리키고 있는 포인터. UAF가 발생하는 원인이 될 수 있다.
- Use-After-Free (UAF):
- 해제된 메모리에 접근할 수 있을 때 발생하는 취약점이다.
'Dreamhack'카테고리의 다른 글
Loading comments...