1. hook一般syscall
在安全、性能分析等領(lǐng)域,經(jīng)常會(huì)需要對(duì)系統(tǒng)調(diào)用syscall進(jìn)行hook。有些模塊在kernel代碼中已經(jīng)預(yù)先hook,例如syscall trace event。
通常syscall使用sys_call_table[]數(shù)組來(lái)間接調(diào)用:
kernelarchx86kernelentry_64.S:
ENTRY(system_call)
call *sys_call_table(,%rax,8) # XXX: rip relative
sys_call_table[]數(shù)組中保存的是所有系統(tǒng)調(diào)用的函數(shù)指針:
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
__SYSCALL(__NR_read, sys_read)
__SYSCALL(__NR_write, sys_write)
__SYSCALL(__NR_open, sys_open)
__SYSCALL(__NR_close, sys_close)
...
};
對(duì)于其他沒有預(yù)置代碼的模塊來(lái)說(shuō),需要在運(yùn)行的時(shí)候動(dòng)態(tài)hook,通常我們使用inline hook。inline hook的好處是hook完以后,運(yùn)行時(shí)零開銷。
實(shí)例代碼:
void syscallxxx_hook_init(void)
{
unsigned long *sct;
void ** g_syscall_table;
g_syscall_table = (void **)kallsyms_lookup_name("sys_call_table");
make_kernel_page_readwrite();
preempt_disable();
/* (1) 備份原有g(shù)_syscall_table[]數(shù)組中的函數(shù)指針 */
orig_syscallxxx = (void *)g_syscall_table[__NR_syscallxxx];
/* (2) 把g_syscall_table[]數(shù)組值改為新的函數(shù)指針 */
sct[__NR_syscallxxx] = (unsigned long)new_syscallxxx;
preempt_enable();
make_kernel_page_readonly();
}
↓
asmlinkage long new_syscallxxx(...)
{
long rc;
/* (2.1) 做一些hook增加的事情 */
rc = do_something(...);
if (0 != rc)
return rc;
/* (2.2) 調(diào)用原有的syscall處理 */
return orig_syscallxxx(....);
}
這種hook方式在大部分情況下工作正常,但是某些特殊的系統(tǒng)調(diào)用會(huì)工作異常。
2. hook stub syscall
2.1 stub_xxx 原理
在4.5版本及以下的內(nèi)核中,x86架構(gòu)對(duì)某些系統(tǒng)調(diào)用有特殊處理。我們可以在sys_call_table[]數(shù)組中看到的函數(shù)不是sys_xxx而是stub_xxx:
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
__SYSCALL(__NR_rt_sigreturn, stub_rt_sigreturn)
__SYSCALL(__NR_clone, stub_clone)
__SYSCALL(__NR_fork, stub_fork)
__SYSCALL(__NR_vfork, stub_vfork)
__SYSCALL(__NR_execve, stub_execve)
__SYSCALL(__NR_sigaltstack, stub_sigaltstack)
__SYSCALL(__NR_iopl, stub_iopl)
...
};
這有點(diǎn)出乎我們的意料,字面上理解是一些樁函數(shù),我們看看其具體做了些什么:
:
/*
Certain special system calls that need to save a complete full stack frame.
*/
PTREGSCALL label,func,arg
ENTRY(label)
PARTIAL_FRAME 1 8 /* offset 8: return address */
subq $REST_SKIP, %rsp
CFI_ADJUST_CFA_OFFSET REST_SKIP
call save_rest
DEFAULT_FRAME -2 8 /* offset 8: return address */
leaq 8(%rsp), arg /* pt_regs pointer */
call func /* (1.1) 調(diào)用實(shí)際的系統(tǒng)調(diào)用sys_xxx()函數(shù) */
jmp ptregscall_common
CFI_ENDPROC
END(label)
.endm
(1) stub_clone/fork/vfork/sigaltstack/iopl 函數(shù)的定義 */
PTREGSCALL stub_clone, sys_clone, %r8
PTREGSCALL stub_fork, sys_fork, %rdi
PTREGSCALL stub_vfork, sys_vfork, %rdi
PTREGSCALL stub_sigaltstack, sys_sigaltstack, %rdx
PTREGSCALL stub_iopl, sys_iopl, %rsi
ENTRY(ptregscall_common)
DEFAULT_FRAME 1 8 /* offset 8: return address */
RESTORE_TOP_OF_STACK %r11, 8
movq_cfi_restore R15+8, r15
movq_cfi_restore R14+8, r14
movq_cfi_restore R13+8, r13
movq_cfi_restore R12+8, r12
movq_cfi_restore RBP+8, rbp
movq_cfi_restore RBX+8, rbx
ret $REST_SKIP /* pop extended registers */
CFI_ENDPROC
END(ptregscall_common)
(2) stub_execve函數(shù)的定義 */
ENTRY(stub_execve)
CFI_STARTPROC
addq $8, %rsp
PARTIAL_FRAME 0
SAVE_REST
FIXUP_TOP_OF_STACK %r11
movq %rsp, %rcx
call sys_execve /* (2.1) 調(diào)用實(shí)際的系統(tǒng)調(diào)用sys_execve()函數(shù) */
RESTORE_TOP_OF_STACK %r11
movq %rax,RAX(%rsp)
RESTORE_REST
jmp int_ret_from_sys_call
CFI_ENDPROC
END(stub_execve)
/*
sigreturn is special because it needs to restore all registers on return.
This cannot be done with SYSRET, so use the IRET return path instead.
*/
(3) stub_rt_sigreturn函數(shù)的定義 */
ENTRY(stub_rt_sigreturn)
CFI_STARTPROC
addq $8, %rsp
PARTIAL_FRAME 0
SAVE_REST
movq %rsp,%rdi
FIXUP_TOP_OF_STACK %r11
call sys_rt_sigreturn /* (3.1) 調(diào)用實(shí)際的系統(tǒng)調(diào)用sys_rt_sigreturn()函數(shù) */
movq %rax,RAX(%rsp) # fixme, this could be done at the higher layer
RESTORE_REST
jmp int_ret_from_sys_call
CFI_ENDPROC
END(stub_rt_sigreturn)
為什么系統(tǒng)要對(duì)這幾個(gè)系統(tǒng)調(diào)用做stub_xxx的特殊處理?
注釋中的一段話說(shuō)明了大概原因:
/*
* Certain special system calls that need to save a complete full stack frame.
* 某些特殊的系統(tǒng)調(diào)用需要保存完整的完整堆棧幀。
*/
針對(duì)這類特殊的系統(tǒng)調(diào)用,我們有兩種方法來(lái)進(jìn)行hook。
2.2 方法1:hook stub_xxx
第一種方法我們還是繼續(xù)替換sys_call_table[]數(shù)組中函數(shù)指針,但是要自己處理hook函數(shù)的棧平衡。
寫一段自己的stub_new_syscallxxx函數(shù)來(lái)替換原有的stub_syscallxxx函數(shù):
stub_new_syscallxxx:
/**
(1.1) 保存寄存器狀態(tài), 保證之后調(diào)用原來(lái)的stub_syscallxxx的時(shí)候CPU執(zhí)行環(huán)境一致
其中rdi,rsi,rdx,rcx,rax,r8,r9,r10,r11保存sysenter的參數(shù),rbx作為臨時(shí)變量
*/
pushq %rbx
pushq %rdi
pushq %rsi
pushq %rdx
pushq %rcx
pushq %rax
pushq %r8
pushq %r9
pushq %r10
pushq %r11
(1.2) 調(diào)用自己的hook函數(shù) */
call new_syscallxxx
test %rax, %rax
movq %rax, %rbx
(1.3) 恢復(fù)寄存器狀態(tài) */
pop %r11
pop %r10
pop %r9
pop %r8
pop %rax
pop %rcx
pop %rdx
pop %rsi
pop %rdi
jz new_syscallxxx_done
(2.1) new_syscallxxx返回值為非0時(shí) */
movq %rbx, %rax
pop %rbx
ret /* 這里不一定要jmp int_ret_from_sys_call,反正syscallxxx已經(jīng)被我們攔截了 */
(2.2) new_syscallxxx返回值為0時(shí) */
new_syscallxxx_done:
pop %rbx
jmp*orig_sys_call_table(,%rax,8)/*調(diào)用原始的stub_syscallxxx*/
這種方法要小心處理調(diào)用堆棧,在我們hook函數(shù)運(yùn)行之前要小心的保護(hù)堆棧,在hook函數(shù)運(yùn)行完成后要完全恢復(fù)堆棧。而且不方便實(shí)現(xiàn)post hook。
2.3 方法2:hook call sys_xxx
另一種方法我們替換stub_syscallxxx函數(shù)中的call sys_syscallxxx語(yǔ)句。例如:
ENTRY(stub_execve)
CFI_STARTPROC
addq $8, %rsp
PARTIAL_FRAME 0
SAVE_REST
FIXUP_TOP_OF_STACK %r11
movq %rsp, %rcx
call sys_execve // 替換call語(yǔ)句中的sys_execve為new_sys_execve
RESTORE_TOP_OF_STACK %r11
movq %rax,RAX(%rsp)
RESTORE_REST
jmp int_ret_from_sys_call
CFI_ENDPROC
END(stub_execve)
查看原始指令碼:
(gdb) disassemble /r stub_execve
Dump of assembler code for function stub_execve:
0xffffffff8146f7e0 <+0>: 48 83 c4 08 add $0x8,%rsp
...
0xffffffff8146f847 <+103>: e8 74 b2 b9 ff callq 0xffffffff8100aac0
// call sys_execve ...
0xffffffff8146f890 <+176>: e9 77 fd ff ff jmpq 0xffffffff8146f60c
End of assembler dump.
(gdb) p sys_execve
$2={long(constchar*,constchar*const*,constchar*const*,structpt_regs*)}0xffffffff8100aac0
我們可以看到call sys_execve對(duì)應(yīng)的命令碼為e8 74 b2 b9 ff,其中:
-
e8對(duì)應(yīng)call指令。
-
ffb9b274表示被調(diào)用函數(shù)和當(dāng)前pc的偏移:
被call函數(shù)地址 - 當(dāng)前地址 - 當(dāng)前指令長(zhǎng)度 = offset
0xffffffff8100aac0-0xffffffff8146f847-5=0xFFFFFFFFFFB9B274&0xFFFFFFFF=0xFFB9B274
所以我們只要定義個(gè)參數(shù)完全一致的新函數(shù)new_sys_execve(),把sys_execve()的對(duì)應(yīng)偏移ffb9b274替換成new_sys_execve()的相對(duì)偏移即可。
static asmlinkage long new_sys_execve(const char __user * filename,
const char __user * const __user * argv,
const char __user * const __user * envp, struct pt_regs *regs) {
size_t exec_line_size;
char * exec_str = NULL;
char ** p_argv = (char **) argv;
long ret = 0;
/* (1) pre hook 點(diǎn) */
/* Finally, call the original sys_execve */
/* (2) 調(diào)用原始系統(tǒng)調(diào)用 */
ret = orig_sys_execve_fn(filename, argv, envp, regs);
/* (3) post hook 點(diǎn) */
printk("orig_sys_execve_fn ret = %d ", ret);
return ret;
}
具體代碼放在inlinehook_syscall_example。
參考文檔:
1.x86平臺(tái)inline hook原理和實(shí)現(xiàn)
2.execmon
3.Linux x64下hook系統(tǒng)調(diào)用execve的正確方法
審核編輯 :李倩
-
模塊
+關(guān)注
關(guān)注
7文章
2713瀏覽量
47485 -
代碼
+關(guān)注
關(guān)注
30文章
4788瀏覽量
68625 -
inline
+關(guān)注
關(guān)注
0文章
4瀏覽量
1641
原文標(biāo)題:Inline Hook Syscall 詳解
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論