Snort Programming

 

Creating our own keywords with snort. We first went to the snort site www.snort.org and downloaded the latest snort version snort-2.3.3.tar.gz. We installed this file in C:\ and this created a zillion files. Water water everywhere, but no exe file to run. We haveno choice but to fire up our trusty Visual Studio 6.0 and compile snort. We first clicked on FIle, Open Workspace and we now have to file the project file which has a dsw extension. We first moved into the core snort-2.3.3 directory and then the src, win32, Win32-prj. Here we have the snort.dsw project file that we open. Before we build, we have a small chage to be made. Snort comes with data base support for Oracle, MySql and SqlServer. The only problem with oracle support is that we need a header file oci.h which we do not have as we have not installed the Oracle Call Interface. Thus we choose the Buiuld menu, set active configuration and choose the configuration SqlServer Debug. Then build by choosing Build, Build and wait for a long time for the compilation to take place. FOr some reason, it creates a sub directory snort___Win32_SqlServer_debug under C:\snort-2.3.3\src\win32\win32-prj. We get lots of warning also but at the end of the day snort.exe gets created.

 

We then create a conf file a.conf as

 

alert tcp any any -> any any (msg: "hi"; vijay: 12;)

 

This is the worst rule you can ever create but he have a keyword vijay which we want snort to understand. This keyword will check the ttl field. If it is 12, then the alert will get triggered. Thus we want to demonstrate how flexible snort is and how we are allowed to add on to the keywords of snort. This example also shows us how snort gives us access to the the packets it has. Also how we can trigger off an alert.

 

After snort.exe gets created and we try and run it, we get an error as twi dlls. pcre.dll and libnetnt.dll are not in the path. These dlls reside in c:\snort-2.3.3\src\win32\win32-prj. We copy them into teh directory snort.exe resides.

 

We then run snort as

 

snort -dev -l vijay -c a.conf

 

It comes back to us saying that we have an errror as the keyword vijay it does not reconize. Thus we need a way to first undersatnd that vijay is a new keyword. In Visual Studio we use the File, Open menu and open the file plugbase.c which resides in the C:\snort-2.3.3\src directory.On line 127 there is a function void InitPlugIns. There are a series of function calls and we need to add our own function here which we be called once. The line has our function function call mukhi. Thus each time snort starts it will call mukhi. It would be a good idea to call teh function Setup... but we like to tell you that you decide the name of the function.

 

We cannot build now as the compiler will compile fine but the linker will give us an error. We thus create a directory sonal from the root and create vmci.c. We use these names to tell you that names do not matter.

 

vmci.c

#include <stdio.h>

void abc(char *p)

{

FILE *fp = fopen("c:\\a.txt","a+");

fprintf(fp,"%s\n",p);

fclose(fp);

}

void mukhi(void)

{

abc("mukhi");

}

 

We create a simple function mukhi and then call our function abc. This function takes a string as a parameter and writes it to a file a.txt on C drive. Thus we will use the abc function to tell us what functions get called and when and with what. Thus if all goes well we snort will call the function mukhi. The only thing left is to add this file vmci.c to the existing snort project. In Visual Studio we click on Project, Add to Project and then Files. At the file dialog box we chose vmci.c from the  sonal directory. Build-Build will only compile plugbase.c and vmci.c. Now lets see what happens when we run snort.

 

When we run snort again we get the same error but the only saying grace is that the file a.txt contains mukhi. This is proof that snort called the mukhi function.

 

#include <stdio.h>

#include "plugbase.h"

void abc(char *p)

{

FILE *fp = fopen("c:\\a.txt","a+");

fprintf(fp,"%s\n",p);

fclose(fp);

}

void zzz(char *data, OptTreeNode *otn, int protocol)

{

char aa[100];

int i;

i = atoi(data);

sprintf(aa,"data=%s %d ",data,i);

abc(aa);

}

void mukhi(void)

{

abc("mukhi");

RegisterPlugin("vijay", zzz);

}

 

a.txt

mukhi

data= 12 12

 

