CTF-虚拟机-QEMU-前置知识-操作流程与源码阅读

news2024/11/16 8:58:44

文章目录

  • 总览
  • 内存
  • PCI设备
    • PCI配置空间前64个字节
    • 对应源码
    • Memorry空间的BAR
    • IO空间的BAR
  • MMIO
  • PMIO
  • Ispci
  • 访问PCI设备配置空间中的Memory空间和IO空间
    • MMIO
    • PMIO
  • QQM(qemu object model)
  • 简洁概要
    • 将 TypeInfo 注册 TypeImpl:
    • ObjectClass的初始化:
    • 实例化 Instance(Object)
    • 准备自己写mini版QEMU吧,不然实在迷糊

吹爆这篇博客,写得巨好

总览

QEMU能够为用户进程进行CPU仿真提供环境
一个QEMU进程提供一种环境可启动一个虚拟机

KVM是在内核中运行的,让QEMU启动的虚拟机能直接在host的CPU上安全地执行guest的代码,作用为负责虚拟机的创建,虚拟内存的分配,虚拟CPU

// 第一步,获取到 KVM 句柄
kvmfd = open("/dev/kvm", O_RDWR);
// 第二步,创建虚拟机,获取到虚拟机句柄。
vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0);
// 第三步,为虚拟机映射内存,还有其他的 PCI,信号处理的初始化。
ioctl(kvmfd, KVM_SET_USER_MEMORY_REGION, &mem);
// 第四步,将虚拟机镜像映射到内存,相当于物理机的 boot 过程,把镜像映射到内存。
// 第五步,创建 vCPU,并为 vCPU 分配内存空间。
ioctl(kvmfd, KVM_CREATE_VCPU, vcpuid);
vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
// 第五步,创建 vCPU 个数的线程并运行虚拟机。
ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0);
// 第六步,线程进入循环,并捕获虚拟机退出原因,做相应的处理。
for (;;) {
  ioctl(KVM_RUN)
  switch (exit_reason) {
      case KVM_EXIT_IO:  /* ... */
      case KVM_EXIT_HLT: /* ... */
  }
}
// 这里的退出并不一定是虚拟机关机,
// 虚拟机如果遇到 I/O 操作,访问硬件设备,缺页中断等都会退出执行,
// 退出执行可以理解为将 CPU 执行上下文返回到 Qemu。

退出时判断原因,可能由KVM执行也有可能由QEMU执行

内存

可以这么认为,guest所使用的物理内存,实际上是对应的启动它的那个QEMU的虚拟内存的一部分。即该部分可能是对应gust的物理内存是从0开始的(guest视角)

两层转换

  1. 从guest的虚拟地址转换到guest的物理地址
    相当于从页表得到物理地址
  2. 从guest的物理地址转换到host的QEMU进程中的虚拟地址
    该物理地址再加上guest对应在host的QEMU进程中的虚拟地址中起始地址的就是对应的host的虚拟地址了

第一层转换。用pagemap的页面映射文件来转换

  1. 虚拟地址对应的pagemap中的偏移(此时为pagemap中第几个)乘8可得到在pagemap中的偏移(此时为pagemap中对应的地址)

在这里插入图片描述

  1. 读取后判断内容是否存在并且判断最高位是否1,为1则代表页面存在,然后将读取的内容左移12位得到低52位(物理页的地址)再或上原虚拟地址的低12位的页内偏移就是guest的物理地址了

用QEMU运行下列代码

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>

#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\n", (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; //取低52位
    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\n", ptr); //此时ptr是guest中虚拟地址
    ptr_mem = gva_to_gpa(ptr); //此时转换成了guest中物理地址
    printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);

    getchar();
    return 0;
}

此时

 printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);
 ptr_mem输出为0x68cf0010
 0x7fcddc000000  为guest的物理地址在host视角下的起始地址
 0x7fcddc000000+0x68cf0010即对应where am I?

PCI设备

PCI是一个外部链接(Peripheral Component Interconnect)标准,PCI设备就是符合这个标准的设备,且连接到PCI总线上。而PCI总线是CPU与外部设备沟通的桥梁。

符合 PCI 总线标准的设备就被称为 PCI 设备
PCI 设备同时也分为主设备和目标设备两种,主设备是一次访问操作的发起者,而目标设备则是被访问者。

每个PCI设备对应备一个PCI配置空间(PCI Configuration Space),它记录了关于此设备的信息。PCI配置空间最大256个字节,其中前64字节都是预定义好的标准。

PCI配置空间前64个字节

在这里插入图片描述

对应源码

