CIDY
[System_Hacking] Tasks 본문
task
리눅스의 태스크는 프로그램 실행 단위를 말한다. 사용자가 어떤 프로그램을 실행하면 태스크가 생성되고, 그 프로그램의 코드가 실행된다. 리눅스에서는 하나의 프로세스와 스레드가 각각의 태스크가 된다.
대부분 운영체제는 여러 태스크를 생성해두고, 각 태스크에 실행 시간을 배분하는 방식으로 태스크를 관리한다. 다수의 태스크를 관리하는것을 멀티 태스킹(Multitasking), 실행 시간을 배분하는 것을 스케줄링(Scheduling)이라고 한다.
각 태스크는 커널 메모리에 task_struct구조체로 표현되는데, 이 구조체에는 태스크의 여러 정보가 저장되어 있다. 그 중에는 사용자 신원, 권한에 대한 정보도 포함되어 있어서, 이를 조작하는 것이 커널 익스플로잇의 목표가 되기도 한다.
리눅스 권한
리눅스는 운영체제의 사용자를 32비트 정수값인 UID(User ID)로 식별한다. 명시적 허용이 없는 한, 각 사용자는 다른 사용자의 자원에 접근할 수 없다.(권한 분리) 하지만 UID값으로 0을 갖는 root사용자는 최고 관리자(superuser)로, 다른 사용자의 자원에 임의로 접근할 수 있다.
권한 상승(root권한 탈취)은 커널 익스플로잇의 주된 목적 중 하나이다. 이를 LPE(Local Privilege Escalation)이라고 한다. 권한 상승 방법은 다양한데, 그 중 하나가 태스크 구조체 조작이다.
태스크 구조체는 커널 메모리에 위치하여 태스크의 신원 정보 등을 기록해둔다. 가령 uid=1000인 사용자가 사용하는 쉘 프로그램은 신원 정보가 uid=1000으로 기록되어 있다. 만약 이를 0으로 조작할 수 있다면 사용자는 root유저가 될 수 있을 것이다.
따라서 오직 커널 권한의 프로세스만이 커널 메모리를 읽거나 조작할 수 있도록 엄격하게 관리하고 있다. (root권한의 프로세스도 커널 메모리에 접근하는 것은 불가하다.)
태스크 구조체
리눅스 커널은 task_struct구조체에 정보를 담고 있다. 이 구조체는 커널 메모리에 존재하며, 프로세스의 메모리 맵, 파일 디스크립터, 프로세스 권한 등의 정보를 저장한다.
// Permal link: https://github.com/torvalds/linux/blob/219d54332a09e8d8741c1e1982f5eae56099de85/include/linux/sched.h#L624:L1284
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구조체는 리눅스 커널 소스 트리에서 include/linux/sched.h 에 저장되어 있다.
state | 현재 태스크 실행 상태. 0은 실행 중이거나 실행(스케줄) 가능한 상태, 양수면 태스크 정지 or 대기 중 |
tasks | 태스크 연결 리스트 노드(커널에 존재) |
mm | mm_struct는 사용자 메모리 영역(주소공간) 정보를 담은 구조체이다. (보통 같은 프로세스의 스레드는 모두 mm이 같다.) |
cred | 현재 태스크의 신원 정보를 가리키는 포인터 |
comm | 실행 파일 or 스레드 이름을 저장 |
files | 열린 파일 디스크립터 정보들. 보통 같은 프로세스 내의 스레드는 모두 files가 같다. |
// Permal link: https://github.com/torvalds/linux/blob/219d54332a09e8d8741c1e1982f5eae56099de85/include/linux/cred.h#L111:L153
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(User ID, UID). 0으로 조작하면 그 태스크는 root 권한을 획득할 수 있다. |
euid | 실효적인 사용자 ID(Effective User ID, EUID). 권한 검사에 실제 사용되는 값으로, 0으로 조작하면 그 태스크는 root 권한을 가진다. (보통 uid와 동일) |
gid, egid | Real GID, Effective GID이다. (GID : group id, 그룹 식별번호) |
권한 상승
우선 gdb부착 옵션을 넣고 가상 머신을 부팅한 뒤, echo $$ 명령어로 현재 쉘의 pid를 출력하자.
그런 다음 gdb에서 위 명령어들로 현재 쉘 task의 task_struct를 조회할 수 있다.
$lx_current() | 선택된 CPU 코어의 현재 프로세스 또는 스레드의 태스크 구조체 반환 |
$lx_task_by_pid(<PID>) | 프로세스 식별자(PID)가 <PID> 인 프로세스 또는 스레드의 태스크 구조체 반 |
위 함수들 역시 vmlinux-gdb.py 스크립트로 사용할 수 있는 것이다.
0x3e8은 1000이다. 위 정보로부터 해당 프로세스 이름이 bash이고, uid가 1000(dreamhack)이라는 것을 확인할 수 있다.
gdb로 euid를 0으로 바꿔보자.
위 사진은 gdb명령어를 실행하기 전후이다. euid가 root를 뜻하는 0으로 바뀐 것을 볼 수 있다.
위 상태에서 sudo없이 cat /etc/shadow를 해도 읽을 수 있다. (해당 파일은 root만 읽을 수 있다.)
물론 현실에서는 당연히 불가능한 방법이다.
Ref.
https://dreamhack.io/lecture/courses/56
'Hack > DreamHack' 카테고리의 다른 글
[System_Hacking] awesome-basics (0) | 2023.07.04 |
---|---|
[System_Hacking] mmapped (0) | 2023.07.04 |
[System_Hacking] Linux Exploitation (0) | 2023.05.16 |
[System_Hacking] Logical Bug (0) | 2023.05.15 |
[System_Hacking] STB-lsExecutor (0) | 2023.04.09 |