虚拟化:把一套硬件通过虚拟化的方式模拟成多套硬件,给不同的系统用 硬件虚拟化:用硬件来实现一部分虚拟化过程,加速虚拟化过程(常见的有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结合下面几种技术共同实现虚拟化:
- soft tlb / Softmmu/内存模拟
- 虚拟中断控制器/中断模拟
- 总线/设备模拟
- TCG的CPU模拟
为什么有了硬件虚拟化之后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的实现原理
- 只保留qemu tcg cpu模拟器的部分,移除掉其他如device,rom/bios等和系统模拟相关的代码
- 尽量维持qemu cpu模拟器部分不变,这样才容易和上游的qemu tcg代码同步
- 重构tcg的代码从而可以更好的实现线程安全性及同时运行多个unicorn实例
- 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()
运行结果
小结
在学习脱掉强壳的路上,常用到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