typedef struct {
   WORD   wBusNum;		// Bus No. input field
   WORD   wDeviceNum;		// Device No. input field
   WORD   wFunction;		// Function No. input field
   WORD   wVendorId;		// Vendor ID input field
   WORD   wDeviceId;		// Device ID input field
   WORD   wDeviceIndex; 	// Device Search No. input field
   WORD   wCommand;             // Command
   WORD   wClassId; 		// Class ID
   BYTE   byInterfaceId;        // Interface ID
   BYTE   byRevId;              // Revision ID
   BYTE   byCLS;                // Cache Line Size
   BYTE   byLatency;            // Latency Timer
   DWORD  dwBaseAddr[6];        // 6个Base Address Register为32位
   DWORD  dwCIS;
   WORD   wSubSystemVendorId;
   WORD   wSubSystemId;
   DWORD  dwRomBaseAddr;        // Extension ROM Base Address
   BYTE   byIntLine;            // Interrupt Line
   BYTE   byIntPin;             // Interrupt Pin 
   BYTE   byMaxLatency;         // Max Latency
   BYTE   byMinGrant;           // Min Grant
  } PCIDEV, *LPPCIDEV;

6个BAR,每个BAR记录了该设备映射的一段地址空间,有Memorry空间和IO空间

Memorry空间的BAR

在这里插入图片描述
第0位为0,表示该为Memorry空间
第1位为0表示32位地址,为1表示64位地址
第2为为0表示区间大小超过1M,为0表示不超过1M
第3位表示是否支持可预读取

IO空间的BAR

在这里插入图片描述第0位为1,表示该为IO空间

MMIO

内存映射io,和内存共享一个地址空间。可以和像读写内存一样读写其内容。
通过Memory 空间访问设备I/O的方式称为memory mapped I/O,即MMIO,这种情况下,CPU直接使用普通访存指令即可访问设备I/O。

PMIO

端口映射io,内存和io设备有各自独立的地址空间,cpu需要通过专门的指令才能去访问。在intel的微处理器中使用的指令是IN和OUT。

通过I/O 空间访问设备I/O的方式称为port mapped I/O,即PMIO,这种情况下CPU需要使用专门的I/O指令如IN/OUT访问I/O端口

Ispci

pci外设地址,形如0000:00:1f.1。第一个部分16位表示域;第二个部分8位表示总线编号;第三个部分5位表示设备号;最后一个部分3位表示功能号。
lspci 命令可以显示当前的pci设备

在这里插入图片描述

lspci -v可以显示当前的pci设备的详细信息,如mmio的地址,pmio的端口号
在这里插入图片描述

lspci -v -m -n -s 设备可以显示头部的一些信息
在这里插入图片描述

/sys/bus/pci/devices可以找到pci设备相关的文件。
在这里插入图片描述
/sys/devices/pci0000:00也可以找到pci设备的相关的文件
在这里插入图片描述
查看设备id是device文件
cat /sys/devices/pci0000:00/0000:00:03.0/device
在这里插入图片描述

随便进入一个pci设备文件用ls查看
在这里插入图片描述

在这里插入图片描述
每个设备的目录下resource0 对应MMIO空间。resource1 对应PMIO空间。(不是所有设备文件都有resource0或者resource1)
resource文件里面会记录相关的数据,第一行就是MIMO的信息,从左到右是:起始地址、结束地址、标识位。第二行是PMIO

I/O 内存:/proc/iomem
I/O 端口:/proc/ioports
使用cat /proc/iomem可查看当前PCI设备的映射内存空间
使用cat /proc/ioports可查看当前PCI设备的映射端口空间

访问PCI设备配置空间中的Memory空间和IO空间

MMIO

#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_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, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//根据resource0中的文件内容来分配Memory空间
    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;
}

uint64_t mmio_read(uint64_t addr)
{
    return *((uint64_t*)(mmio_base + addr));
}

int main(int argc, char const *argv[])
{
    getMMIOBase();
    printf("mmio_base Resource0Base: %p\n", mmio_base);

    mmio_write(144, val);
    mmio_read(144);
    
    return 0;
}

PMIO

需要权限才能访问端口
0x000-0x3ff端口可以用ioperm(from, num, turn_on)获得权限
比如ioperm(0x300,5,1); 获得 0x300 到 0x304 端口的访问权限
更高端口需要iopl(3)获得权限,这个可以获得范围所有端口权限
in,out系列函数如下,分别是写入/读取一个字节(b结尾),两个字节(w结尾),四个字节(l结尾)

#include <sys/io.h >

iopl(3); 
inb(port); 
inw(port); 
inl(port);

outb(val,port); 
outw(val,port); 
outl(val,port);

