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

文章目录

  • 总览
  • 内存
  • 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 & 0xfffreturn 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输出为0x68cf00100x7fcddc000000  为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 fieldWORD   wDeviceNum;		// Device No. input fieldWORD   wFunction;		// Function No. input fieldWORD   wVendorId;		// Vendor ID input fieldWORD   wDeviceId;		// Device ID input fieldWORD   wDeviceIndex; 	// Device Search No. input fieldWORD   wCommand;             // CommandWORD   wClassId; 		// Class IDBYTE   byInterfaceId;        // Interface IDBYTE   byRevId;              // Revision IDBYTE   byCLS;                // Cache Line SizeBYTE   byLatency;            // Latency TimerDWORD  dwBaseAddr[6];        // 6个Base Address Register为32位DWORD  dwCIS;WORD   wSubSystemVendorId;WORD   wSubSystemId;DWORD  dwRomBaseAddr;        // Extension ROM Base AddressBYTE   byIntLine;            // Interrupt LineBYTE   byIntPin;             // Interrupt Pin BYTE   byMaxLatency;         // Max LatencyBYTE   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.mzph.cn/news/190232.shtml

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

相关文章

SAS聚类分析介绍

1 聚类分析介绍 1.1基本概念 聚类就是一种寻找数据之间一种内在结构的技术。聚类把全体数据实例组织成一些相似组&#xff0c;而这些相似组被称作聚类。处于相同聚类中的数据实例彼此相同&#xff0c;处于不同聚类中的实例彼此不同。聚类技术通常又被称为无监督学习&#xff0…

深入了解Vue.js:构建现代、响应式的前端应用

文章目录 1. Vue.js简介1.1 安装Vue.js 2. Vue的核心概念2.1 数据驱动2.2 组件化2.3 生命周期钩子 3. Vue的特性3.1 响应式数据3.2 模板语法3.3 组件通信 4. 示例项目结语 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1…

Jmeter性能测试 —— 压力模式

压力模式 性能测试中的压力模式有两种。 第一种是并发用户模式&#xff08;虚拟用户模式&#xff09;并发用户是指虚拟并发用户数&#xff0c;从业务角度&#xff0c;也可以理解为同时在线的用户数。 从客户端的角度出发&#xff0c;摸底业务系统各节点能同时承载的在线用户数…

pytorch中Conv1d、Conv2d与Conv3d详解

1 卷积介绍 1.1 什么是卷积 卷积&#xff08;convolution&#xff09;&#xff0c;是一种运算&#xff0c;你可以类比于加&#xff0c;减&#xff0c;乘&#xff0c;除&#xff0c;矩阵的点乘与叉乘等等&#xff0c;它有自己的运算规则&#xff0c;卷积的符号是星号*。表达式…

Linux Makefile的认识及CMake的使用

1 Makefile的作用 Makefile 指的是一个叫 Makefile 的文件,里面提前写了一些指令。每次要自动化的完成一个比较复杂项目的自动编译用的时候,就在命令行输入“make”命令Makefile使用。使用Makefile可以 “智能” 的知道: 1 哪些文件需要先进行编译。 2 当某一文件在某次mak…

Blast中文手册(4)

