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.