QQM(qemu object model)

QEMU提供了一套面向对象编程的模型——QOM,即QEMU Object Module,几乎所有的设备如CPU、内存、总线等都是利用这一面向对象的模型来实现的。
而对象的初始化分为四步:

  1. 将 TypeInfo 注册 TypeImpl
  2. 实例化 ObjectClass
  3. 实例化 Object
  4. 添加 Property

ObjectClass: 是所有类对象的基类,仅仅保存了一个整数 type 。
Object: 是所有对象的 基类Base Object , 第一个成员变量为指向 ObjectClass 的指针。
TypeInfo:是用户用来定义一个 Type 的工具型的数据结构。
TypeImpl:对数据类型的抽象数据结构,TypeInfo的属性与TypeImpl的属性对应。

简洁概要

将 TypeInfo 注册 TypeImpl:

1、首先__attribute__((constructor))的修饰让type_init在main之前执行,type_init的参数是XXX_register_types函数指针,将函数指针传递到ModuleEntry的init函数指针,最后就是将这个ModuleEntry插入到ModuleTypeList
2、main函数中的module_call_init(MODULE_INIT_QOM);调用了MODULE_INIT_QOM类型的ModuleTypeList中的所有ModuleEntry中的init()函数,也就是第一步type_init的第一个参数XXX_register_types函数指针
3、那就下了就是XXX_register_types函数的操作了,就是创建TypeImpl的哈希表

ObjectClass的初始化:

调用链main->select_machine->object_class_get_list->object_class_foreach->object_class_foreach_tramp->type_initialize

将parent->class->interfaces的一些信息添加到ti->class->interfaces列表上面,ti->interfaces[i].typename对应的type的信息也添加到ti->class->interfaces列表,最后最重要的就是调用parent的class_base_init进行初始化,最后调用自己ti->class_init进行初始化。

实例化 Instance(Object)

调用链qemu_opts_foreach->device_init_func->qdev_device_add->object_new->object_new_with_type

object_new_with_type函数里面初始化了Object的一些成员,并通过object_init_with_type函数调用ti->instance_init函数(有parent就会先递归调用object_init_with_type,再调用自身的ti->instance_init函数),而最后就是通过object_post_init_with_type函数差不多,只不过先调用自身的ti->instance_post_init,再递归调用parent的ti->instance_post_init

准备自己写mini版QEMU吧,不然实在迷糊

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

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

相关文章

linux学习资源

linux书籍资源&#xff08;pdf版&#xff09;&#xff1a; 有需要的请在评论区留言。 《Linux Basics for Hackers》 kaiwan的三部曲&#xff1a; 《Hands-On System Programming with Linux》 《Linux Kernel Programming》 《Linux Kernel Programming Part 2》 《Ma…

电子商务网站规划

摘 要 随着我国网民数量的不断增长&#xff0c;在关于互联网政策的大力支持下&#xff0c;国内的电子商务已经发展为全民网购的热潮。什么是电子商务&#xff0c;就是在互联网中盲目投资的网名有一个稳定的有规则的平台&#xff0c;有了电子商务平台后&#xff0c;多数商家选择…

使用 kubeadm 部署 Kubernetes 集群(三)kubeadm 初始化 k8s 证书过期解决方案

一、延长k8s证书时间 查看 apiserver 证书有效时间&#xff1a;默认是一年的有效期 [rootxuegod63 ~]# openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text |grep Not 延长证书过期时间 1.把 update-kubeadm-cert.sh 文件上传到 xuegod63 节点 vim update-…

.net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法

文章目录 .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法详细报错内容解决方案修改数据修改表修改字段 .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法 详细报错内容 System.NotSupportedException…

【WPF.NET开发】WPF.NET桌面应用开发概述

本文内容 为何从 .NET Framework 升级使用 WPF 进行编程标记和代码隐藏输入和命令控件布局数据绑定图形和动画文本和版式自定义 WPF 应用 Windows Presentation Foundation (WPF) 是一个与分辨率无关的 UI 框架&#xff0c;使用基于矢量的呈现引擎&#xff0c;构建用于利用现…

【matlab程序】画海洋流场

