
ASLR 우회, 정보 유출 없이
이 글에서는 Samuel Groß가 자신의 Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass에서 설명한 기법을 Linux x86_64에서 ASLR을 우회하기 위해 적용하는 방법에 대해 다루겠습니다.
이를 보여주기 위해 Buckeye CTF의 pwnable 챌린지인 guess_god를 풀어보겠습니다.
가능한 한 초보자 친화적으로 내용을 작성하려고 하니, 자신 있다면 각 섹션을 건너뛰고 익스플로잇만 보고 싶어도 괜찮습니다.

저는 CTF에 참가하지 않았지만, fibonhack 디스코드에서 Guray00이 어떤 암호학 관련 문제에 대해 도움을 요청하면서 CTF 종료 약 2시간 전에 이 챌린지에 관심을 가지게 되었습니다.
저는 그를 도와줄 수 없었지만, pwnable 챌린지들을 살펴보았고 P0 블로그 게시물을 이해하는 것이 좋겠다고 생각했으며, hopefully 그 현상금을 받을 수도 있겠다고 생각했습니다.
주소 공간 레이아웃 무작위화(ASLR) 는 프로세스의 주소 공간에서 실행 파일의 베이스 주소와 라이브러리, 힙, 스택의 위치를 무작위로 배치하는 컴퓨터 보안 기술입니다.
Linux에서는 프로세스의 pid가 주어졌을 때, /proc/<pid>/maps 파일을 읽어 해당 프로세스의 메모리 매핑을 확인할 수 있습니다. 이는 procfs를 통해 가능합니다.
여러분이 프로세스이고 자신의 메모리 매핑을 알고 싶다면 /proc/self/maps를 읽으면 됩니다.
예를 들어, cat으로 /proc/self/maps를 읽어볼 수 있습니다:```
root@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps
55faeb01c000-55faeb01e000 r--p 00000000 fe:01 2497233 /usr/bin/cat
55faeb01e000-55faeb023000 r-xp 00002000 fe:01 2497233 /usr/bin/cat
55faeb023000-55faeb026000 r--p 00007000 fe:01 2497233 /usr/bin/cat
55faeb026000-55faeb027000 r--p 00009000 fe:01 2497233 /usr/bin/cat
55faeb027000-55faeb028000 rw-p 0000a000 fe:01 2497233 /usr/bin/cat
55faeb115000-55faeb136000 rw-p 00000000 00:00 0 [heap]
7fe15dfb1000-7fe15dfd5000 rw-p 00000000 00:00 0
7fe15dfd5000-7fe15dffb000 r--p 00000000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15dffb000-7fe15e166000 r-xp 00026000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15e166000-7fe15e1b2000 r--p 00191000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15e1b2000-7fe15e1b5000 r--p 001dc000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15e1b5000-7fe15e1b8000 rw-p 001df000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so
7fe15e1b8000-7fe15e1c3000 rw-p 00000000 00:00 0
7fe15e1c7000-7fe15e1c8000 r--p 00000000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fe15e1c8000-7fe15e1ef000 r-xp 00001000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fe15e1ef000-7fe15e1f9000 r--p 00028000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fe15e1f9000-7fe15e1fb000 r--p 00031000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fe15e1fb000-7fe15e1fd000 rw-p 00033000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so
7fff4388f000-7fff438b0000 rw-p 00000000 00:00 0 [stack]
7fff43989000-7fff4398d000 r--p 00000000 00:00 0 [vvar]
7fff4398d000-7fff4398f000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
root@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps 55ffc0b1b000-55ffc0b1d000 r--p 00000000 fe:01 2497233 /usr/bin/cat 55ffc0b1d000-55ffc0b22000 r-xp 00002000 fe:01 2497233 /usr/bin/cat 55ffc0b22000-55ffc0b25000 r--p 00007000 fe:01 2497233 /usr/bin/cat 55ffc0b25000-55ffc0b26000 r--p 00009000 fe:01 2497233 /usr/bin/cat 55ffc0b26000-55ffc0b27000 rw-p 0000a000 fe:01 2497233 /usr/bin/cat 55ffc2108000-55ffc2129000 rw-p 00000000 00:00 0 [heap] 7f1ec6e0f000-7f1ec6e33000 rw-p 00000000 00:00 0 7f1ec6e33000-7f1ec6e59000 r--p 00000000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec6e59000-7f1ec6fc4000 r-xp 00026000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec6fc4000-7f1ec7010000 r--p 00191000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7010000-7f1ec7013000 r--p 001dc000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7013000-7f1ec7016000 rw-p 001df000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7016000-7f1ec7021000 rw-p 00000000 00:00 0 7f1ec7025000-7f1ec7026000 r--p 00000000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7026000-7f1ec704d000 r-xp 00001000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec704d000-7f1ec7057000 r--p 00028000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7057000-7f1ec7059000 r--p 00031000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7059000-7f1ec705b000 rw-p 00033000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7ffc72fa4000-7ffc72fc5000 rw-p 00000000 00:00 0 [stack] 7ffc72fe7000-7ffc72feb000 r--p 00000000 00:00 0 [vvar] 7ffc72feb000-7ffc72fed000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
### 메모리 매핑 패턴
이 작업을 몇 번 반복하다 보면 다음을 추론할 수 있습니다:
* 바이너리 PIE 베이스는 0x00005500_00000000-0x00005700_00000000 범위에 있어야 하며, 즉 가능한 주소는 2TB입니다.
* 힙은 바이너리 근처에 있습니다.
* 라이브러리는 0x00007f00_00000000 - 0x00007fff_ffffffff 범위에 있으며, 가능한 주소는 1TB입니다.
* 스택은 (대부분의 경우) 0x00007ffc_00000000 - 0x00007fff_ffffffff 범위에 있으며, 가능한 주소는 16gb입니다.
* 0xffffffffff600000 - 0xffffffffff601000 범위는 항상 매핑되어 있습니다. 그것이 무엇인지 궁금하다면 [이 문서](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso)를 읽어보세요.
## 1.3 정보 누출 없이 ASLR 우회하기
정보 누출이 불가능할 때 ASLR을 우회하기 위해 무엇을 할 수 있는지 논의해 보겠습니다.
이 글은 Saelo의 블로그 포스트에서 얻은 내용을 요약한 것입니다.
ASLR을 우회하려면 다음이 필요합니다:
* 지정된 주소 범위에 원하는 크기의 연속 메모리를 매핑할 수 있는 메모리 스프레이 기법.
그가 말했듯이 이를 수행하는 방법은 두 가지입니다:
1. 메모리 누수(정보 누출이 아니라!)를 악용하는 방법. 메모리 청크가 “잊혀져” 해제되지 않는 버그를 여러 번 트리거하여 원하는 만큼의 메모리가 누출될 때까지 반복하는 방식입니다.
2. “증폭 가젯”을 찾아 악용하는 방법. 기존 데이터 청크를 가져와 잠재적으로 여러 번 복사하는 코드 조각으로, 공격자가 상대적으로 적은 수의 바이트만 전송해도 대량의 메모리를 스프레이할 수 있습니다.
* `isAddressMapped` 오라클. 주소가 주어지면 해당 주소가 매핑되어 있는지 여부를 알려줍니다.
### Linux에서 ASLR 우회 PoC
Linux에서 ASLR을 완전히 깨는 saelo의 PoC를 재현해 봅시다.
<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/93fcc6aff8ffd2e8395f34b35bc2f5d244e74a53b2fac39c3dae35c802227339.png" > <i> saelo의 PoC</i> <p/> <br/>
Linux에서는 그렇게 쉽지 않습니다. 16TB의 메모리를 할당할 수 있어야만 ASLR을 완전히 깨는 것이 가능합니다.```C
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 64gb
size_t size = 0x1000000000;
// 16TB allocations
for (int i = 0; i < 256; i++) {
void *mem = malloc(size); // this ends up calling mmap
if (!mem) {
puts("Failed");
return 1;
}
printf("%p\n", mem);
}
unsigned int *mem = (void*)0x7f0000000000ULL;
*mem = 0x41414141;
printf("R/W to %p: %x\n", mem, *mem);
return 0;
}
man malloc 문서의 참고 사항:
따라서 void *mem = malloc(size)는 결국 mmap(size + malloc_metadata_size, ...)을 호출하게 됩니다.
라이브러리는 ld에 의해 mmap을 통해 프로세스에 매핑되므로 이러한 할당은 결국 라이브러리 근처에 위치하게 됩니다.