When we run snort, we get no error at all this time. This is because the function RegisterPlugin takes two parameters, the first is the name of the keyword vijay and the second the name of a function. It then registers the keyword vijay for us. this is why we get no error this time around. The second parameter is the name of the function zzz that should be called each time we come across the keyword vijay. The first parameter is a pointer to a char of the data following vijay: in the conf file. In our case the space left after the : also gets send to us. We use the atoi function to convert it into a actual number. This is how we get the parameters we have been called with.

 

vmci.c

#include <stdio.h>

#include "plugbase.h"

void abc(char *p)

{

FILE *fp = fopen("c:\\a.txt","a+");

fprintf(fp,"%s\n",p);

fclose(fp);

}

static int yyy(Packet *p, struct _OptTreeNode *otn, OptFpList *fp_list)

{

char aa[100];

int ttl;

abc("yyy");

sprintf(aa,"IP id=%d len=%d",htons(p->iph->ip_id),htons(p->iph->ip_len));

abc(aa);

sprintf(aa,"Ports dp=%d %d sp=%d %d",p->dp,htons(p->tcph->th_dport),p->sp,htons(p->tcph->th_sport));

abc(aa);

sprintf(aa,"Seq=%d Ack=%d",htonl(p->tcph->th_seq),htonl(p->tcph->th_ack));

abc(aa);

ttl = *(int *)fp_list->context;

sprintf(aa,"Context %d %d",ttl,p->iph->ip_ttl);

abc(aa);

if ( ttl == p->iph->ip_ttl)

return fp_list->next->OptTestFunc(p,otn,fp_list->next);

else

return 0;

}

void zzz(char *data, OptTreeNode *otn, int protocol)

{

char aa[100];

OptFpList *ofl;

int *template_data;

int i;

i = atoi(data);

sprintf(aa,"data %s %d",data,i);

abc(aa);

template_data = SnortAlloc(4);

*template_data = i;

ofl = AddOptFuncToList(yyy,otn);

ofl->context = template_data;

}

void mukhi(void)

{

abc("mukhi");

RegisterPlugin("vijay", zzz);

}

 

a.txt

mukhi

data  12 12

yyy

IP id=65286 len=40

Ports dp=2 2 sp=1 1

Seq=3 Ack=4

Context 12 3

yyy

IP id=65286 len=40

Ports dp=2 2 sp=1 1

Seq=3 Ack=4

Context 12 12

 

alert.ids

[**] [1:0:0] hi [**]

[Priority: 0]

05/30-18:12:02.906260 0:0:E8:DF:60:57 -> 0:0:E8:D7:61:C4 type:0x800 len:0x36

196.254.34.0:1 -> 196.254.34.0:2 TCP TTL:12 TOS:0x0 ID:65286 IpLen:20 DgmLen:40

******S* Seq: 0x3  Ack: 0x4  Win: 0x1000  TcpLen: 20

 

nemesis

nemesis tcp -x 1 -y 2 -v -s 3 -a 4 -T 3

nemesis tcp -x 1 -y 2 -v -s 3 -a 4 -T 12

 

alert tcp any any -> any any (msg: "hi"; vijay: 12;)

 

