Task?
- 리눅스의 태스크는 프로그램의 실행단위를 나타낸다.
- 사용자가 어떤 프로그램을 실행하면, 태스크가 생성되고 프로그램의 코드가 실행된다.
- 리눅스에서는 하나의 프로세스 및 스레드가 각각의 태스크로 구성된다.
- 많은 현대 운영체제는 여러개의 태스크를 생성해두고, 각각에 실행 시간을 배분하는 방식으로 태스크를 관리한다.
- 여기서 다수의 태스크를 관리하는 것을 멀티 태스킹, 실행시간을 배분하는 것을 스케줄링이라 한다..!
- 각 태스크는 커널 메모리에 task_struct구조체로 표현되는데, 여기에 태스크의 여러 정보가 저장되어 있다.
- 그 중에는 사용자 신원 및 권한과 관련된 정보도 있어서, 이를 조작하는 것이 커널 익스플로잇의 주된 목표가 되기도 한다.
리눅스 권한
- 리눅스는 운영체제의 사용자를 User ID(UID)라는 32비트 정수값으로 식별하며, 명시적으로 허용하지 않는 이상 각 사용자는 다른 사용자의 자원에 접근이 불가하다.
- 이런 권한 분리에서 특권을 가지는 사용자가 root이다. root는 UID값이 0인 최고 관리자이며, 다른 사용자의 자원에 임의로 접근할 수 있다.
- 따라서 일반 사용자의 권한에서 root의 막강한 권한을 획득하는 것은 커널 익스플로잇의 주된 목적 중 하나이다.
- 이를 Local Privilege Escalation이라고 하며 줄여서 LPE라고 부른다.
- 태스크 구조체는 커널메모리에 위치한다.
- 일반유저가 사용하는 셸 프로그램의 태스크 구조체 신원을 적절히 조작할 수 있다면, 셸을 사용하는 사용자는 root유저가 될 수 있다.
- 이러한 위험성 때문에 리눅스는 커널 메모리를 엄격하게 관리하고 있으며, 오직 커널 권한의 프로세스만이 커널 메모리를 읽거나 조작할 수 있게 한다.
- root권한의 프로세스도 커널 메모리에 접근하는 것은 금지되어 있다.
커널 익스플로잇을 통한 권한 상승 공격 뿐 아니라 시스템에 rootkit설치, 커널 함수 후킹, 하드웨어 조작등 다양한 공격을 수행할 수 있다.
태스크 구조체 : 기본 정보
- 리눅스 커널은 task_struct구조체에 관련된 정보를 담고 있다.
- task_struct구조체는 커널 메모리 내에 존재하며, 프로세스의 매모리 맵, 파일 디스크립터, 프로세스 권한 등의 정보를 저장한다.
- task_struct구조체는 리눅스 커널 소스 내 include/linux/sched.h내에 정의되어 있다.
struct task_struct {
...
volatile long state;
...
struct list_head tasks;
...
struct mm_struct *mm;
...
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
...
char comm[TASK_COMM_LEN];
...
/* Open file information: */
struct files_struct *files;
...
}
- 위는 task_struct구조체의 일부이다.
이름 | 설명 |
state | 현재 태스크의 실행 상태. 0은 실행 중이거나 실행 가능한 상태(스케줄)를 나타내며, 양수 값은 태스크가 정지되었거나 대기 중임을 나타낸다. |
tasks | 커널에 존재하는 태스크의 연결 리스트 노드이다. |
mm | mm_struct는 사용자 메모리 영역(주소 공간)에 관한 정보를 가지고 있는 구조체이다. 일반적으로 같은 프로세스 내의 스레드는 모두 mm이 같다. |
cred | 현재 태스크의 신원정보를 가리키는 포인터이다. |
comm | 실행 파일 또는 스레드의 이름을 저장한다. |
files | 열린 파일 디스크립터 정보를 가지고 있다. 일반적으로 같은 프로세스 내의 스레드는 모두 files가 같다. |
태스크 구조체: cred
- task_struct필드 중 권한 상승을 목표로 할 때 보아야 할 필드는 cred필드이다.
- cred구조체는 리눅스 커널 소스 내 include/linux/sched.h에 정의되어 있다.
struct cred {
atomic_t usage;
...
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
...
}
- 위는 cred구조체의 일부이다.
이름 | 설명 |
usage | cred참조 카운터이다. 하나의 cred구조체는 여러 개의 프로세스에서 동시에 사용될 수 있다. |
uid | 프로세스를 소유하고 있는 사용자 ID를 저장한다. 0으로 덮어쓰게 되면 해당 태스크는 seteuid(0)로 root권한을 획득할 수 있다. |
euid | 실효적인 사용자 ID를 저장한다. 권한 검사에 실제 사용되는 값을 저장하며, 0으로 덮어쓰면 해당 태스크는 root권한을 획득하게 된다. 일반적으로 uid와 같은 값을 가진다. |
gid, egid | 각각 Real GID와 Effective GID를 저장한다. GID는 group ID로 사용자 그룹 식별번호이다. |
태스크 조회
- 저번 이미지를 이용하여 task_struct 구조체를 살펴보고 gdb를 이용하여 권한 상승이 어떻게 이루어지는지 알아보자.
- vmlinux-gdb.py를 사용하면, task_struct 디버깅에 유용한 함수들을 사용할 수 있다.
함수명 | 설명 |
$lx_current() | 선택된 CPU코어의 현재 프로세스 또는 스레드의 태스크 구조체를 반환한다. |
$lx_task_by_pid(<PID>) | PID가 <PID>인 프로세스 또는 스레드의 태스크 구조체를 반환한다. |
- QEMU로 VM을 켠 뒤 gdb로 붙는다.
- 그 후 게스트에서 현재 쉘의 PID를 확인한다.
- 위와 같이 vmlinux-gdb.py의 함수를 사용하여 gdb에서 셸 태스크의 task_struct를 조회할 수 있다.
- 해당 프로세스의 이름이 bash이고, UID가 1000(0x3e8)인 것을 확인 할 수 있다.
권한 상승
- 이제 gdb로 EUID가 1000인 쉘 태스크의 EUID값을 0으로 수정해보자.
- 게스트로 돌아가서 id명령어를 실행해보면 현재 사용자의 euid가 0으로 바뀐것을 확인할 수 있다.
- 이 상태로 root권한으로만 읽을 수 있는 /etc/shadow파일을 읽어보면, 성공적으로 읽히는 것을 볼 수 있다.
- 지렸다...
'Pwn > Kernel Security' 카테고리의 다른 글
[Pwn] Dreamhack - 6.Mitigation: KASLR (0) | 2024.01.07 |
---|---|
[Pwn] Dreamhack - 5.Exploit Tech: prepare & commit (1) | 2024.01.07 |
[Pwn] Dreamhack - 3.Background: Kernel Debugging (0) | 2024.01.06 |
[Pwn] Dreamhack - 2.Tool: QEMU (0) | 2024.01.05 |
[Pwn] Dreamhack - 1.Introduction: Linux Kernel Exploit (1) | 2024.01.05 |