AlienVault R&D Labs Portal. Get the latest news from our research.
Header

Tutorial 6: Plugin writing primer

March 11th, 2008 | Posted by DK in Tutorials - (Comments Off)

A couple of days ago I was fixing the fortinet/fortigate with the kind help of a Swiss OSSIM user (thanks Mikael ;-) ) and I wrote this little piece of python in order to help me out with it. Now I’m using it a lot to debug plugins so I guess more people could benefit from this also :-)


And well, I’ll paste a sample plugin debugging session in order to give ideas.

BTW, this assumes basic knowledge of regular expressions, check this Regexp Primer out if you want to refresh that knowledge. And BTW2, some log lines are broken for readability.



BREAK

#!/usr/bin/python 
import sys,re

if sys.argv[3] is None:
        print "Args are filename, regexp and [0|1]"

f = open(sys.argv[1], 'r')
data = f.readlines()
exp=sys.argv[2]

print sys.argv[2]

line_match = 0

matched = 0

for line in data:
	result = re.findall(exp,line)
	try:
		tmp = result[0]
	except IndexError:
		if sys.argv[3] is "1":
			print "Not matched:", line
		continue
	print result
	matched += 1


print "Counted", len(data), "lines."
print "Matched", matched, "lines."

Basically it will take a logfile as input, a regexp and wether to verbosely show the matched lines or not. That way you can start working towards more complex regexps and test it against a full logfile in realtime.

regexp.py logfile “regexp” (0|1) # 0 == do not show “non-matching lines”

