文档章节

Linux x64下hook系统调用execve的正确方法

macwe
 macwe
发布于 2016/01/16 14:45
字数 837
阅读 1298
收藏 4

Linux x64下hook系统调用execve的正确方法

1.概述

Linux下hook内核execve系统调用的方法有很多:

  1. 可以使用inline-hook,将sys_execve函数头部修改成jmp指令跳转到我们自己hook函数;
  2. 可以使用内核本身的kprobes功能,内核需包含CONFIG_KPROBES编译选项;
  3. 还可以使用hook系统调用表的方法,替换其中__NR_execve项为我们的hook函数的地址。

本文介绍一下采用hook系统调用表项的方法该如何实现。

2.问题

一般的hook系统调用表项代码要这样写(伪代码)

asmlinkage long my_execve_hook64(const char __user *filename,
                                 const char __user *const __user *argv,
                                 const char __user *const __user *envp)
{
	long rc;
	
	rc = do_something(filename, argv, envp);
	if (0 != rc)
	    return rc; /* 拦截execve */
	
	return orig_execve(filename, argv, envp); /* 执行原来的execve路径 */
}

void execve_hook_init(void)
{
	unsigned long *sct;
	
	sct = get_sys_call_table();
	
	make_kernel_page_readwrite();
	preempt_disable();
	
	orig_execve = (void *)sct[__NR_execve];
	sct[__NR_execve] = (unsigned long)my_execve_hook64;
	
	preempt_enable();
	make_kernel_page_readonly();
}

在x64系统中,我们按照上面思路写好我们的内核模块并加载,结果却发现任何操作都会导致Segment fault错误!看来这种方式有些不对。

3.分析

这里以我的CentOS 7.2(3.10.0-327.3.1.el7.x86_64)为例来分析一下execve的调用路径:

(Ring3)sysenter -> (Ring0)system_call -> stub_execve -> sys_execve

来看看stub_execve函数:

(gdb) disassembl stub_execve
Dump of assembler code for function stub_execve:
   0xffffffff81715010 <+0>:     add    $0x8,%rsp       # 相当于把call stub_execve变成了jmp stub_execve
   0xffffffff81715014 <+4>:     sub    $0x30,%rsp      # SAVE_REST
   0xffffffff81715018 <+8>:     mov    %rbx,0x28(%rsp)
   0xffffffff8171501d <+13>:    mov    %rbp,0x20(%rsp)
   0xffffffff81715022 <+18>:    mov    %r12,0x18(%rsp)
   0xffffffff81715027 <+23>:    mov    %r13,0x10(%rsp)
   0xffffffff8171502c <+28>:    mov    %r14,0x8(%rsp)
   0xffffffff81715031 <+33>:    mov    %r15,(%rsp)
   0xffffffff81715035 <+37>:    mov    %gs:0xaf80,%r11 # FIXUP_TOP_OF_STACK
   0xffffffff8171503e <+46>:    mov    %r11,0x98(%rsp)
   0xffffffff81715046 <+54>:    movq   $0x2b,0xa0(%rsp)
   0xffffffff81715052 <+66>:    movq   $0x33,0x88(%rsp)
   0xffffffff8171505e <+78>:    movq   $0xffffffffffffffff,0x58(%rsp)
   0xffffffff81715067 <+87>:    mov    0x30(%rsp),%r11
   0xffffffff8171506c <+92>:    mov    %r11,0x90(%rsp)
   0xffffffff81715074 <+100>:   callq  0xffffffff8123a150 <SyS_execve>   # 调用sys_execve
   0xffffffff81715079 <+105>:   mov    %rax,0x50(%rsp)
   0xffffffff8171507e <+110>:   mov    (%rsp),%r15     # RESTORE_REST
   0xffffffff81715082 <+114>:   mov    0x8(%rsp),%r14
   0xffffffff81715087 <+119>:   mov    0x10(%rsp),%r13
   0xffffffff8171508c <+124>:   mov    0x18(%rsp),%r12
   0xffffffff81715091 <+129>:   mov    0x20(%rsp),%rbp
   0xffffffff81715096 <+134>:   mov    0x28(%rsp),%rbx
   0xffffffff8171509b <+139>:   add    $0x30,%rsp
   0xffffffff8171509f <+143>:   jmpq   0xffffffff81714c70 <int_ret_from_sys_call>
