基于VMI的Linux虚拟机系统调用解析

VMI(Virtual Machine Introspection,虚拟机自省),是一种从虚拟机外部对虚拟机内部状态进行监控的技术。基于VMI获取的内存数据并结合通过KVM对VCPU相关信息的获取,实现将该类低级语义转换为系统调用层面的高级语义信息,从而可以一定程度的分析出系统的行为。 有关基于KVM的VMI开发环境部署可参考这篇文章。本文主要记录有关修改KVM源码实现系统调用陷入、对内存数据进行语义转换相关的原理与流程。 Kernel Side-获取系统调用的寄存器信息 在KVM侧主要实现的是设置VCPU的陷入以及一些VCPU基本信息的获取,如VM的VCPU数量、VM相关的一些句柄等等。除去设置陷入需要对VCPU的状态进行修改,其他的接口通过ioctl的方式来提供给用户态。 Intel x86 VMX架构简介 Intel 为了实现CPU的硬件虚拟化,在原来x86 CPU的基础上增加了VMX(Virtual Machine Extensions)架构。在VMX架构中有两类软件,这也是现今虚拟化的基本模式,分别是虚拟机监控器(VMM,就是KVM、Xen这类软件)和虚拟机(VM)。VMM对于整个系统的硬件资源具有完全的掌控权,然后再将机器的实体硬件进行抽象和模拟,最终提供给运行于其上的虚拟机。除了给虚拟机提供虚拟的硬件资源之外,VMM还负责保证不同虚拟机实例之间的互相隔离与独立,并且确保每个虚拟机在资源使用、调度等方面都是公平的。 但是传统的操作系统内核都是运行在CPU的Ring 0特权级别(定义参见Protection ring - Wikipedia),而为了完全掌控虚拟机的硬件,虚拟机的系统内核显然不能直接运行在Ring 0,所以为此Intel引入了一种新的CPU操作VMX operation(具体介绍在Intel SDM Volume 3, Chapter 23),来支撑虚拟机的运行。VMX Operation包含两类CPU操作: VMX root operation:VMM运行在该种操作模式下,CPU行为和VMX operation之外的行为相似; VMX non-root operation:VM运行在该种操作模式下,CPU的一些指令操作受限。 两种操作模式下,均有独立的Ring 0-Ring 3的特权级别,VMX operation和CPU特权级别是正交的,且两种操作模式可以相互转换,称为VMX转换(VMX transition)。VMX root转换为VMX non-root称为VM Entry,VMX non-root转换为VMX root称为VM Exit。 通过VM Entry,可以令一个虚拟机进入到运行状态,而当虚拟机在执行某些特殊指令的时候也会产生VM Exit退出到VMM,从而交由VMM处理。为了支撑这样一种模式的正常运行,Intel也设计了一系列指令,并且对每个虚拟机都提供了一个对应的控制数据结构,称为VMCS(Virtual Machine Control Structure),用于存储虚拟机的相关信息。指令的详细描述,可见Intel SDM Volume 1 Chapter 5.22。 令虚拟机陷入KVM 为了在VMM的层面上监控系统调用的信息,很容易想到就是让虚拟机在每当产生系统调用的时候就产生VM Exit,陷入到VMM,再对其CPU、寄存器等信息进行获取,以便后续处理。根据发起系统调用的方式不同分为以下三类,但是其基本原理都是产生一个系统中断,从而陷入到VMM中(KVM只能捕获系统中断): 基于中断的系统调用 对于一些老版本的OS,系统调用通过用户中断的方式来进行实现(如Linux为int 0x80,Windows为int 0x2e),所以可以通过修改中断描述符表(IDT),将所有用户中断描述符全部删除,只保留系统中断描述符,之后每当发起用户中断时,都会因为查询IDT地址越界产生一个#GP异常(General Protection Fault),引起系统中断。 基于快速系统调用指令的系统调用 为了提高系统调用的效率,x86 CPU还支持快速系统调用指令。对于32位的系统内核而言,采用的是SYSENTER/SYSEXIT的指令对;而对64位内核而言则采用SYSCALL/SYSRET指令对来进行系统调用。两者在陷入的实现上也有所差别,但都是操作与系统调用相关的MSR寄存器实现: SYSENTER/SYSEXIT:在进行系统调用的时候,需要把内核系统调用函数的位置装载到特殊的某些MSR寄存器中,如IA32_SYSENTER_CS ,用于存储目标函数的CS段地址,还有IA32_SYSENTER_ESP 、IA32_SYSENTER_EIP等也是同理。此时如果将IA32_SYSENTER_CS的值置空,那么最终装载进CS寄存器的最终也是空值,从而引发#GP异常(General Protection Fault),引起系统中断。 SYSCALL/SYSRET:这个指令对使用到了一个MSR寄存器IA32_EFER(Extended Feature Enable Register,详细介绍在Intel SDM Volume 3, Chapter 2....