Let’s go through a simple file containing logs (got it from http://www.ossec.net/wiki/index.php/PostgreSQL_Samples).

[2007-08-31 19:22:21.469 ADT] :[unknown] LOG:  connection received: host=192.168.2.99 port=52136
[2007-08-31 19:22:21.485 ADT] 192.168.2.99:ossecdb LOG:  connection authorized: user=ossec_user 
database=ossecdb
[2007-08-31 19:22:22.427 ADT] 192.168.2.99:ossecdb LOG:  disconnection: session time: 0:00:00.95 
user=ossec_user database=ossecdb host=192.168.2.99 port=52136
[2007-09-27 11:02:44.941 ADT] 192.168.2.10:ossecdb ERROR:  relation "lala" does not exist
[2007-09-27 11:02:46.444 ADT] 192.168.2.10:ossecdb LOG:  disconnection: session time: 0:00:35.79 
user=ossec_user database=ossecdb host=192.168.2.10 port=3584

Log messages:
[2007-09-01 07:14:41.062 ADT] : LOG:  autovacuum: processing database "template1"
[2007-09-01 07:15:41.079 ADT] : LOG:  autovacuum: processing database "ossecdb"

Query log:
[2007-09-01 16:44:49.244 ADT] 192.168.2.10:ossecdb LOG:  duration: 4.550 ms  statement: 
SELECT id FROM location WHERE name = 'enigma->/var/log/messages' AND server_id = '1'
[2007-09-01 16:44:49.251 ADT] 192.168.2.10:ossecdb LOG:  duration: 5.252 ms  statement: 
INSERT INTO location(server_id, name) VALUES ('1', 'enigma->/var/log/messages')
[2007-09-01 16:44:49.252 ADT] 192.168.2.10:ossecdb LOG:  duration: 0.016 ms  statement: 
SELECT id FROM location WHERE name = 'enigma->/var/log/messages' AND server_id = '1'

[2007-09-27 11:02:51.611 ADT] 192.168.2.10:ossecdb LOG:  statement: INSERT INTO 
alert(id,server_id,rule_id,timestamp,location_id,src_ip) 
VALUES ('3577', '1', '50503','1190916566', '140', '0')

Query error:
[2007-08-31 19:17:42.128 ADT] 192.168.2.99:test ERROR:  relation "alertaaa" does not exist
[2007-08-31 19:17:46.375 ADT] 192.168.2.99:test ERROR:  syntax error at or near "a" at character 1
[2007-09-27 11:02:44.941 ADT] 192.168.2.10:ossecdb ERROR:  relation "lala" does not exist

Authentication error:
[2007-09-01 19:08:49.862 ADT] : LOG:  connection received: host=192.168.2.99 port=37142
[2007-09-01 19:08:49.869 ADT] 192.168.2.99: FATAL:  password authentication failed for user "ossec_user"

The actual regexp debugging process

1. Start out simple

Gestalt:tmp dk$ regexp.py postgresql.log.txt "(^.*$)" 0 | tail -n 2
['[2007-09-01 19:08:49.869 ADT] 192.168.2.99: FATAL:  password authentication failed for user "ossec_user"', '', '']
Counted 25 lines.
Matched 25 lines.
Gestalt:tmp dk$ 

The tail after the command is there because by default it shows all the matching lines, and one is enough for our sample.

The used regexp is (^.*$) which as you know matches everything from the beginning to the end.

2. Basic separation

Gestalt:tmp dk$ regexp.py postgresql.log.txt "^\[(?P<date>\S+\s+\S+)\.\d+\s+ADT\]\s+(.*)$" 0
^\[(?P<date>\S+\s+\S+)\.\d+\s+ADT\]\s+(.*)$
[('2007-08-31 19:22:21', ':[unknown] LOG:  connection received: host=192.168.2.99 port=52136')]
[('2007-08-31 19:22:21', '192.168.2.99:ossecdb LOG:  connection authorized: 
user=ossec_user database=ossecdb')]
[('2007-08-31 19:22:22', '192.168.2.99:ossecdb LOG:  disconnection: session time: 0:00:00.95 
user=ossec_user database=ossecdb host=192.168.2.99 port=52136')]
[('2007-09-27 11:02:44', '192.168.2.10:ossecdb ERROR:  relation "lala" does not exist')]
[('2007-09-27 11:02:46', '192.168.2.10:ossecdb LOG:  disconnection: session time: 0:00:35.79 
user=ossec_user database=ossecdb host=192.168.2.10 port=3584')]
[('2007-09-01 07:14:41', ': LOG:  autovacuum: processing database "template1"')]
[('2007-09-01 07:15:41', ': LOG:  autovacuum: processing database "ossecdb"')]
[('2007-09-01 16:44:49', "192.168.2.10:ossecdb LOG:  duration: 4.550 ms  statement: 
SELECT id FROM location WHERE name = 'enigma->/var/log/messages' AND server_id = '1'")]
[('2007-09-01 16:44:49', "192.168.2.10:ossecdb LOG:  duration: 5.252 ms  statement: 
INSERT INTO location(server_id, name) VALUES ('1', 'enigma->/var/log/messages')")]
[('2007-09-01 16:44:49', "192.168.2.10:ossecdb LOG:  duration: 0.016 ms  statement: 
SELECT id FROM location WHERE name = 'enigma->/var/log/messages' AND server_id = '1'")]
[('2007-09-27 11:02:51', "192.168.2.10:ossecdb LOG:  statement: 
INSERT INTO alert(id,server_id,rule_id,timestamp,location_id,src_ip) 
VALUES ('3577', '1', '50503','1190916566', '140', '0')")]
[('2007-08-31 19:17:42', '192.168.2.99:test ERROR:  relation "alertaaa" does not exist')]
[('2007-08-31 19:17:46', '192.168.2.99:test ERROR:  syntax error at or near "a" at character 1')]
[('2007-09-27 11:02:44', '192.168.2.10:ossecdb ERROR:  relation "lala" does not exist')]
[('2007-09-01 19:08:49', ': LOG:  connection received: host=192.168.2.99 port=37142')]
[('2007-09-01 19:08:49', '192.168.2.99: FATAL:  password authentication failed for user "ossec_user"')]
Counted 25 lines.
Matched 16 lines.
Gestalt:tmp dk$ 

Here we make a first attempt at separating the original datetime from the rest of the line. Additionally we’ve filtered out the junk lines that might appear in the file, so now we only match 16 lines (the actual 16 lines that contain valid log files). Out regexp starts looking uglier now, but still understandable ;-): ^\[(?P<date>\S+\s+\S+)\.\d+\s+ADT\]\s+(.*)$

3. Extracting the database host and actual DB name

Gestalt:tmp dk$ regexp.py postgresql.log.txt 
"^\[(?P&lt;date>\S+\s+\S+)\.\d+\s+ADT\]\s+(?P&lt;dbhost>[^:]+)?:(?P&lt;dbname>\S+)?\s+(.*)$" 0 | tail -n 3
[('2007-09-01 19:08:49', '192.168.2.99', '', 'FATAL:  password authentication failed for user "ossec_user"')]
Counted 25 lines.
Matched 16 lines.

This time I’m facing the first problems. In order to get the output shown above I had to go through a bit of try and error.

Starting with a first try I see there are 5 lines not matching, since we know it’s 16 we need to match:

Gestalt:tmp dk$ regexp.py postgresql.log.txt "^\[(?P&lt;date>\S+\s+\S+)\.\d+\s+ADT\]\s+([^:]+):(\S+)\s+(.*)$" 0 | tail -n 2
Counted 25 lines.
Matched 11 lines.
Gestalt:tmp dk$ 

Let’s see which ones are not matching (notice the change from 0 to 1 at the end of the python’s ARGV):

Gestalt:tmp dk$ regexp.py postgresql.log.txt "^\[(?P&lt;date>\S+\s+\S+)\.\d+\s+ADT\]\s+([^:]+):(\S+)\s+(.*)$" 1 | grep "Not matched: \["
Not matched: [2007-08-31 19:22:21.469 ADT] :[unknown] LOG:  connection received: host=192.168.2.99 port=52136
Not matched: [2007-09-01 07:14:41.062 ADT] : LOG:  autovacuum: processing database "template1"
Not matched: [2007-09-01 07:15:41.079 ADT] : LOG:  autovacuum: processing database "ossecdb"
Not matched: [2007-09-01 19:08:49.862 ADT] : LOG:  connection received: host=192.168.2.99 port=37142
Not matched: [2007-09-01 19:08:49.869 ADT] 192.168.2.99: FATAL:  password authentication failed for user "ossec_user"
Gestalt:tmp dk$ 

Ok, understood, either DB host or DB name are optional, so let’s add that to our regexp, getting a final regexp of: ^\[(?P<date>\S+\s+\S+)\.\d+\s+ADT\]\s+(?P<dbhost>[^:]+)?:(?P<dbname>\S+)?\s+(.*)$

On the matching log line we can also see how the db host and name are being correctly extracted ;-).

4. Got a plugin_sid value

Gestalt:tmp dk$ regexp.py postgresql.log.txt 
"^\[(?P&lt;date>\S+\s+\S+)\.\d+\s+ADT\]\s+(?P&lt;dbhost>[^:]+)?:(?P&lt;dbname>\S+)?\s+(?P&lt;sid>[^:]+):\s+(?P&lt;log_msg>.*)$" 0 | tail -n 3
[('2007-09-01 19:08:49', '192.168.2.99', '', 'FATAL', 'password authentication failed for user "ossec_user"')]
Counted 25 lines.
Matched 16 lines.
Gestalt:tmp dk$ 

Looking at the remainder string it starts getting obvious that the next field is sort of a priority value. In this first phase the postfix plugin will remain somewhat crippled since I’m going to use that field as plugin sid, instead of identifying each of the log types by what they actually do. (That’s left as a task for the reader).


Our resulting regexp, extracting the priority and removing some white space in front of the logline would be: ^\[(?P<date>\S+\s+\S+)\.\d+\s+ADT\]\s+(?P<dbhost>[^:]+)?:(?P<dbname>\S+)?\s+(?P<sid>[^:]+):\s+(?P<log_msg>.*)$

Final Roundup

The plugin is far from being finished, but the goal of this tutorial was to demistify the regexp part a bit, since it’s actually only based on a bit of research about the log (possible values for fields), a bit of intuition and lots of try && error.

I hope that goal has been met.

To be continued… (finishing the regexp, writing the .cfg file and writing the .sql file)

DK

Mr Wolf Wannabe.

More Posts - Website

OSSIM Mobile now available ;-)

December 1st, 2007 | Posted by DK in Plugins - (Comments Off)

Well, kindof at least…
Since Apple’s iPhone is basically a stripped down MacosX and it has some nice toys to play with, I thought I’d give the provided python port a try and fire up the OSSIM agent. As expected everything worked like a charm and getting ossim up & running was very easy. Here is the rest of it.

Next thing was the logs. By default syslog isn’t logging on the device, so you have to enable it manually. A bit of googling did the job and I quickly were able to find how to do this:

  1. Copy /etc/syslogd.conf from any mac
  2. Break /System/Library/LaunchDaemons/apple.com.syslogd with random text so it doesn’t get loaded
  3. Restart the phone (just killing syslog should work too) and run /usr/sbin/syslogd -bsd_out 1 &

Voila, syslog up & running.

Now the fun part. Looking at what kind of events the iphone generated I thought maybe this little toy may deserve a plugin on it’s own. So after some poking around I came up with a small list of interesting events:

  • Dec 1 17:33:03 localhost /usr/sbin/mediaserverd: In H264 decode frame thread the first time
  • Dec 1 17:36:19 localhost YouTube[189]: clearing out queue
  • Dec 1 17:37:26 localhost crashdump[199]: Creating crash report for process vi[192]
  • Dec 1 17:40:25 localhost MobileSMS[219]: SummerBoardLoader: SummerBoardService available.
  • Dec 1 17:47:32 localhost MobileCal[235]: SummerBoardLoader: SummerBoardService available.
  • Dec 1 17:51:29 localhost SpringBoard[15]: Memory level is urgent (10), but there are no apps to warn!
  • Dec 1 17:54:14 localhost MobileMusicPlayer[50]: initializeMainUI, Role = 2 (MediaPlayer)
  • Dec 1 17:55:23 localhost Installer[51]: ATInstaller: Initializing…
  • Dec 1 17:55:29 localhost Installer[51]: ATPackageManager: Refreshing source: http://conceitedsoftware.com/iphone/
  • Dec 1 17:56:30 localhost Installer[51]: ATPackageManager: Perfoming operation “Install” on package “Tapp”…
  • Dec 1 18:25:02 localhost Installer[58]: ATPackageManager: Queued package “Tapp” for operation “Uninstall”.
  • Dec 1 17:56:30 localhost Installer[51]: ATUnpacker: Extracting folder: Tapp.app/ >> /Applications/Tapp.app
  • Dec 1 17:56:31 localhost Installer[51]: ATUnpacker: Extracting file: Tapp.app/TableApp >> /Applications/Tapp.app/TableApp
  • Dec 1 18:25:02 localhost Installer[58]: Executing script instruction: RemovePath with arguments (“/Applications/Tapp.app”)
  • Dec 1 17:58:47 localhost MobileBluetooth[12]: Session::attach “com.apple.mobilephone1014721381″

So, after a rainy afternoon I had my fully working iphone plugin.

See it in action on the following screens:

(image removed, broken link, I'm very sorry. DK.)

Could have some interesting big brother uses… and the good thing is, if the agent has no connection to the server it will queue up the events and send them the next time it can reach it.

And for the end, a quick proof of concept screenshot

2007-12-01 19:03:29,366 Conn [DEBUG]: event type="detector" date="2007-12-01 17:58:34" sensor="127.0.0.1"
interface="any" plugin_id="4006" plugin_sid="4" protocol="tcp" src_ip="127.0.0.1" userdata1="MobilePhone"
userdata2="55" log="Dec  1 17:58:34 localhost MobilePhone[55]: SummerBoardLoader: SummerBoardService
available."
^C2007-12-01 19:03:31,192 Agent [WARNING]: Kill signal received, exiting..
2007-12-01 19:03:31,200 Conn [INFO]: Closing server connection..
2007-12-01 19:03:31,210 Stats [INFO]:
-------------------------
 Agent execution summary:
  + Startup date: Sat Dec  1 19:03:10 2007
  + Shutdown date: Sat Dec  1 19:03:31 2007
  + Total events: 66 (Detector: 66, Monitor: 0)
    - plugin_id 4006: 66
  + Apps restarted by watchdog: 0
  + Server reconnection attempts: 0
-------------------------
2007-12-01 19:03:31,224 Stats [INFO]: Agent statistics written in /var/log/ossim/agent_stats.log
zsh: killed     ./ossim-agent -v
# uname -a
Darwin iPhone 9.0.0d1 Darwin Kernel Version 9.0.0d1: Wed Sep 19 00:08:43 PDT 2007;
root:xnu-933.0.0.203.obj~21/RELEASE_ARM_S5L8900XRB iPhone1,1 Darwin
#

DK

Mr Wolf Wannabe.

More Posts - Website

Plugin Tree && Graph installer update

November 26th, 2007 | Posted by DK in News - (Comments Off)

I thought I’d post a plugin tree I just hacked together here. It uses a javascript library and could be useful to someone.

I’m not posting the complete tree here since the page is about 1MB big.

As a little extra, below is some sample output from the graph package installer. Pablo’s almost done with it :-)




BREAK

Changing working dir to /tmp/swf_plugins.kUgtCe15592
Loading files... 
Template file: top_nets.inc
Source file: top_nets.php
Extracting files... 
etc/ossim/framework/panel/swf_plugin_templates/top_nets.inc
usr/share/ossim/www/graphs/top_nets.php
Checking PHP sintaxis (with lint [ see php -l ])
No syntax errors detected in etc/ossim/framework/panel/swf_plugin_templates/top_nets.inc
No syntax errors detected in usr/share/ossim/www/graphs/top_nets.php
PHP sintaxis Ok!
Checking template format: etc/ossim/framework/panel/swf_plugin_templates/top_nets.inc
Name Ok!
Homepage Ok!
Description Ok!
Category Ok!
Revision Ok!
Parameters Ok!
Detected pair of Parameters Ok!
The template format looks good.
*************************
Checking for older revision of Top C&A nets rev:1
Detected posible older revision in /etc/ossim/framework/panel/swf_plugin_templates/top_nets.inc
Local Name: Top C&A nets
The plugin Top C&A nets is already installed. Checking Revision Number
1 x 0
Upgrading Top C&A nets rev:0 to rev:1
Changing working directory to /
etc/ossim/framework/panel/swf_plugin_templates/top_nets.inc
usr/share/ossim/www/graphs/top_nets.php
top_nets.tgz Installed ok!

DK

Mr Wolf Wannabe.

More Posts - Website