Skip to content

虚拟化技术

Updated: at 04:05

虚拟化:把一套硬件通过虚拟化的方式模拟成多套硬件,给不同的系统用 硬件虚拟化:用硬件来实现一部分虚拟化过程,加速虚拟化过程(常见的有VT技术,inter芯片提供的硬件模拟方案) 采用虚拟化技术的例子(有点抽象但又不抽象的例子): 通过使用VT技术,操作系统和应用程序可以在虚拟机中运行,从而使一个物理服务器上能够同时运行多个虚拟机,并且每个虚拟机都感觉自己独占了整个物理服务器。

KVM与QEMU产生的碰撞

KVM是什么

KVM是Linux的一个内核驱动模块, 用来打开并初始化VMX功能, 提供相应的接口以支持虚拟机的运行 这里的VMX功能就是上面说的VT技术中的一部分 通俗的讲KVM就是把一台linux变成一个虚拟化的 hypervisor(一种管理虚拟机的软件层,它可以在物理计算机上创建和运行多个虚拟机。)

QEMU是什么

QEMU虚拟机是一个纯软件的实现。QEMU有整套的虚拟机实现,包括处理器虚拟化、内存虚拟化以及I/O设备的虚拟化,也就是说光有QEMU其实就可以直接运行程序。 通俗的讲QEMU就是我们认识的那种虚拟机

QEMU+KVM

KVM只模拟CPU和内存,因此KVM的缺点就是一个客户机操作系统可以在宿主机上跑起来,但是你看不到它,无法和它沟通。 QEMU通过软件实现了整个虚拟机,因此QEMU的缺点显而易见就是慢了。 于是,有人修改了QEMU代码,把他模拟CPU、内存的代码换成KVM,而网卡、显示器等留着,因此QEMU+KVM就成了一个完整的虚拟化平台。 KVM和QEMU相辅相成,QEMU通过KVM达到了硬件虚拟化的速度,而KVM则通过QEMU来模拟设备。 通俗的讲QEMU就是外壳,而KVM就是核心装置。但是这就像两个零件可以随意组装QEMU可以跟换成其他核心,KVM也可以套上其他外壳

QEMU的详细介绍

QEMU的其他核心有kvm/xen/hyper-v等虚拟化方式,同时还有一个上面说过的纯粹的跨架构的模拟器的模式 Qemu在没开启硬件虚拟化支持的时候实现全系统的虚拟化,Qemu结合下面几种技术共同实现虚拟化:

为什么有了硬件虚拟化之后TCG依旧存在

TCG 起源于 C 编译器后端,后来被简化为 QEMU 的动态代码生成器。 QEMU 虚拟化的TCG模式采用的思路是二进制指令翻译技术

动态翻译的基本思想就是把每一条 Guest机的 指令切分成为若干条微操作,每条微操作由一段简单的 C 代码来实现,运行时通过一个动态代码生成器(TCG)把这些微操作组合成一个函数,最后执行这个函数,就相当于执行了一条 Guest机的。

这里我们把要运行的目标程序叫做Guest,实际运行的代码的物理机器叫做Host

为什么我们费劲力气要把目标代码翻译一遍增加cpu的消耗呢。当我们需要在一个架构的cpu上运行另一个架构的cpu指令时,TCG的威力就完全发挥出来了。 TCG会把目标架构的cpu指令动态翻译成物理主机的cpu架构的一系列指令,这就类似于某些语言的解释执行一样。 硬件虚拟化的缺点就是无法运行其他架构的程序,它只能加速本架构的程序的模拟执行。

TCG本质上属于DBT,即dynamic binary translation动态二进制转换,相应的还有SBT,即static binary translation静态二进制转换。

unicorn

事实上,要用qemu来执行一个程序是相当的复杂的,由此就诞生了unicorn可以说是qemu的高度简化和封装 unicorn的诞生就是为了模拟执行某一个代码片段的, 其关注的方面想对qemu来说更加细微,主要是在每一条汇编和内存层面,而qemu更加关注整个程序的运行。

unicorn的实现原理

  1. 只保留qemu tcg cpu模拟器的部分,移除掉其他如device,rom/bios等和系统模拟相关的代码
  2. 尽量维持qemu cpu模拟器部分不变,这样才容易和上游的qemu tcg代码同步
  3. 重构tcg的代码从而可以更好的实现线程安全性及同时运行多个unicorn实例
  4. qemu tcg不是一个Instrumentation框架,而是一个轻量级的虚拟机,而unicorn的目标是实现一个有多种语言绑定的Instrumentation框架,可以在多个级别跟踪代码的运行并执行设置好的回调函数。

Instrumentation框架是什么? Instrumentation框架是一种软件开发工具,用于在应用程序执行期间(即动态时期)监视、收集和分析数据。 简单来说Instrumentation框架就是一类hook插桩工具。