Mar. 3, 2022 · 1 min · 147 words

虚拟机自省环境搭建

虚拟机自省(Virtual Machine Introspection,简称VMI),是一种从外部(即Hypervisor)对虚拟机内部状态进行监控的技术。从Hypervisor层面,通过虚拟机的陷入,可以直接读取到虚拟机陷入瞬间的内存数据。 部署基于KVM的VMI开发环境主要分为三个部分,分别为KVM、QEMU、LibVMI。前两个是Linux下部署虚拟化的必备组件,LibVMI则是基于各种Hypervisor所实现的VMI库,官网地址https://libvmi.com。 KVM 编译内核 下面的步骤,编译了一个带有KVM的4.9.0版本内核。 首先从kernel.org - kvm/kvm.git上下载KVM的源码,可以直接git clone,也可以下载Tarball。下载好源码之后,可以先使用make kernelversion来查看一下源码对应的Kernel版本,然后开始配置内核config。 使用make olddefconfig将现有系统的config直接复制过来,调整部分冲突的配置项,其余的维持现状(这里还有一种选择是直接从默认的config里来创建:make x86_64_defconfig,但是这样编译出来的内核因为一些驱动相关的选项可能没有打开,安装启动之后可能只能进BusyBox的Shell,是没办法进去系统的) 使用make menuconfig进入命令行可视化界面继续调整部分配置项,这里列出KVM相关的配置项如下: 1 2 3 4 5 6 7 8 9 10 11 12 CONFIG_HAVE_KVM=y CONFIG_HAVE_KVM_IRQCHIP=y CONFIG_HAVE_KVM_EVENTFD=y CONFIG_KVM_APIC_ARCHITECTURE=y CONFIG_KVM_MMIO=y CONFIG_KVM_ASYNC_PF=y CONFIG_HAVE_KVM_MSI=y CONFIG_VIRTUALIZATION=y CONFIG_KVM=m CONFIG_KVM_INTEL=m CONFIG_KVM_AMD=m CONFIG_KVM_MMU_AUDIT=y 之后就可以开始准备编译内核。首先更新系统,安装编译的依赖程序: 1 2 3 4 5 6 7 8 # Ubuntu sudo apt-get update sudo apt-get upgrade sudo apt-get install libncurses5-dev libssl-dev build-essential openssl pkg-config libc6-dev bison flex libelf-dev zlibc minizip libidn11-dev libidn11 bc fakeroot bison ncurses-dev # CentOS sudo yum update sudo yum groupinstall "Development Tools" sudo yum install ncurses-devel hmaccalc zlib-devel binutils-devel elfutils-libelf-devel openssl-devel 使用以下命令编译带有KVM的新内核。需要注意的是,视内核版本,必须使用能够兼容该版本内核的GCC和发行版版本来编译和安装,否则会出现编译失败的情况。...

Dec. 14, 2021 · 3 min · 521 words

《Black Hat Go》学习笔记(三)

