注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

熊猫正正的博客

熊猫正正的天空

 
 
 

日志

 
 

Android native层动态库打针  

2015-01-17 16:51:41|  分类: Android开发与逆 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
Android native层动态库注射 
1.简介

本文讲解在Android native层,root权限下,注射动态库到目标进程,从而hook目标进程中动态库的函数的实现方式。文中的源码全部来源于网络,我只是稍微加以整理。

环境:Android4.2 源码下编译,模拟器中运行。

2.代码构成

包含三个模块的代码:

1.inject程序:把动态库libhookhelper注射到目标进程

2.libhookhelper:动态库代码。此部分代码用于修改目标进程中目标函数所在的got表的信息,从而替换掉目标函数。理解这部分需要一点点elf格式的知识。文章最后会给出链接文档以作参考。

3.libtest:这部分含有用于测试的目标进程的代码。目标进程调用一个动态库实现打印字符串的功能。无实际意义,仅作测试。

3.代码

3.1 inject程序与Android.mk文件:

inject.c代码如下:

#include <stdio.h>    
#include <stdlib.h>    
#include <asm/user.h>    
#include <asm/ptrace.h>    
#include <sys/ptrace.h>    
#include <sys/wait.h>    
#include <sys/mman.h>    
#include <dlfcn.h>    
#include <dirent.h>    
#include <unistd.h>    
#include <string.h>    
#include <elf.h>    
#include <android/log.h>    

#if defined(__i386__)    
#define pt_regs         user_regs_struct    
#endif    

#define ENABLE_DEBUG 1    

#if ENABLE_DEBUG    
#define  LOG_TAG "inject"    
#define  LOGD(fmt, args...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, fmt, ##args)    
#define DEBUG_PRINT(format,args...) \    
LOGD(format, ##args)    
#else    
#define DEBUG_PRINT(format,args...)    
#endif    

#define CPSR_T_MASK     ( 1u << 5 )    

const char *libc_path = "/system/lib/libc.so";    
const char *linker_path = "/system/bin/linker";    

int ptrace_readdata(pid_t pid,  uint8_t *src, uint8_t *buf, size_t size)    
{    
	uint32_t i, j, remain;    
	uint8_t *laddr;    

	union u {    
		long val;    
		char chars[sizeof(long)];    
	} d;    

	j = size / 4;    
	remain = size % 4;    

	laddr = buf;    

	for (i = 0; i < j; i ++) {    
		d.val = ptrace(PTRACE_PEEKTEXT, pid, src, 0);    
		memcpy(laddr, d.chars, 4);    
		src += 4;    
		laddr += 4;    
	}    

	if (remain > 0) {    
		d.val = ptrace(PTRACE_PEEKTEXT, pid, src, 0);    
		memcpy(laddr, d.chars, remain);    
	}    

	return 0;    
}    

int ptrace_writedata(pid_t pid, uint8_t *dest, uint8_t *data, size_t size)    
{    
	uint32_t i, j, remain;    
	uint8_t *laddr;    

	union u {    
		long val;    
		char chars[sizeof(long)];    
	} d;    

	j = size / 4;    
	remain = size % 4;    

	laddr = data;    

	for (i = 0; i < j; i ++) {    
		memcpy(d.chars, laddr, 4);    
		ptrace(PTRACE_POKETEXT, pid, dest, d.val);    

		dest  += 4;    
		laddr += 4;    
	}    

	if (remain > 0) {    
		d.val = ptrace(PTRACE_PEEKTEXT, pid, dest, 0);    
		for (i = 0; i < remain; i ++) {    
			d.chars[i] = *laddr ++;    
		}    

		ptrace(PTRACE_POKETEXT, pid, dest, d.val);    
	}    

	return 0;    
}    

#if defined(__arm__)    
int ptrace_call(pid_t pid, uint32_t addr, long *params, uint32_t num_params, struct pt_regs* regs)    
{    
	uint32_t i;    
	for (i = 0; i < num_params && i < 4; i ++) {    
		regs->uregs[i] = params[i];    
	}    

	//    
	// push remained params onto stack    
	//    
	if (i < num_params) {    
		regs->ARM_sp -= (num_params - i) * sizeof(long) ;    
		ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)?ms[i], (num_params - i) * sizeof(long));    
	}    

	regs->ARM_pc = addr;    
	if (regs->ARM_pc & 1) {    
		/* thumb */    
		regs->ARM_pc &= (~1u);    
		regs->ARM_cpsr |= CPSR_T_MASK;    
	} else {    
		/* arm */    
		regs->ARM_cpsr &= ~CPSR_T_MASK;    
	}    

	regs->ARM_lr = 0;        

	if (ptrace_setregs(pid, regs) == -1     
		|| ptrace_continue(pid) == -1) {    
		printf("error\n");    
		return -1;    
	}    

	int stat = 0;  
	waitpid(pid, &stat, WUNTRACED);  
	while (stat != 0xb7f) {  
		if (ptrace_continue(pid) == -1) {  
			printf("error\n");  
			return -1;  
		}  
		waitpid(pid, &stat, WUNTRACED);  
	}  

	return 0;    
}    