In our alert we use the keyword vijay with a value 10. We would like an alert to be generated whenever a packet comes with a ttl of 12. Using nemesis we send two packets one with ttl 3 and the other with ttl 12 using the -T option. When we run snort, we see in alert.ids one alert for a packet with ttl 12. Thus we have been able to duplicate the functionality of the keyword ttl using vijay. The initialization function mukhi gets called only once and this we have seen is before sniffing can start. Here we are supposed to carry out one time initialization tasks. The most important thing we do here is infor snort that vijay is a valid keyword and whenever it sees this keyword in a rule option it should call the zzz function. This function gets called once per rule that it appears in. If we had two rules that contained the keyword vijay, this function would get called twice. The first use of this function is that the first parameter is a string that continaed the argument that we specified to the keyword. In our case as there is no > or < operation, a straight comparision is to be made, we can use the atoi function to convert the string to a number. We then display both the string and number. If we use the keyword vijay in different rules, we will have a problem knowing which value to check for. Thus we allocate an area of memory totaling 4 bytes as the parameter passed is a int. We use the function AnortAlloc to this this and then set the value stored here as 12 or the value we have been passed. Whenever we are now called with the packet data we can extract this value 12 as this memory location template_data will be passed to us. Thus we do not have to store teh parameters of every rule. In another situation, this memory could also store an entire structure if we are expecting some complex parameters. We then call another function AddOptFuncToList so that we canm receive the packet data and also tell snort to trigger off an alert. This function is passed the address of the function to be called, yyy and the otn linked list handle that we have been passed. The return value is of type OptFpList and we set the context member to the newly created memory that stores the parameter passed to us. Now for every packet function yyy gets called, and in our case it gets called twice. The first parameter is a pointer to a huge structure Packet that is present in decode.h. The packet structure has two main pointer to structures, one that carries ip data iph and the other tcp data tcph. The ip_id member carries the id field and the ip_len field the length of the total packet. These values are stored in big endian and hence we use htons to convert them into little endian. In the same vein the we first use the do member to get at the destination port but we could also use the th_dport member of the tcp structure. This membsr needs function htonl as it is a long. We also dispaly the sequence and ack number and then got tired and left the rest to you.  We now use the OptFpList member to extract the value of our parameter stored in the context member. We first cast it to an int and then re-reference. We then check the value stored in the ip_ttl memebr with this value, it it matches, then we need an alert to be sounded. We do this by calling the next function in the list using the OptTestFunc that is part of the next structure. We pass teh same three paarmeters that we are being called with. This does not really say teh alert passed, what we are saying is taht the alert passes from our point of view, call the next keyword and if that passes the alert, call teh next. If all pass, display the alert. If we fail, we return zero. If any one keyword, fails the test, no point checking with the others as all keywords make up a big and. This is how easy it is to extend snort.

 

Writing Preprocessors

 

a.conf

preprocessor mukhi: 32 hi

 

One of the things that make snort more intelligent is that it supports the concepts of preprocessors. We will explain how because of preprocessors snort knows that someone is conducting a port scan on our machine. One SYS on a port does not make a port scan, One SYS's send to different port being repeated makes a port scan. Does checking a single packet is not enough, we need a way to check multiple packets and then arrive at a conclusion. Also hackers may fragment packets. Thus if we check only a single fragment the content will never match. We neeed to get all the fragments together and then decide. Finally the smarter hackers do not gragment packets. they send the data as two different packets. We now need to join these packets. Thus we need to be smarter and the way to go is preprocessors. In our conf file we use the preprocessor keyword to activate a processor mukhi, but snort says that it does not know a preprocessor mukhi.

 

We go back to the plugbase.c file and add a function call mukhi1 in the function InitPreprocessors at line 395.

 

void InitPreprocessors()

{

mukhi1();

}

 

We then move into the sonal directory and create a file vmci1.c with the following lines.

 

vmci1.c

#include <stdio.h>

#include "plugbase.h"

void yyy (Packet *p)

{

abc("yyy");

}

static void zzz(u_char *args)

{

char aa[1000];

sprintf(aa,"zzz %s",args);

abc(aa);

AddFuncToPreprocList(yyy);

}

void mukhi1()

{

RegisterPreprocessor("mukhi", zzz);

abc("mukhi1");

}

 

a.txt

mukhi1

mukhi

zzz 32 hi

 

We then vmci1.c to the project by choosing menu Project, Add to Project and then files. We now move to sonal and this vmci1.c to the project. We then build again which builds both plugbase.c as well as vmci1.c. We get no error but we have to run snort with the log option, -l vijay. We see that mukhi getscalled and then in turn function zzz with 32 and hi as parameters. we then expected yyy to get called but it does not.

 

a.txt

mukhi1

mukhi

zzz 32 hi

yyy

yyy

yyy

yyy

yyy

yyy

 

