- 论坛徽章:
- 0
|
转自: http://www.linuxforum.net/forum/showflat.php?Cat=&Board=security&Number=534712&page=0&view=collapsed&sb=5&o=31&fpart=
|=----------------=[ strace, anti-strace, anti anti-strace ]=---------------=|
|=--------------------------------------------------------------------------=|
|=-----------------=[ CoolQ ]=----------------=|
|=--------------------------------------------------------------------------=|
--[ 内容
0 - 前言
1 - strace的原理
2 - strace死循环的分析
3 - anti-strace
3.1 方法一
3.2 方法二
3.3 方法三
4 - anti anti-strace
4.1 int3的情况
4.2 kill的情况
5 - 参考
6 - strace.4.5.8.patch
--[ 0 - 前言
前面在介绍Burneye加密文件的时候,遇到了两个问题,一个是GDB中无法设置断点,另一个
问题是strace时死循环。GDB的问题已经找到,无法设置断点是GDB的一个Bug,具体的结
论见[1].至于strace的问题,一直没有解决,如果真能写出一个让strace死循环的程序,
也算是一种anti-strace的技术。但是一直没有将死循环的现象用程序重现。
后来经Grip2的指点,发现了问题的原因,经过对内核和strace源代码的研究,写了一个
防止strace死循环的patch,之后更进一步的patch,使得strace更加健壮,能够跟踪anti-
strace程序的系统调用。
在这里感谢Grip2的帮助和测试程序。
本文的环境是Redhat Fedora Core 2, Linux 2.6.5/2.6.8.1,
gcc 3.3.1, strace 4.5.8
--[ 1 - strace的原理
要想了解strace的原理,首先得谈谈ptrace。
ptrace是操作系统为了调试为用户程序提供的系统接口。先来看看ptrace的用法:[2]
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void
*data)
strace需要使用的__ptrace_request主要有以下几个:
被调试的进程)
PTRACE_TRACEME 自己主动提供被跟踪的请求,当被跟踪的进程收到信号时,会先被
父进程截获.同样,execve时也是如此.
监视进程strace)
PTRACE_GETREGS 获得被跟踪进程的寄存器状况,详细的结构请参见asm/user.h的
user_regs_struct
PTRACE_PEEKDATA 获得系统堆栈中的参数
PTRACE_SYSCALL 这是最重要的,每次本跟踪的进程在系统调用时,ptrace会返回
两次,一次是系统调用之前,会调用PTRACE_PEEKDATA获得参数
值,另一次是系统调用之后,会调用PTRACE_GETREGS获得返回值
ENTRY(system_call)
...
# system call tracing in operation
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
...
syscall_trace_entry:
movl $-ENOSYS,EAX(%esp)
movl %esp, %eax
xorl %edx,%edx
call do_syscall_trace do_sigaction()
2298 if (signal_pending(current)) {
2299 /*
2300 * If there might be a fatal signal pending on multiple
2301 * threads, make sure we take it before changing the action.
2302 */
2303 spin_unlock_irq(¤t->sighand->siglock);
2304 return -ERESTARTNOINTR;
2305 }
也就是说当程序执行sys_signal时,如果有信号还未处理,就直接返回-ERESTARTNOINTR
接下来当系统调用返回时,会依次调用
resume_userspace->work_pending->work_notify_sig->do_notify_resume->do_sign
al
590 no_signal:
591 /* Did we come from a system call? */
592 if (regs->orig_eax >= 0) {
593 /* Restart the system call - no handlers present */
594 if (regs->eax == -ERESTARTNOHAND ||
595 regs->eax == -ERESTARTSYS ||
596 regs->eax == -ERESTARTNOINTR) {
597 regs->eax = regs->orig_eax;
598 regs->eip -= 2;
599 }
600 if (regs->eax == -ERESTART_RESTARTBLOCK){
601 regs->eax = __NR_restart_syscall;
602 regs->eip -= 2;
603 }
604 }
605 return 0;
606 }
此时, regs->eip -= 2;正好代表int $0x80 (0xcd 0x80)两个字节,可见,内核重启
系统调用。
那么,究竟什么时候signal_pending(current)为真呢?经过GDB调试,发现strace在处理
sys_signal系统调用时,syscall.c:trace_syscall会调用signal的sys_signal函数
sys_res = (*sysent[tcp->scno].sys_func)(tcp)
signal.c::sys_signal()
...
#ifndef USE_PROCFS
if (tcp->u_arg[0] == SIGTRAP) {
tcp->flags |= TCB_SIGTRAPPED;
kill(tcp->pid, SIGSTOP);
}
#endif /* !USE_PROCFS */
死循环的问题就在这个kill(tcp->pid, SIGSTOP)上,当程序被处于跟踪的时刻,向已经
停止的进程发送SIGSTOP本来是没有必要的,不知道strace的作者为什么还要单独来上这
么一句?Linux 2.4和2.6内核又出现了差异,在2.6内核的do_sigaction判断了是否有信
号pending,而2.4却没有,因此在2.4的机器上,运行strace不会出现死循环的情况,在
2.6上,我们只须将改行注释掉即可。
我们可以认为这是strace和2.6内核不兼容的一个Bug!
注意:如果你在程序里用的是C库的signal,实际上使用的是sys_rt_sigaction而不是
sys_signal,而strace在处理sys_rt_sigaction时,并没有kill(tcp->pid, SIGSTOP);
也许strace的作者认为用C写的程序不会用到sys_signal?
接下来让我们试一试新的strace来跟踪burneye加密的程序
#./strace /tmp/ls.new (ls.new是burneye加密的程序)
execve("/tmp/ls.new", ["/tmp/ls.new"], [/* 20 vars */]) = 0
signal(SIGTRAP, 0x5371991) = 0 (SIG_DFL)
--- SIGSEGV (Segmentation fault) @ 0 (0) ---
+++ killed by SIGSEGV ++
OK,不死循环了,但是我们现在还无法继续跟踪,因为burneye加密的时候使用了某些anti-
strace的技术。
--[ 3 - anti-strace的原理
了解了strace的工作原理,就可以有针对的使用anti-strace的技术
--[ 3.1 方法一
利用上边介绍的strace与2.6内核的不兼容,造成死循环.对上边打过patch的strace不适用
测试程序(Grip2提供)
#include
#include
#include
#include
#include
#include
#include
void sig_handler(int sig)
{
printf("signal trap\n");
return;
}
static inline int my_signal(int num, void *func)
{
int ret;
__asm__ __volatile__ ( "int $0x80"
:"=a"(ret)
:"0" (48), "b" ((long)num),
"c" ((int)func));
return ret;
}
int main(int argc, char *argv[])
{
my_signal(SIGTRAP, sig_handler);
return 0;
}
注意一定不能使用C库的signal,原因在前边已经提过
--[ 3.2 方法二
自己发送int3
这种方法是Silvio Cesare在[3]中介绍的方法,由程序自己执行int3,这样会产生一个
陷阱,内核会向程序发送一个SIGTRAP信号,由于程序被跟踪,因此信号由strace截获,
根据strace的源代码,if (ptrace(PTRACE_SYSCALL, pid, (char *) 1, 0)
#include
#include
#include
#include
#include
#include
static int not_trace
void sig_handler(int sig)
{
not_trace++;
return;
}
int main(int argc, char *argv[])
{
signal(SIGTRAP, sig_handler);
__asm__ __volatile__ ( "int3" );
if(!not_trace){
printf("TRACING...\n");
exit(-1);
}
return 0;
}
--[ 3.3 方法三
程序使用kill向自己发送SIGTRAP,跟方法二类似,这里就不赘述了
kill(getpid(), SIGTRAP);
--[ 4 - anti anti-strace
现在我们来进一步完善strace,让它也能对付方法二和方法三,其实问题的关键就是看
strace在ptrace退出之后,下次使用ptrace能不能将SIGTRAP信号返回给被跟踪程序。
根据ptrace的手册页对PTRACE_CONT和PTRACE_SYSCALL的描述
PTRACE_CONT ... If data is non-zero and not SIGSTOP, it is interpreted as a
signal to be delivered to the child ...
PTRACE_SYSCALL ... Restarts the stopped child as for PTRACE_CONT
看来我们只须在下一次调用ptrace时指定返回的信号是SIGTRAP即可,不过不能胡乱发送,
只有在发现int3和kill(pid, SIGTRAP)的情况下才适用。
--[ 4.1 int3的情况
首先,我们需要判断int3的情况,因此,需要在strace.c::trace()最后添加以下几行
tracing:
if(ptrace(PTRACE_GETREGS, pid, NULL, (int)®s) scno != __NR_sigreturn && tcp->scno != __NR_rt_sigreturn)
--[ 4.2 kill的情况
kill情况比较简单,只须在sys_kill调用的第二次ptrace返回时,将返回值设定为SIGTRAP
if(tcp->scno == __NR_kill){
if(!(tcp->flags & TCB_INSYSCALL)){
tprintf("\n!! Self SIGTRAP !!\n");
if(ptrace(PTRACE_SYSCALL,
pid,
(char *)1,
SIGTRAP)
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/7487/showart_156581.html |
|