中文翻译版:https://github.com/YYRise/black-hat-go 数据库和文件系统 对SQL数据库的操作 Go使用database/sql包来进行对SQL数据库的操作。不同于Python中对不同数据库使用不同的依赖包和调用不同的方法,Go官方文档中其实是要求开发者都使用database/sql中的方法来进行统一的数据库操作,而要操作不同的数据库则需要导入不同的数据库驱动即可: To use database/sql you’ll need the package itself, as well as a driver for the specific database you want to use. You generally shouldn’t use driver packages directly, although some drivers encourage you to do so.(In our opinion, it’s usually a bad idea.) Instead, your code should only refer to types defined in database/sql, if possible. This helps avoid making your code dependent on the driver, so that you can change the underlying driver (and thus the database you’re accessing) with minimal code changes....

Sep. 1, 2021 · 11 min · 2156 words

《Black Hat Go》学习笔记(二)

中文翻译版:https://github.com/YYRise/black-hat-go 接下来第六章是讲SMB和NTLM的,但其实原版书里面讲的主要是基于SMB协议数据结构编码和解码的处理方式,对于具体的协议交互方式和算法细节书里只给了个官方文档的名称,但自己也没深入去研究过SMB的东西,所以顺便也把SMB相关的细节都看了一遍,内容比较多,所以单独写一篇记录一下 这一章书里的实战项目实现了一个SMB登录的过程,并且基于此写了个密码爆破程序,所以我的学习过程也是基于这个项目的代码的,地址https://github.com/stacktitan/smb,参考的版本也和书上的一致,为SMB2.1。 首先是一些做参考的文档和RFC: MS-SMB2 - Server Message Block (SMB) Protocol Versions 2 and 3 MS-SPNG - Simple and Protected GSS-API Negotiation Mechanism (SPNEGO) Extension MS-NLMP - NT LAN Manager (NTLM) Authentication Protocol ASN. 1 语法和编码简介 - Win32 apps A Layman’s Guide to a Subset of ASN.1, BER, and DER RFC-4178 RFC-2743 SMB的工作流程 搭建SMB服务 通过抓包可以分析出SMB的交互过程。在分析之前首先需要搭建一个SMB服务 Kali可以直接通过apt安装:sudo apt install samba,然后启动smbd服务即可:sudo systemctl start smbd.service Windows通过开启Windows功能里的SMB有关项来开启SMB服务,然后设置文件夹属性里的高级共享即可启用文件夹的SMB共享 搭建好服务后,在运行框里或文件资源管理器地址栏输入\\IP\即可访问对应的SMB服务了。 SMB工作流程分析 搭建好服务之后,直接使用运行或文件资源管理器访问并抓包,可以获取到完整的SMB数据包细节: 因为这一章不太关心具体的SMB文件操作等细节,所以这些数据包其实就可以简单的分成以下的五个部分: Negotiate Session Setup Tree Connect 具体的文件操作 Tree Disconnect 其中在Session Setup过程成功后,就可以多次进行步骤3-5,用于访问不同位置的共享。...

Aug. 22, 2021 · 10 min · 1989 words

《Black Hat Go》学习笔记(一)

中文翻译版:https://github.com/YYRise/black-hat-go TCP、扫描器和代理 TCP扫描程序 单个端口扫描 通过net.Dial(network, address)来连接一个地址的特定端口(network参数支持TCP、UDP、IP以及Unix Socket) 1 2 3 4 conn, err := net.Dial("tcp", "scanme.nmap.org:80") if err != nil{ return } 使用goroutine实现并发扫描 信号量+循环 信号量使用sync.WaitGroup实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var wg sync.WaitGroup for i := 1; i <= 1024; i++ { wg.Add(1) go func(j int) { defer wg.Done() address := fmt.Sprintf("scanme.nmap.org:%d", j) conn, err := net.Dial("tcp", address) if err !...

Jul. 27, 2021 · 11 min · 2214 words

2021夏令营记录