#elif defined(__i386__)    
long ptrace_call(pid_t pid, uint32_t addr, long *params, uint32_t num_params, struct user_regs_struct * regs)    
{    
	regs->esp -= (num_params) * sizeof(long) ;    
	ptrace_writedata(pid, (void *)regs->esp, (uint8_t *)params, (num_params) * sizeof(long));    

	long tmp_addr = 0x00;    
	regs->esp -= sizeof(long);    
	ptrace_writedata(pid, regs->esp, (char *)&tmp_addr, sizeof(tmp_addr));     

	regs->eip = addr;    

	if (ptrace_setregs(pid, regs) == -1     
		|| ptrace_continue( pid) == -1) {    
		printf("error\n");    
		return -1;    
	}    

	int stat = 0;  
	waitpid(pid, &stat, WUNTRACED);  
	while (stat != 0xb7f) {  
		if (ptrace_continue(pid) == -1) {  
			printf("error\n");  
			return -1;  
		}  
		waitpid(pid, &stat, WUNTRACED);  
	}  

	return 0;    
}    
#else     
#error "Not supported"    
#endif    

int ptrace_getregs(pid_t pid, struct pt_regs * regs)    
{    
	if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0) {    
		perror("ptrace_getregs: Can not get register values");    
		return -1;    
	}    

	return 0;    
}    

int ptrace_setregs(pid_t pid, struct pt_regs * regs)    
{    
	if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {    
		perror("ptrace_setregs: Can not set register values");    
		return -1;    
	}    

	return 0;    
}    

int ptrace_continue(pid_t pid)    
{    
	if (ptrace(PTRACE_CONT, pid, NULL, 0) < 0) {    
		perror("ptrace_cont");    
		return -1;    
	}    

	return 0;    
}    

int ptrace_attach(pid_t pid)    
{    
	if (ptrace(PTRACE_ATTACH, pid, NULL, 0) < 0) {    
		perror("ptrace_attach");    
		return -1;    
	}    

	int status = 0;    
	waitpid(pid, &status , WUNTRACED);    

	return 0;    
}    

int ptrace_detach(pid_t pid)    
{    
	if (ptrace(PTRACE_DETACH, pid, NULL, 0) < 0) {    
		perror("ptrace_detach");    
		return -1;    
	}    

	return 0;    
}    

void* get_module_base(pid_t pid, const char* module_name)    
{    
	FILE *fp;    
	long addr = 0;    
	char *pch;    
	char filename[32];    
	char line[1024];    

	if (pid < 0) {    
		/* self process */    
		snprintf(filename, sizeof(filename), "/proc/self/maps", pid);    
	} else {    
		snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);    
	}    

	fp = fopen(filename, "r");    

	if (fp != NULL) {    
		while (fgets(line, sizeof(line), fp)) {    
			if (strstr(line, module_name)) {    
				pch = strtok( line, "-" );    
				addr = strtoul( pch, NULL, 16 );    

				if (addr == 0x8000)    
				    addr = 0;    

				break;    
			}    
		}    

		fclose(fp) ;    
	}    

	return (void *)addr;    
}    