We forgot to send any packets to snort and thus it did not call the yyy function at all. Now we send two packets and yyy gets called twice, one for the IP packet, twice for theARP request and reply. Thus for each packet we now get called.

 

vmci1.c

void yyy (Packet *p)

{

char aa[1000];

sprintf(aa,"yyy %x",p->iph);

abc(aa);

}

 

a.txt

mukhi1

mukhi

zzz 32 hi

yyy 0

yyy 0

yyy e700aa

yyy 0

yyy 0

yyy e700aa

 

Rules were easier to deal with as we knew the protocol we are dealing with. The problem with a preprocessor is that it gets called for arp, icmp, tcp everything. Thus when we have an arp protocol, the iph member is zero. This is a quick and dirtyway of knowing that wehave no IP packet. There are better ways, but that is what programming is all about.

 

void yyy (Packet *p)

{

char aa[1000];

sprintf(aa,"yyy %x",p->iph);

abc(aa);

if ( p->iph != 0)

{

sprintf(aa,"IP id=%d len=%d",htons(p->iph->ip_id),htons(p->iph->ip_len));

abc(aa);

sprintf(aa,"Ports dp=%d %d sp=%d %d",p->dp,htons(p->tcph->th_dport),p->sp,htons(p->tcph->th_sport));

abc(aa);

sprintf(aa,"Seq=%d Ack=%d",htonl(p->tcph->th_seq),htonl(p->tcph->th_ack));

abc(aa);

p->iph->ip_ttl = 25;

}

}

 

a.txt

mukhi1

mukhi

zzz 32 hi

yyy 0

yyy 0

yyy e700ea

IP id=65286 len=40

Ports dp=2 2 sp=1 1

Seq=3 Ack=4

yyy 0

yyy 0

yyy e700ea

IP id=65286 len=40

Ports dp=2 2 sp=1 1

Seq=3 Ack=4

 

We use the same code that we wrote earlier and print out the source and destination port numbers. We are not allowed to change any of the fields. We tries to change the IP ttl field but theoutput showed us the same.

 

vmci1.c

void yyy (Packet *p)

{

abc("yyy");

GenerateSnortEvent(p,1,2,3,4,5,"Vijay Mukhi");

}

 

alert.ids

[**] [1:2:3] Vijay Mukhi [**]

05/31-11:20:12.758092

 

[**] [1:2:3] Vijay Mukhi [**]

05/31-11:20:12.862605

 

[**] [1:2:3] Vijay Mukhi [**]

05/31-11:20:12.961677 0:0:E8:DF:60:57 -> FF:FF:FF:FF:FF:FF type:0x800 len:0x36

196.254.34.0:1 -> 196.254.34.0:2 TCP TTL:3 TOS:0x0 ID:65286 IpLen:20 DgmLen:40

******S* Seq: 0x3  Ack: 0x4  Win: 0x1000  TcpLen: 20

 

ARP

[**] Vijay Mukhi [**]

05/31-11:20:12.758092 ARP who-has 196.254.34.0 (FF:FF:FF:FF:FF:FF) tell 70.0.0.3

 

[**] Vijay Mukhi [**]

05/31-11:20:12.862605 ARP who-has 0.0.0.0 (FF:FF:FF:FF:FF:FF) tell 70.0.0.3

 

IP address

[**] Vijay Mukhi [**]

05/31-11:20:12.961677 0:0:E8:DF:60:57 -> FF:FF:FF:FF:FF:FF type:0x800 len:0x36

196.254.34.0:1 -> 196.254.34.0:2 TCP TTL:3 TOS:0x0 ID:65286 IpLen:20 DgmLen:40

******S* Seq: 0x3  Ack: 0x4  Win: 0x1000  TcpLen: 20

 

=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

 

Generating an alert is childs play in snort. Internally the system uses a function GenerateSnortAlert that will write out the alert for us.The first three values are the gen id, sig id and revision id. Then we have the classification, followed by the priority. Finally a message. There is also a function LogTagData taht simply logs only.