End of assembler dump.

这不是一个符合gcc调用约定的函数。我们上面的hook方法必定导致栈不平衡,从而导致错误。

4.解决

看来我们要自己平衡一下栈了,我们也要写一个stub_execve函数来间接调用hook函数,这个函数要用汇编来写:

/**
 * @filename my_stub_execve_64.S
 */
 
.text
.globl  my_stub_execve_hook64
.type   my_stub_execve_hook64, @function

my_stub_execve_hook64:
    /**
     * 保存寄存器状态, 保证之后调用原来的stub_execve的时候CPU执行环境一致
     * 其中rdi,rsi,rdx,rcx,rax,r8,r9,r10,r11保存sysenter的参数,rbx作为临时变量
     */
    pushq   %rbx
    pushq   %rdi
    pushq   %rsi
    pushq   %rdx
    pushq   %rcx
    pushq   %rax
    pushq   %r8
    pushq   %r9
    pushq   %r10
    pushq   %r11

	/* 调用自己的hook函数 */
    call    my_execve_hook64
    test    %rax, %rax
    movq    %rax, %rbx

    /* 恢复寄存器状态 */
    pop     %r11
    pop     %r10
    pop     %r9
    pop     %r8
    pop     %rax
    pop     %rcx
    pop     %rdx
    pop     %rsi
    pop     %rdi

    jz      my_stub_execve_hook64_done
    
    /* my_execve_hook64返回值为非0时 */
    movq    %rbx, %rax
    pop     %rbx
    ret   /* 这里不一定要jmp int_ret_from_sys_call,反正execve已经被我们拦截了 */
    
    /* my_execve_hook64返回值为0时 */
my_stub_execve_hook64_done:
    pop     %rbx
    jmp     *orig_sys_call_table(, %rax, 8) /* 调用orig_execve, 既stub_execve */

然后使用my_stub_execve_hook64函数来替换系统调用表中的__NR_execve项即可:

extern long my_stub_execve_hook64(char *, char **, char **);
......
orig_execve = (void *)sct[__NR_execve];
sct[__NR_execve] = (unsigned long) my_stub_execve_hook64;

hook以后的调用路径如下:

(Ring3)sysenter -> (Ring0)system_call -> my_stub_execve_hook64 
          -> my_execve_hook64 -> stub_execve -> sys_execve

5.参考资料

http://stackoverflow.com/questions/8372912/hooking-sys-execve-on-linux-3-x

http://lxr.free-electrons.com/source/arch/x86/kernel/entry_64.S?v=3.10#L877

© 著作权归作者所有

macwe
粉丝 14
博文 18
码字总数 22244
作品 0
海淀
其他
私信 提问
加载中

评论(3)

E_Bwill
E_Bwill

引用来自“E_Bwill”的评论

您好,我最近也在做相关的学习,在参考您的这篇文章实践的时候,insmod后执行命令会出现“/lib64/ld-linux-x86-64.so.2: bad ELF interpreter: 没有那个文件或目录”的错误,kernel是:3.10.0-862.el7.x86_64,不知道您是否能指点一二。还有一个问题想请教,使用kprobes或者LSM进行hook是否会有更好的稳定性呢?🙏

引用来自“macwe”的评论

https://github.com/ysrc/yulong-hids/tree/master/syscall_hook 这里有一个开源的实现,你参考下。
他们的代码我很早就看到了,反正不是很稳定,至少我跑起来是测试服务器直接挂了
macwe
macwe 博主

引用来自“E_Bwill”的评论