前言 基本情况: 学校:西南某末流985 专业:软件工程 成绩:48/209(去年保研率16%)+2.5加分,保研边缘 项目:一个国二 由于成绩较为拉垮,所以没有报特别好的学校(还是不去当炮灰了吧) 报名与入营结果: 北京邮电大学网络空间安全学院:入营顺利参加面试,最后拿到优营和offer 四川大学网络空间安全学院:入营,笔试被刷 中国科学院信息工程研究所:入营,优营 电子科技大学网络空间安全学院:入营 武汉大学网络空间安全学院:未入营 Timetable: 北京邮电大学网络空间安全学院 整体体验一般 7月2号开营,但是1号上午还没出入营结果,打了三个电话才接,说是下午晚上陆续会出 然后下午出了,系统内确认参营,我填了三个志愿: 第一志愿:8组,方向漏洞挖掘、渗透测试、固件安全、5G安全 第二志愿:7组,方向漏洞挖掘与分析、车联网安全、大数据分析 第三志愿:3组,方向网络安全攻防、大数据安全、移动互联网安全 2号下午四点开营仪式,结果下午两点钟打个电话给我问我参不参营,说是夏令营系统坏了之前的选择没了要我再选一次,我??? 然后开营仪式先介绍,然后公布安排,后面两天组内单独安排面试,说是志愿里的组都有面试机会,结果等了一晚上只有3组打了电话给我安排了面试,7组和8组无消息,官网联系方式里的招生群也没法进,所以我就主动发邮件给7组8组的指导老师询问情况(不是陶瓷,单纯问面试情况),无回复,估计是给拒了(说好的志愿里的组都有面试机会呢?) 3号早上面3组,一共只有七个人面: 问了一个政治问题如何看待中美关系,想到啥就说啥了 问研究生的计划,按写好的说 中英文自我介绍,没啥好说的,背稿子 SDN的安全风险(因为我的比赛项目是SDN) 本科除了安全攻防还有其他的研究内容吗 为什么不跟着学校的战队打CTF 然后是问我有什么问题,我问了研究方向发现3组不怎么做攻防,以安全研究和灾备为主 整个面了20分钟左右吧,感觉老师还是很好说话,脾气也还可以,面下来还是很舒服的,但就是没有问什么专业问题,准备了好久的安全没用上,项目问题也没细问 面完之后几个小时老师打电话来,发了offer,主要从事的是网络安全的研究,不怎么搞攻防 面试第二天收到了7组的邮件,说我的报名信息不合适7组要求所以没给面试,还没收到8组的消息,可能是因为成绩太捞了把我刷掉了,听说每个组里面都20+人,3组只有7个人面试 5号原定闭营式取消,只在夏令营系统里面录入优营结果,不知道过了几天,有一天佛系登上去看发现给了优营 四川大学网络空间安全学院 7月4号出的入营结果,5号下午模拟面试 模拟面试要求很严格,环节也很多,要求双机位,而且要求有备用网络,同时是准备了两套会议平台(主WeLink,备用tx会议),三点钟开始整到了晚上八点 后面安排一天进行笔试,然后安排一天进行面试,中间穿插着有专家讲座,实际有内容的就两天时间 笔试考数据结构、OS、计网、数据库还有网络安全基础,通知说的是45分钟100个单选题,实际笔试的时候有180个题,搞得有点离谱,实在做不完只做了120个左右,好多知识也记不太清了属于是蒙的(说明专业课一定要准备到期末考前的水平,考的知识点很广而且很多) 当天晚上就出了笔试结果,我因为只准备了两天的复习所以意料之中的被刷了(刷了大概一半的人) 第二天的面试又刷了2/3左右,最终优营只给了20多个人(一共入营了120+人) 电子科技大学网络空间安全学院 直接发了封邮件就算入营了: 此次夏令营为线上开展宣传活动,主旨在于给导师和学生搭建交流平台,不涉及推免招生。2022年度的推免招生,预计九月中下旬开展,针对于获得就读学校推荐免试攻读研究生资格的优秀应届本科毕业生。具体要求安排以届时通知为准。 说白了就只有一些宣传介绍和导师交流这类的东西,没有面试的环节 中国科学院信息工程研究所 通知上说的是七月初出入营结果,然后在6号晚上十一点才收到入营邮件。去年都是线下,今年突然改线上了,只有优营才能去线下游学,正式的活动时间也就4天(12-16号) 前几天没啥事,主要是提交一些之前要求的材料,以及进行了个线上的心理测试 12号开营仪式,然后有一些专业讲座;13号是研究室介绍,每个研究室一个多小时吧,讲的很详细,然后最后一个小时是和导师线上交流,13号晚上就选志愿了,我果断冲了以攻防为主的六室 14号调试设备,yysy信工所的效率是真的比川大高了不知道多少倍(3个老师同时测,流程也很简单),上午一两个小时就调试完了快50号人的设备,下午直接机试,今年只有编程没有CTF,三个题目一个小时: 左旋转字符串:剑指 Offer 58 - II. 左旋转字符串 - 力扣(LeetCode) (leetcode-cn.com) 有效字符串:20. 有效的括号 - 力扣(LeetCode) (leetcode-cn.com) 最少硬币:322. 零钱兑换 - 力扣(LeetCode) (leetcode-cn.com) 最后一题想到了是用动态规划做,但是没怎么写过动态规划的代码所以没写出来,测试用例就过了一半...