void* get_remote_addr(pid_t target_pid, const char* module_name, void* local_addr)    
{    
	void* local_handle, *remote_handle;    

	local_handle = get_module_base(-1, module_name);    
	remote_handle = get_module_base(target_pid, module_name);    

	DEBUG_PRINT("[+] get_remote_addr: local[%x], remote[%x]\n", local_handle, remote_handle);    

	void * ret_addr = (void *)((uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle);    

#if defined(__i386__)    
	if (!strcmp(module_name, libc_path)) {    
		ret_addr += 2;    
	}    
#endif    
	return ret_addr;    
}    

int find_pid_of(const char *process_name)    
{    
	int id;    
	pid_t pid = -1;    
	DIR* dir;    
	FILE *fp;    
	char filename[32];    
	char cmdline[256];    

	struct dirent * entry;    

	if (process_name == NULL)    
		return -1;    

	dir = opendir("/proc");    
	if (dir == NULL)    
		return -1;    

	while((entry = readdir(dir)) != NULL) {    
		id = atoi(entry->d_name);    
		if (id != 0) {    
			sprintf(filename, "/proc/%d/cmdline", id);    
			fp = fopen(filename, "r");    
			if (fp) {    
				fgets(cmdline, sizeof(cmdline), fp);    
				fclose(fp);    

				if (strcmp(process_name, cmdline) == 0) {    
				    /* process found */    
				    pid = id;    
				    break;    
				}    
			}    
		}    
	}    

	closedir(dir);    
	return pid;    
}    

long ptrace_retval(struct pt_regs * regs)    
{    
#if defined(__arm__)    
	return regs->ARM_r0;    
#elif defined(__i386__)    
	return regs->eax;    
#else    
#error "Not supported"    
#endif    
}    

long ptrace_ip(struct pt_regs * regs)    
{    
#if defined(__arm__)    
	return regs->ARM_pc;    
#elif defined(__i386__)    
	return regs->eip;    
#else    
#error "Not supported"    
#endif    
}    

int ptrace_call_wrapper(pid_t target_pid, const char * func_name, void * func_addr, long * parameters, int param_num, struct pt_regs * regs)     
{    
	DEBUG_PRINT("[+] Calling %s in target process.\n", func_name);    
	if (ptrace_call(target_pid, (uint32_t)func_addr, parameters, param_num, regs) == -1)    
		return -1;    

	if (ptrace_getregs(target_pid, regs) == -1)    
		return -1;    
	DEBUG_PRINT("[+] Target process returned from %s, return value=%x, pc=%x \n",     
		func_name, ptrace_retval(regs), ptrace_ip(regs));    
	return 0;    
}    

int inject_remote_process(pid_t target_pid, const char *library_path, 
	const char *function_name, const char *param, size_t param_size)    
{    
	int ret = -1;    
	void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;    
	void *local_handle, *remote_handle, *dlhandle;    
	uint8_t *map_base = 0;    
	uint8_t *dlopen_param1_ptr, *dlsym_param2_ptr, *saved_r0_pc_ptr, *inject_param_ptr, *remote_code_ptr, *local_code_ptr;    

	struct pt_regs regs, original_regs;    
	extern uint32_t _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s, \    
	_dlsym_param2_s, _dlclose_addr_s, _inject_start_s, _inject_end_s, _inject_function_param_s, \    
	_saved_cpsr_s, _saved_r0_pc_s;    

	uint32_t code_length;    
	long parameters[10];    

	DEBUG_PRINT("[+] Injecting process: %d\n", target_pid);    

	if (ptrace_attach(target_pid) == -1)    
		goto exit;    

	if (ptrace_getregs(target_pid, &regs) == -1)    
		goto exit;    

	/* save original registers */    
	memcpy(&original_regs, &regs, sizeof(regs));    

	mmap_addr = get_remote_addr(target_pid, libc_path, (void *)mmap);    
	DEBUG_PRINT("[+] Remote mmap address: %x\n", mmap_addr);    

	/* call mmap */    
	parameters[0] = 0;  // addr    
	parameters[1] = 0x4000; // size    
	parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC;  // prot    
	parameters[3] =  MAP_ANONYMOUS | MAP_PRIVATE; // flags    
	parameters[4] = 0; //fd    
	parameters[5] = 0; //offset    

	if (ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, 6, &regs) == -1)    
		goto exit;    

	map_base = ptrace_retval(&regs);    

	dlopen_addr = get_remote_addr( target_pid, linker_path, (void *)dlopen );    
	dlsym_addr = get_remote_addr( target_pid, linker_path, (void *)dlsym );    
	dlclose_addr = get_remote_addr( target_pid, linker_path, (void *)dlclose );    
	dlerror_addr = get_remote_addr( target_pid, linker_path, (void *)dlerror );    

	DEBUG_PRINT("[+] Get imports: dlopen: %x, dlsym: %x, dlclose: %x, dlerror: %x\n",    
	dlopen_addr, dlsym_addr, dlclose_addr, dlerror_addr);    

	printf("library path = %s\n", library_path);    
	ptrace_writedata(target_pid, map_base, library_path, strlen(library_path) + 1);    

	parameters[0] = map_base;       
	parameters[1] = RTLD_NOW| RTLD_GLOBAL;     

	if (ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, 2, &regs) == -1)    
		goto exit;    

	void * sohandle = ptrace_retval(&regs);    

	#define FUNCTION_NAME_ADDR_OFFSET       0x100    
	ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + 1);    
	parameters[0] = sohandle;       
	parameters[1] = map_base + FUNCTION_NAME_ADDR_OFFSET;     

	if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, &regs) == -1)    
		goto exit;    

	void * hook_entry_addr = ptrace_retval(&regs);    
	DEBUG_PRINT("hook_entry_addr = %p\n", hook_entry_addr);    

	#define FUNCTION_PARAM_ADDR_OFFSET      0x200    
	ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, param, strlen(param) + 1);    
	parameters[0] = map_base + FUNCTION_PARAM_ADDR_OFFSET;      
	
	if (ptrace_call_wrapper(target_pid, "hook_entry", hook_entry_addr, parameters, 1, &regs) == -1)    
		goto exit;        

	
	printf("Press enter to dlclose and detach\n");    
	getchar();    
	parameters[0] = sohandle;       
	
	if (ptrace_call_wrapper(target_pid, "dlclose", dlclose, parameters, 1, &regs) == -1)    
		goto exit;  
	/* restore */    
	ptrace_setregs(target_pid, &original_regs); 
	ptrace_detach(target_pid);    
	ret = 0;    