Extracting data from BLAST databases with blastdbcmd(用blastdbcmd从BLAST数据库中提取数据) Created: June 23, 2008; Updated: January 7, 2021. Extract lowercase masked FASTA from a BLAST database with masking information(从具有掩码信息的BLAST数据库中提取小写掩…

知识管理平台Confluence:win10安装confluence

文章目录 介绍主要功能 安装教程安装java运行平台JRE安装数据库Postgresql在Postgresql创建confluence使用的数据库创建数据库用户创建数据库 安装confluence注册confluence启动confluence 参考链接 介绍 Confluence 是由澳大利亚软件公司 Atlassian 开发的企业协作平台。它提…

2023.11.27 关于 Mybatis 增删改操作

目录 引言 增加用户操作 删除用户操作 修改用户操作 阅读下述文章之间 建议点击下方链接先了解 MyBatis 的创建与使用 MyBatis 的创建与使用 建议点击下方链接先了解 单元测试 的创建与使用 Spring Boot 单元测试的创建与使用 引言 为了方便下文实现增、删、改操作我们先…

推荐3个完美替代 Navicat 的工具

现在企业&#xff0c;mysql数据库用的比较多&#xff0c;mysql数据库客户端的需求也就比较大&#xff0c;navicat就被大家所熟知。 这个工具&#xff0c;确实好用&#xff0c;功能也非常强大&#xff0c;但是&#xff0c;它的强大&#xff0c;是需要付费&#xff0c;或者用一些…

STM32内部温度传感器使用方法详解

STM32内部温度传感器使用方法详解 前言 STM32内部集成了一个片上温度传感器&#xff0c;可以用来测量MCU及周围的温度。测量范围&#xff1a;-40~125&#xff0c;精度1.5℃。虽然精度不高&#xff0c;但在某些应用场景下是够了的&#xff0c;相比于外部接入传感器&#xff0c…

最新Graphviz python安装教程及使用

文章目录 Graphviz 安装python安装graphviz库 Graphviz 安装 Graphviz是一个独立的软件&#xff0c;在用python的pip下载之前&#xff0c;需要先下载软件。 网址&#xff1a;https://graphviz.org/download/ 找到合适的版本进行下载安装。记住自己的安装位置&#xff0c;完…

Linux - 进程间通信

进程通信 初步理解进程通信 所谓进程之间的通信&#xff0c;就是两个进程之间的 数据层面的交互。 我们之前说过&#xff0c;父子进程之间是有一些数据通信的&#xff0c;子进程可以看到一些父进程 允许 子进程访问的数据&#xff0c;比如 父进程的 环境变量&#xff0c;子…

用通俗的方法讲解:大模型微调训练详细说明(附理论+实践代码)

本文内容如下 介绍了大模型训练的微调方法&#xff0c;包括prompt tuning、prefix tuning、LoRA、p-tuning和AdaLoRA等。 介绍了使用deepspeed和LoRA进行大模型训练的相关代码。 给出了petals的介绍&#xff0c;它可以将模型划分为多个块&#xff0c;每个用户的机器负责其中一…

InsCode实践分享

在当今信息爆炸的时代&#xff0c;如何从海量信息中脱颖而出&#xff0c;获取更多的关注和认可&#xff0c;成为了许多人的共同追求。作为知乎平台上的优质用户&#xff0c;我愿意分享一些自己的经验和技巧&#xff0c;帮助大家更好地运用InsCode&#xff0c;实现个人成长和进步…

【爬虫逆向分析实战】某笔登录算法分析——本地替换分析法

前言 作者最近在做一个收集粉币的项目&#xff0c;可以用来干嘛这里就不展开了&#x1f601;&#xff0c;需要进行登录换算token从而达到监控收集的作用&#xff0c;手机抓包发现他是通过APP进行计算之后再请求接口的&#xff0c;通过官网分析可能要比APP逆向方便多&#xff0…

01-使用Git操作本地库,如初始化本地库,提交工作区文件到暂存区和本地库,查看版本信息,版本切换命令等

Git的使用 概述 Git是一个分布式版本控制工具, 通常用来管理项目中的源代码文件(Java类、xml文件、html页面等)进行管理,在软件开发过程中被广泛使用 Git可以记录文件修改的历史记录并形成备份从而实现代码回溯, 版本切换, 多人协作, 远程备份的功能Git具有廉价的本地库,方便…

开源图床Qchan本地部署远程访问,轻松打造个人专属轻量级图床

文章目录 前言1. Qchan网站搭建1.1 Qchan下载和安装1.2 Qchan网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar云端设置2.2 Cpolar本地设置 3. 公网访问测试总结 前言 图床作为云存储的一项重要应用场景&#xff0c;在大量开发人员的努力下&#xff0c;已经开发出大…

如果你想成为一名提示词工程师(Prompt Engineer),这款工具你不能错过

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 前言 我们知道&#xff0c;如果想要通过AI得到更好更精确的答案&#xff0c;那么提示词Prompt的好坏至关重要。 因此&#xff0c;提示词工程师这个岗位应运而出。…

第一节:认识微服务

一、微服务技术对比 Dubbo SpringCloudSpringCloudAlibaba注册中心zookeeper、Redis Eureka、ConsulNacos、Eureka服务远程调用Dubbo协议Feign&#xff08;http协议&#xff09;Dubbo、Feign配置中心无SpringCloudGateway、ZuulSpringCloudConfig、Nacos服务网…

qemu网络通信

TAP&#xff08;官网参考地址&#xff09; TAP&#xff0c;即Tunneling traffic access point&#xff0c;是一种在Linux上使用的虚拟网卡技术&#xff0c;它可以为应用程序提供安全的网络连接。可以利用TAP搭建桥接网络&#xff0c;bridge两端分别为host和qemu虚拟机。 安装…