Jul. 3, 2021 · 1 min · 89 words

汇编语言学习笔记

在读《汇编语言》这本书学习汇编语言的时候,在此对每一章的要点进行总结和记录,以便日后复习与查看。 练习环境:Windows 2000 Professional,与书中程序运行环境一致。 第一章 基础知识 汇编语言是一门直接在硬件之上工作的编程语言。由于早期人们使用机器语言(一串二进制数字)进行编程存在不易纠错、晦涩难懂的缺点,所以发明了汇编语言来帮助程序员更高效的编程。汇编语言经编译连接之后可以直接形成由机器指令构成的程序,可以直接被CPU执行。而不同型号的CPU拥有不同的指令集,所以汇编语句对应的机器码可能不尽相同。这本书的汇编语言是基于8086CPU的指令集来进行描述的。 汇编语言包括三个部分: 汇编指令(核心部分):机器码的助记符,有对应的机器码 伪指令:由编译器执行,没有对应的机器码,计算机本身并不执行 其他符号:如+、-、*、/等,由编译器识别,没有对应机器码 汇编语言的指令和数据信息存放在存储器(内存)中。在内存中指令和数据并没有任何区别,本质上都是二进制信息。而决定一段二进制信息是指令还是数据,则由CPU工作时决定。存储器由若干个存储单元构成,单个存储单元的最小单位是字节(Byte,1Byte=8Bit)。 CPU想要进行数据的读写(包括存储器及一些外部器件),必须通过总线,与这些器件进行三类信息的交互(主线逻辑上也分为这三类): 地址信息:CPU首先通过地址总线将要读写的存储单元地址发出 控制信息:随后CPU通过控制总线发送控制命令(内存读/写命令、存储芯片片选命令) 数据信息:再通过数据总线将数据送入对应的存储单元/从对应的存储单元读出数据 一个CPU的地址总线有$N$根,那么可寻址的最多单元数为$2^N$,CPU能够寻址到的所有内存单元构成了这个CPU的内存地址空间;数据总线有$N$根,那么一次性可传输的数据位数为$N$;控制总线的宽度则决定了CPU对外部器件的控制能力。 CPU类型 地址总线宽度 数据总线宽度 寻址能力 一次可传送的数据量 8080 16 8 64KB 1B 8088 20 8 1MB 1B 8086 20 16 1MB 2B 80286 24 16 16MB 2B 80386 32 32 4GB 4B 在一台PC中,装有多个存储器芯片,这些存储器芯片从读写属性上分为RAM和ROM两类。RAM可读可写,但必须带点存储,掉电则内容丢失;ROM只可读不可写,掉电后数据不丢失。从功能上还可以分为三类: RAM:这就是我们平常所说的内存,用于存放CPU使用的绝大部分程序和数据 装有BIOS的ROM:主板和各接口卡均可以有,通过各自的BIOS可以实现基本的输入和输出 各接口卡的RAM:比如显卡上的显存 CPU将上述的所有存储器都当作是内存来看待,所以逻辑上可以将上述的所有存储器映射为一个存储器,也就是CPU的内存地址空间。每个物理存储器在这个逻辑存储器中占有一个地址段,CPU向这个地址段读写数据,就是在向对应的存储器读写数据。所以,我们在基于一个硬件系统编程时,必须要知道这个系统的内存地址空间分配情况。 8086PC的内存地址空间分配情况: 00000H~9FFFFH:主存地址空间(RAM) A0000H~BFFFFH:显存地址空间 C0000H~FFFFFH:各类ROM地址空间 第二章 寄存器 计算机组成原理中有提到:一个计算机系统除了I/O设备外,还有三大部件:运算器、控制器、存储器。其实这三大部件在CPU中也是存在的,它们在CPU中通过CPU内部的总线实现连接和信息传递。 运算器:进行信息处理和运算 控制器:控制各器件进行工作 存储器(寄存器):存储信息 而对于汇编语言来说,CPU中主要的部件就是寄存器。因为程序员可以通过汇编指令来读写寄存器,从而实现对CPU的控制。 不同的CPU寄存器的个数和结构也不尽相同。8086CPU中有14个寄存器,分别为:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。8086CPU中所有的寄存器都是16位(2B)的。 AX、BX、CX、DX这四个寄存器通常用来存放一般的数据,被称为通用寄存器。由于8086CPU上一代的CPU的寄存器都是8位(1B)的,为了保证兼容性,这四个通用寄存器也都可以分为两个8位的寄存器来使用,高位部分称为xH,低位部分称为xL(举例:AX高位为AH,低位为AL)。 8086CPU可以一次性处理一个字节(Byte)或一个字(Word,即两个字节)大小的数据。字型数据存储在寄存器里时,高8位数据存储在寄存器的高位部分,低8位存储在寄存器的低8位部分。 几条汇编指令: 指令 语法 功能 高级语言描述 mov mov a, b 将b数据放进a中 a = b add add a, b 将a和b相加,结果存储在a中 a = a + b sub sub a, b 将a和b相减,结果存储在a中 a = a - b 注意:上述指令的两个操作对象的数据位数必须一致;当最高位产生进位时,进位值不能存储,将会丢失(但这个进位值并未真正被丢弃)...

