[Research] Linux Kernel Basic - (2) 태스크(task)

[Research] Linux Kernel Basic - (2) 태스크(task)

서론

지난 포스트에서는 리눅스 커널 공격(Linux Kernel Exploitation)을 진행하기 위해서는 리눅스 커널(Linux Kernel)에 대해 간략히 언급하였다. 해당 포스트에서는 운영체제(Operating System)에서 다뤄지는 프로세스(prcoess)와 스레드(thread)를 정의하는 구조인 태스크(task)를 커널 소스코드 분석을 통해 심도있게 이해하고 해당 과정을 통해 어떠한 방식으로 권한확대(Local Privilege Escalation)를 일으킬 수 있는 가에 관한 인사이트를 얻기 위한 연구를 진행하였다.

프로세스(proces) 및 스레드(thread)

$ ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 01:50 ?        00:00:02 /sbin/init auto noprompt
root          2      0  0 01:50 ?        00:00:00 [kthreadd]
root          3      2  0 01:50 ?        00:00:00 [rcu_gp]
root          4      2  0 01:50 ?        00:00:00 [rcu_par_gp]
root          6      2  0 01:50 ?        00:00:00 [kworker/0:0H-kb]
root          9      2  0 01:50 ?        00:00:00 [mm_percpu_wq]
root         10      2  0 01:50 ?        00:00:00 [ksoftirqd/0]
root         11      2  0 01:50 ?        00:00:00 [rcu_sched]
root         12      2  0 01:50 ?        00:00:00 [migration/0]
root         13      2  0 01:50 ?        00:00:00 [idle_inject/0]

리눅스를 사용하다보면 다음과 같은 명령어를 통해 프로세스의 정보를 확인해야하는 경우가 발생한다. 프로세스의 정보를 확인하면 어떠한 프로세스인지, 프로세스의 번호는 몇번인지, 어떠한 권한으로 동작을 진행하는지에 관한 정보들을 확인할 수 있다. 리눅스에서는 프로세스에 종속되어 새로운 실행의 흐름을 생성하는 스레드(thread)의 경우에도 하나의 프로그램의 실행단위로 볼 수 있으며 이러한 단위를 태스크(task)라고 한다.

태스크(task)

리눅스 운영체제의 경우 컴퓨터가 해야할 작업의 단위인 여러 개의 태스크(task)를 생성하며 다수의 태스크를 관리(multitasking)하며 이러한 태스크의 실행 시간을 배분하는 방식(scheduling)을 통해 사용자가 다수의 프로그램을 정상적으로 사용할 수 있도록 한다. 태스크를 관리하기 위해서는 여러가지 정보가 필요하다. 이는 앞서 제시하였던 명령어의 결과로 일부를 확인할 수 있다. 이러한 정보는 task_struct 구조체에 정의되어 있으며 해당 구조체는 <include/linux/sched.h>에서 확인할 수 있다.

task_struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct task_struct {
...
unsigned int __state;
struct list_head tasks;
struct mm_struct *mm;
pid_t pid;
pid_t tgid;
struct task_struct __rcu *real_parent;
struct task_struct __rcu *parent;
const struct cred __rcu *cred;
char comm[TASK_COMM_LEN];
struct files_struct *files;
...
}
속성(property) 설명(description)
__state 해당 태스크의 실행상태를 나타낸다. 0은 실행 중이거나 실행 가능한(ready) 상태를 표현한다. 0보다 큰 값의 경우는 정지 또는 대기 상태를 표현한다.
tasks 태스크의 연결 노드를 의미한다.
mm mm_struct는 유저의 메모리 영역(주소공간)에 관한 정보를 나타내는 구조체이다. 해당 태스크의 주소공간의 정보를 나타낸다.
pid 프로세스의 식별값을 의미한다.
tgid 스레드 그룹의 식별값을 의미한다.
real_parent 해당 태스크를 생성한 부모 태스크를 나타낸다.
parent 해당 태스크의 현재 부모 태스크를 나타낸다.
cred 해당 태스크의 신원 정보를 나타낸다.
comm[TASK_COMM_LEN] 해당 태스크의 이름을 나타낸다.
files 해당 태스크에서 열린 파일 디스크립터 정보를 나타낸다.