exit:    
	return ret;    
}    

int main(int argc, char** argv) {    
	pid_t target_pid;
	if(argc!=2) {
		printf("invalid prarams");
	}
	target_pid = find_pid_of(argv[1]);    
	if (-1 == target_pid) {  
		printf("Can't find the process\n");  
		return -1;  
	}  
   
	inject_remote_process(target_pid, "/system/lib/libhookhelper.so", "hook_entry",  "I'm parameter!", strlen("I'm parameter!"));    
	return 0;  
}  

Android.mk文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := inject.c
LOCAL_SHARED_LIBRARIES := libcutils libutils libdl
LOCAL_MODULE := inject

include $(BUILD_EXECUTABLE)

3.2 libhookhelper代码

hookhelper代码如下:

#include <unistd.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <android/log.h>  
#include <elf.h>  
#include <fcntl.h>  
#include <sys/mman.h>
#include <linker.h>
#include <dlfcn.h>

#define LOG_TAG "hookhelper"  
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)    

extern "C" {
//external function declaration
extern void print();

void newPrint()  
{  
    LOGD("new Print\n");
    print();
}  

#define LIBSF_PATH      "/system/bin/cqlmain"
#define FUNCTIONNAME    "print"

void *g_OriginalFunc = (void*)print;
void *g_NewFunc = (void*)newPrint;

void* get_module_base(pid_t pid, const char* module_name)  
{  
    FILE *fp;  
    long addr = 0;  
    char *pch;  
    char filename[32];  
    char line[1024];  
  
    if (pid < 0) {  
        /* self process */  
        snprintf(filename, sizeof(filename), "/proc/self/maps", pid);  
    } else {  
        snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);  
    }  
  
    fp = fopen(filename, "r");  
  
    if (fp != NULL) {  
        while (fgets(line, sizeof(line), fp)) {  
            if (strstr(line, module_name)) {  
                pch = strtok( line, "-" );  
                addr = strtoul( pch, NULL, 16 );  
  
                if (addr == 0x8000)  
                    addr = 0;  
  
                break;  
            }  
        }  
  
        fclose(fp) ;  
    }  
  
    return (void *)addr;  
} 

void replaceFunc(void *handle,const char *name, void* pNewFun, void** pOldFun)  
{  

    if(!handle)  
        return;  

    soinfo *si = (soinfo*)handle;     
    Elf32_Sym *symtab = si->symtab;    
    const char *strtab = si->strtab;    
    Elf32_Rel *rel = si->plt_rel;  
    unsigned count = si->plt_rel_count;   
    unsigned idx;   

    bool fit = 0;

    for(idx=0; idx<count; idx++)   
    {    
        unsigned int type = ELF32_R_TYPE(rel->r_info);    
        unsigned int sym = ELF32_R_SYM(rel->r_info);    
        unsigned int reloc = (unsigned)(rel->r_offset + si->base);    
        char *sym_name = (char *)(strtab + symtab[sym].st_name);   

        if(strcmp(sym_name, name)==0)   
        {   
            uint32_t page_size = getpagesize();  
            uint32_t entry_page_start = reloc& (~(page_size - 1));  
            mprotect((uint32_t *)entry_page_start, page_size, PROT_READ | PROT_WRITE);

            *pOldFun = (void *)*((unsigned int*)reloc);
            *((unsigned int*)reloc)= (unsigned int)pNewFun;
            LOGD("find %s function at address: %p\n",name,(void*)*pOldFun);
            fit = 1;
            break;
        }   
        rel++;    
    }  

    if(fit) {
        LOGD("not find :%s in plt_rel\n",FUNCTIONNAME);
    }
} 