Feb. 20, 2020 · 12 min · 2517 words

Python爬虫学习总结

网络爬虫的流程和原理 整个网络爬虫的流程可以分为如下的三个步骤: 整个爬虫的过程都可以使用 Python(本文使用 Python 3)来完成,每个步骤使用的模块大致如下: 获取网页:requests、urllib、selenium(模拟浏览器) 解析网页:re正则表达式、BeautifulSoup、HTML 解析器lxml等 存储数据:存储至 txt、csv 等文件或是存储至 MySQL、MongoDB 等数据库 使用 requests 模块发起 HTTP 请求与抓取静态网页 使用 pip 命令安装requests模块。 1 pip install requests 使用requests.get()可以向目标 URL 发送一个GET请求并返回页面内容与信息。 1 2 import requests r = requests.get('https://www.baidu.com') 此时我们就已经实现了一个静态网页的抓取。requests.get()方法返回的对象包含了关于本次请求的信息,通过它的一些实例变量和方法可以进行访问。下面列出了一些常用的变量和方法: status_code :返回 HTTP 状态码 headers :返回请求头 encoding :返回编码类型 text :返回响应内容(Unicode) content :返回响应内容(二进制数据) json() :返回 JSON 响应内容 url :返回网页 URL 上面只是一个所有参数都为默认时的请求。有时候我们可以对requests进行定制,使得请求符合我们的需求。 设置 URL 参数 可以使用一个字典用于保存参数名称与其对应的值,然后通过params参数传入requests.get()方法中。在下面的代码中,将值为value1的参数key1和值为value2的参数key2传入网页http://httpbin.org/get,发现 URL 已经正确编码: 1 2 3 4 5 import requests key = {'key1': 'value1', 'key2': 'value2'} r = requests....

Sep. 27, 2019 · 13 min · 2728 words