mm_struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct task_struct {
...
unsigned int __state;
struct list_head tasks;
struct mm_struct *mm;
pid_t pid;
pid_t tgid;
struct task_struct __rcu *real_parent;
struct task_struct __rcu *parent;
const struct cred __rcu *cred;
char comm[TASK_COMM_LEN];
struct files_struct *files;
...
}
속성(property) 설명(description)
__state 해당 태스크의 실행상태를 나타낸다. 0은 실행 중이거나 실행 가능한(ready) 상태를 표현한다. 0보다 큰 값의 경우는 정지 또는 대기 상태를 표현한다.
tasks 태스크의 연결 노드를 의미한다.
mm mm_struct는 유저의 메모리 영역(주소공간)에 관한 정보를 나타내는 구조체이다. 해당 태스크의 주소공간의 정보를 나타낸다.
pid 프로세스의 식별값을 의미한다.
tgid 스레드 그룹의 식별값을 의미한다.
real_parent 해당 태스크를 생성한 부모 태스크를 나타낸다.
parent 해당 태스크의 현재 부모 태스크를 나타낸다.
cred 해당 태스크의 신원 정보를 나타낸다.
comm[TASK_COMM_LEN] 해당 태스크의 이름을 나타낸다.
files 해당 태스크에서 열린 파일 디스크립터 정보를 나타낸다.

cred

1
2
3
4
5
6
7
8
9
10
11
12
13
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 */
...
}
속성(property) 설명(description)
usage 몇 개의 프로세스가 해당 cred 구조체를 참조하고 있는 지 나타낸 정보이다.
uid 프로세스를 소유하고 있는 사용자의 ID를 나타낸 정보이다. 0일 경우 최고관리자 권한을 의미한다.
euid 프로세스의 실효적인 사용자 ID를 나타낸 정보이다. 권한 검사를 할 경우 실제 사용되는 값을 저장한다. 일반적으로 uid와 같은 값을 가진다.
gid 프로세스를 소유하 고 있는 그룹의 ID 정보를 나타낸다.
egid 프로세스의 실효적인 그룹의 ID 정보를 나타낸다.

credential 정보 수정을 통한 권한 상승

앞서 진행한 과정을 통해 프로세스와 스레드는 태스크라는 단위로 관리되어 있고 태스크의 정보 속성을 관리하는 주체가 있으며 신원정보는 cred라는 구조체에서 관리되고 있음을 알 수 있다. 그렇다면 태스크의 신원정보를 변경하였을 경우 권한 상승(Local Privilege Escalation)이 이뤄질 수 있는 지 확인하겠다.

해당 실습은 Dreamhack에서 제시하는 실습 파일을 이용하여 진행하였음을 밝힌다.

dreamhack@dh-lke:~$ whoami
dreamhack
dreamhack@dh-lke:~$ cat /etc/shadow
cat: /etc/shadow: Permission denied
dreamhack@dh-lke:~$ echo $$
295
dreamhack@dh-lke:~$ 
.
.
.

qemu를 이용하여 터미널을 실행하였을 경우 다음과 같이 root 권한이 아니기 때문에 /etc/shadow 파일을 볼 수 없는 것을 확인할 수 있으며 해당 쉘의 프로세스 아이디(PID)는 295로 확인된다.

pwndbg> x/bs $lx_task_by_pid(295).comm
0xffff88803e348a98:    "bash"
pwndbg> print $lx_task_by_pid(295).state
$1 = 1
pwndbg> print $lx_task_by_pid(295).cred->uid
$2 = {
    val = 1000
}
pwndbg> set $lx_task_by_pid(295).cred->euid = 0
pwndbg> cont

해당 프로세스의 아이디를 이용하여 해당 태스크의 정보를 확인해본 결과 해당 프로세스는 실행중인 프로세스이며 uid가 1000으로 지정되어 있다. 이를 의도적으로 0으로 수정해보았다.

dreamhack@dh-lke:~$ whoami
root
dreamhack@dh-lke:~$ cat /etc/shadow
root:*:18511:0:99999:7:::
daemon:*:18511:0:99999:7:::
bin:*:18511:0:99999:7:::
sys:*:18511:0:99999:7:::
sync:*:18511:0:99999:7:::
games:*:18511:0:99999:7:::
man:*:18511:0:99999:7:::
lp:*:18511:0:99999:7:::
mail:*:18511:0:99999:7:::

해당 과정을 수행해본 결과 해당 프로세스는 최고 관리자 권한(root)를 획득하였음을 볼 수 있다.

결론

프로세스(process)와 스레드(thread)는 태스크 관리로 운영되며 태스크의 속성을 표현하는 정보가 있다. 해당 정보 중에는 프로세스의 신원정보를 의미하는 cred 구조체 형태의 정보가 있으며 이를 조작하여 최고관리자 권한을 얻을 수 있다. 태스크의 정보는 커널 메모리의 상주하며 커널의 취약점을 이용한다면 권한상승(Local Privilege Escalation)이 발생할 수 있음을 보인다.