【matlab程序】画海洋流场 clear;clc; file ( ‘0227.nc’); latncread(file,‘latitude’); lonncread(file,‘longitude’); uncread(file,‘water_u’); vncread(file,‘water_v’); [x,y]meshgrid(lon,lat); xx’; yy’; interval4; figure (1) set(gcf,‘color’,[1 1 1…

工业机器视觉megauging(向光有光)使用说明书(二,轻量级的visionpro)

测试程序暂时支持80万&#xff08;包含1024*768&#xff09;以上的gige工业相机&#xff0c;以后会支持640*480分辨率相机。 我们程序中使用注意力机制&#xff0c;其实就是感兴趣区域&#xff08;roi&#xff0c;你看过我前面博文&#xff0c;就应该明白&#xff09;精神的延…

UiPath:人工智能和重新加速增长是 2024 年的好兆头

UiPath&#xff08;NYSE&#xff1a;PATH&#xff09;重新加速增长&#xff0c;同时在销售和营销方面变得最高效&#xff0c;使其成为进入 2024 年的有吸引力的成长型股票。 UiPath 最初被归类为机器人流程自动化 (RPA) 公司&#xff0c;现在认为自己是一家人工智能驱动的自动…

PyQt基础_014_对话框类控件QFileDialog

基本操作 import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import *class filedialogdemo(QWidget):def __init__(self, parentNone):super(filedialogdemo, self).__init__(parent)layout QVBoxLayout()self.btn QPushButton("…

探索意义的深度:自然语言处理中的语义相似性

一、说明 语义相似度&#xff0c;反应出计算机对相同内容&#xff0c;不同表达的识别能力。因而识别范围至少是个句子&#xff0c;最大范围就是文章&#xff0c;其研究方法有所区别。本文将按照目前高手的研究成绩&#xff0c;作为谈资介绍给诸位。 二、语义相似度简介 自然语言…

springboot虚拟请求——测试

springboot虚拟请求 表现层测试 web环境模拟测试 虚拟请求状态匹配——执行状态的匹配 Testvoid testStatus(Autowired MockMvc mvc) throws Exception { // //http://localhost:8080/books// 创建一个虚拟请求&#xff0c;当前访问的是booksMockHttpServletRequestBui…

Nginx反向代理详解

Nginx反向代理详解 nginx反向代理是一种常用的服务器架构设计方案&#xff0c;其原理是将客户端请求先发送到反向代理服务器&#xff0c;反向代理服务器再将请求转发到后端真实服务器处理&#xff0c;并将处理结果返回给客户端&#xff0c;从而实现负载均衡、高可用、安全和减…

SQL-分页查询offset的用法

今天在做一道关于查询一张表中第二高工资的问题时发现没有思路&#xff0c;经过一番搜索发现需要用到offset偏移量来解决这个问题。 OFFSET关键字用于指定从结果集的哪一行开始返回数据。通常&#xff0c;它与LIMIT一起使用&#xff0c;以实现分页效果。其语法如下&#xff1a…

8.ROS的TF坐标变换(二):动态坐标变换、多坐标变换代码讲解(以LIO-SAM为例)

目录 1 ROS的动态坐标变换及代码解释 1.1 什么是ROS的动态坐标变换 1.2 CMakeLists.txt、package.xml基础配置 1.3 发布方代码实现 1.4 接收方代码实现 2 ROS的多坐标变换及代码解释 2.1 什么是ROS的多坐标变换 2.2 发布方代码实现 2.3 接收方代码实现 3 L…

Java数据结构之《希尔排序》题目

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我写完…

OOM了?物理内存不够了?试试这个方法来提升内存容量,不花钱的

通过增加虚拟内存来提高内存使用 本文解决的实际问题&#xff1a; 当我们物理内存小的时候&#xff0c;会出现OOM&#xff0c;然后服务自动死掉的情况。因为物理内存大小是固定的&#xff0c;有没有其他好的办法来解决呢&#xff1f;这里我们可以适当调整Linux的虚拟内存来协作…

服务号可以升级订阅号吗

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;很多小伙伴想把服务号改为订阅号&#xff0c;但是不知道改了之后具体有什么作用&#xff0c;今天跟大家具体讲解一下。首先我们知道服务号一个月只能发四次文章&#xff0c;但是订阅号每天都可以发…

Hdoop学习笔记(HDP)-Part.18 安装Flink

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

注意力机制及Transformer-3GPT版

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 知识框架No.1 自注意力机制(self-attention)一、输入的种类以及表示1、输入是a vector2、输入是a set of vectors(一段文字)3、输入是a set of vectors(一段音频)4、输入是a set of vectors(一段图谱)5、输入是a set of vectors(一个…

什么是CAS, 什么是AQS

文章目录 什么是CAS, 什么是AQSCASAQS 什么是CAS, 什么是AQS CAS AQS AQS 全称是AbstractQueuedSynchronizer&#xff0c; 是juc 下一个核心的抽象类&#xff0c;用于构建各种同步器和锁 比如我们熟悉的 ReentrantLock、ReadWriteLock、CountDownLatch等等是基于AQS. 首先在…