void replaceFun2() {   
    void * base_addr = get_module_base(getpid(), LIBSF_PATH);
    
    LOGD("%s address = %p\n", LIBSF_PATH,base_addr);    
  
    int fd;    
    fd = open(LIBSF_PATH, O_RDONLY);    
    if (-1 == fd) {    
        LOGD("error\n");    
        return;    
    }    
  
    Elf32_Ehdr ehdr;    
    read(fd, &ehdr, sizeof(Elf32_Ehdr));    
  
    unsigned long shdr_addr = ehdr.e_shoff;      
    int shnum = ehdr.e_shnum;      
    int shent_size = ehdr.e_shentsize;      
    unsigned long stridx = ehdr.e_shstrndx;      
  
    Elf32_Shdr shdr;    
    lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET);      
    read(fd, &shdr, shent_size);      
  
    char * string_table = (char *)malloc(shdr.sh_size);      
    lseek(fd, shdr.sh_offset, SEEK_SET);      
    read(fd, string_table, shdr.sh_size);    
    lseek(fd, shdr_addr, SEEK_SET);      
  
    int i;      
    uint32_t out_addr = 0;    
    uint32_t out_size = 0;    
    uint32_t got_item = 0;  
    int32_t got_found = 0;    

    for (i = 0; i < shnum; i++) {      
        read(fd, &shdr, shent_size);      
        if (shdr.sh_type == SHT_PROGBITS) {    

            int name_idx = shdr.sh_name;            
            if (strcmp(&(string_table[name_idx]), ".got.plt") == 0
                || strcmp(&(string_table[name_idx]), ".got") == 0) {
                
                out_addr = (uint32_t)(base_addr + shdr.sh_addr);      
                out_size = shdr.sh_size;    
                  
                for (i = 0; i < out_size; i += 4) {      
                    got_item = *(uint32_t *)(out_addr + i);
                    if (got_item  == (uint32_t)g_OriginalFunc) {      
                        LOGD("found %s in got\n",FUNCTIONNAME); 
                        
                        got_found = 1;  
  
                        uint32_t page_size = getpagesize();  
                        uint32_t entry_page_start = (out_addr + i) & (~(page_size - 1));  
                        mprotect((uint32_t *)entry_page_start, page_size, PROT_READ | PROT_WRITE);
                        
                        *(uint32_t *)(out_addr + i) = (uint32_t)g_NewFunc;
                        //break;      
                    } else if (got_item == (uint32_t)g_NewFunc) {      
                        LOGD("Already hooked\n");
                        got_found = 1;
                        break;      
                    } 
                }
                
                if (got_found)
                    break;   
            }     
        }      
    }      

    free(string_table);      
    close(fd);
}


int hook()                                                  
{                                                               
    #if 1                                              
    replaceFunc( dlopen( LIBSF_PATH, RTLD_GLOBAL ),             
            FUNCTIONNAME, g_NewFunc, (void**)&g_OriginalFunc);  
    #else                                                       
    replaceFun2();                                              
    #endif                                                      
    return 0;                                                   
}                                                               



int hook_entry(char * a) {  
    hook();
    return 0;  
}

}

Android.mk文件如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := hookhelper.cpp
LOCAL_C_INCLUDES += bionic/linker \

LOCAL_SHARED_LIBRARIES := libcutils libutils libprint libdl
LOCAL_MODULE    := libhookhelper

include $(BUILD_SHARED_LIBRARY)

3.3 libtest文件如下:

main.c:

#include <stdio.h>
//#include "print.h"
int main() {
	extern void print();
	while(1) {
		print();
		sleep(2);
	}
	return 0;
}

print.c文件如下:

#include<cutils/log.h>
#undef LOG_TAG
#define LOG_TAG "test"  
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)    

void print() {
	LOGD("my print :%p\n",(void*)print);
}

Android.mk文件如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := print.c
LOCAL_SHARED_LIBRARIES := libcutils libutils liblog
LOCAL_MODULE    := libprint

include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_SRC_FILES := main.c
LOCAL_SHARED_LIBRARIES := libprint
LOCAL_MODULE := cqlmain

include $(BUILD_EXECUTABLE)

4. 参考文档

http://blog.csdn.net/jinzhuojun/article/details/9900105

http://www.xfocus.net/articles/200208/438.html

http://blog.csdn.net/myarrow/article/details/9630377

  评论这张
 
阅读(133)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017