在unicorn中虚拟地址(GVA)就等于物理地址(GPA)。 (这句话在写unicorn的代码中很关键

qiling框架

基于unicorn框架的再次抽象 但是笔者不经常用到对整个程序进行模拟的情况,所以详细的用法就不一一列举了,等之后用到在进行系统的学习。 项目地址 https://github.com/qilingframework/qiling 相关架构的文件的链接库 https://github.com/qilingframework/rootfs/tree/32c4fcf52f4aa0efaa1cb03ab6b2186c61f512c6 采用pip安装

QilingLab入门

快速了解的qiling框架的测试题目 附件直接放在文末了,需要的可以自行复现、

qilinglab-aarch64.7z 测试程序

import struct

from qiling import *
from qiling.const import *
from qiling.os.mapper import QlFsMappedObject


# Challenge1
def challenge1(ql: Qiling):
    addr = 0x1337
    ql.mem.map(addr // 4096 * 4096, 0x1000)
    ql.mem.write(addr, ql.pack16(1337))


# Challenge2
def fake_uname(ql: Qiling, pName, *args):
    ql.mem.write(pName, b'QilingOS\x00')
    ql.mem.write(pName + 65 * 3, b'ChallengeStart\x00')


def challenge2(ql: Qiling):
    ql.os.set_syscall('uname', fake_uname, QL_INTERCEPT.EXIT)


# Challenge3
def fake_getrandom(ql: Qiling, pBuf, buflen, flag, *args):
    ql.mem.write(pBuf, b'\x01' * buflen)


class Fake_urandom(QlFsMappedObject):

    def read(self, size):
        if size == 1:
            return b'\x00'
        else:
            return b'\x01' * size

    def fstat(self):
        return -1

    def close(self):
        return 0


def challenge3(ql: Qiling):
    ql.add_fs_mapper("/dev/urandom", Fake_urandom())
    ql.os.set_syscall('getrandom', fake_getrandom, QL_INTERCEPT.EXIT)


# Challenge4
def stop(ql: Qiling) -> None:
    # ql.arch.regs.write("x1", 0)
    ql.arch.regs.write("x0", 1)


def challenge4(ql: Qiling):
    address = 0x555555554000 + 0xFE0
    ql.hook_address(stop, address)


# Challenge5
def fake_rand(ql: Qiling, *args):
    ql.arch.regs.write("x0", 0)
    # return 0


def challenge5(ql: Qiling):
    ql.os.set_api("rand", fake_rand)


# Challenge6
def stop2(ql: Qiling) -> None:
    # ql.arch.regs.write("x1", 0)
    ql.arch.regs.write("x0", 0)


def challenge6(ql: Qiling):
    address = 0x555555554000 + 0x1118
    ql.hook_address(stop2, address)


# Challenge7
def fake_sleep(ql: Qiling, *args):
    ql.arch.regs.write("w0", 0)

def stop3(ql: Qiling) -> None:
    ql.arch.regs.write("w0", 0)

def challenge7(ql: Qiling):
    #ql.os.set_api("sleep", fake_sleep)
    address = 0x555555554000 + 0x1154
    ql.hook_address(stop3, address)

# Challenge8
def fake_nop(ql: Qiling):
    num = 0x3DFCD6EA00000539
    num_address_list = ql.mem.search(ql.pack64(num))
    for num_address in num_address_list:
        s1_address = num_address - 8
        s1 = ql.mem.read(s1_address, 0x18)
        s2_address, num2_addr, flag = struct.unpack('QQQ', s1)
        random_data = ql.mem.string(s2_address)
        print(hex(s2_address),":",random_data,"   ",hex(num2_addr))
        if random_data == 'Random data':
            ql.mem.write(flag, b'\x01')
            break


def challenge8(ql: Qiling):
    address = 0x555555554000 + 0x11dc
    ql.hook_address(fake_nop, address)


# Challenge9
def fake_strcmp(ql: Qiling, *args):
    ql.arch.regs.write("x0", 0)


def challenge9(ql: Qiling):
    ql.os.set_api('strcmp', fake_strcmp)


# Challenge10
class Fake_cmdline(QlFsMappedObject):

    def read(self, size):
        return b'qilinglab'

    def fstat(self):
        return -1

    def close(self):
        return 0


def challenge10(ql: Qiling):
    ql.add_fs_mapper("/proc/self/cmdline", Fake_cmdline())


# Challenge11
def fake_end(ql: Qiling) -> None:
    ql.arch.regs.write("x1", 0x1337)


def challenge11(ql: Qiling):
    ql.hook_address(fake_end, 0x555555554000+ 0x1400)


if __name__ == "__main__":
    target = ['./qilinglab-aarch64']
    rootfs = "./rootfs/arm64_linux"
    ql = Qiling(target, rootfs, verbose=QL_VERBOSE.DISABLED)
    challenge1(ql)
    challenge2(ql)
    challenge3(ql)
    challenge4(ql)
    challenge5(ql)
    challenge6(ql)
    challenge7(ql)
    challenge8(ql)
    challenge9(ql)
    challenge10(ql)
    challenge11(ql)
    ql.run()

运行结果 image

小结

在学习脱掉强壳的路上,常用到unicorn,不知道它如何实现怎么行呢?(

参考链接 https://bbs.kanxue.com/thread-277163.htm#msg_header_h2_0 https://bbs.kanxue.com/thread-271557.htm https://bbs.kanxue.com/thread-277412.htm 附件下载 http://s0rry.cn/upload/2023/06/qilinglab-aarch64.7z