您好,我最近也在做相关的学习,在参考您的这篇文章实践的时候,insmod后执行命令会出现“/lib64/ld-linux-x86-64.so.2: bad ELF interpreter: 没有那个文件或目录”的错误,kernel是:3.10.0-862.el7.x86_64,不知道您是否能指点一二。还有一个问题想请教,使用kprobes或者LSM进行hook是否会有更好的稳定性呢?🙏
https://github.com/ysrc/yulong-hids/tree/master/syscall_hook 这里有一个开源的实现,你参考下。
E_Bwill
E_Bwill
您好,我最近也在做相关的学习,在参考您的这篇文章实践的时候,insmod后执行命令会出现“/lib64/ld-linux-x86-64.so.2: bad ELF interpreter: 没有那个文件或目录”的错误,kernel是:3.10.0-862.el7.x86_64,不知道您是否能指点一二。还有一个问题想请教,使用kprobes或者LSM进行hook是否会有更好的稳定性呢?🙏
腾讯开源恶意软件分析工具 HaboMalHunter

1月17日,腾讯宣布正式开源恶意软件分析工具 HaboMalHunter。 HaboMalHunter 是哈勃分析系统的开源子项目,用于 Linux 平台下进行自动化分析、文件安全性检测的开源工具。使用该工具能够帮助...

两味真火
2017/01/17
4.3K
7
使用kprobes,截获execve系统调用

关于截获execve等系统调用,很久以来存在一个问题:新函数不能直接调旧函数, 否则导致stack不平衡,出错。 曾经有高人用一串的汇编代码去平衡堆栈, 但对于偶们这些汇编菜鸟来说, 连阅读都...

LiSteven
2012/12/29
129
0
如何增强 Linux 内核中的访问控制安全

背景 前段时间,我们的项目组在帮客户解决一些操作系统安全领域的问题,涉及到windows,Linux,macOS三大操作系统平台。无论什么操作系统,本质上都是一个软件,任何软件在一开始设计的时候,...

2018/12/13
0
0
Habo Linux 恶意软件分析系统--HaboMalHunter

HaboMalHunter 是哈勃分析系统的开源子项目,用于 Linux 平台下进行自动化分析、文件安全性检测的开源工具。使用该工具能够帮助安全分析人员简洁高效的获取恶意样本的静态和动态行为特征。分...

jingleyang
2017/01/17
2.5K
0
linux下的fork和execve函数使用

fork函数是linux中创建进程的函数,linux创建进程只有用fork,别无他法。我自己写代码fork用的不多,对它的一些细节还不是清楚,今天抽空研究了下fork,把它的一些关键点总结一下,以后用到了...

yixinuestc
2018/06/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Phpstorm2018 永久激活

1、安装phpstorm,安装包请自行官网下载 http://www.jetbrains.com/phpstorm/download/ 2、下载JetbrainsCrack.jar文件,存放至你的phpstorm执行文件同级目录下 下载JetbrainsCrack.jar 提取...

happyfish319
45分钟前
9
0
谈一谈Android进程间通信的几种方式

###来看一下Android中除了AIDL还有哪些进程间通信的方式: 1、Bundle Bundle实现了Parcelable,所以在Android中我们可以通过Intent在不同进程间传递Bundle数据。 但是在Intent 传输数据的过程...

二营长的意大利炮手
45分钟前
9
0
互联网薪资“高开低走”,你的能力是否真的可以匹配高薪?

对于国内外主流互联网大厂,技术出身似乎已经成为各大掌门人的必备标签。谷歌 CEO 桑达尔·皮查伊、马克·扎克伯格、李彦宏、马化腾、雷军等等皆为技术人出身,都曾参与了公司内部重要产品的...

Java技术剑
47分钟前
12
0
java 多线程

线程声明周期 线程的五个状态:新建,就绪,运行,阻塞,死亡。 其中就绪和运行两个状态客户互相转换,但运行到阻塞,阻塞到就绪,只能单向转换。 刚new出的线程就是【新建】状态,调用start...

雷开你的门
49分钟前
17
0
构造器Constructor是否可被overrid

构造器不能被重写,不能用static修饰构造器,只能用public private protected这三个权限修饰符,且不能有返回语句。

无名氏的程序员
52分钟前
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部