# Sanity check - Grabbing platform tokens from browser user agents (was p0f)

**URL:** <https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982>\
**Category:** Zeek\
**Created:** [January 29, 2014, 11:35pm UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982 "2014-01-29T23:35:46Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Gary\_Faulkner](https://avatars.discourse-cdn.com/v4/letter/g/4bbf92/32.png) [@Gary\_Faulkner](https://community.zeek.org/u/Gary_Faulkner)\
**Post date:** [January 29, 2014, 11:35pm UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982/1 "2014-01-29T23:35:46Z")

</div>

After being asked if Bro could be used to gather passive intelligence on OS usage I started investigating places to try to identify OS. I initially was looking into p0f and Seth showed me a way to invoke the existing p0f fingerprinting functionality within Bro, but also suggested a slew of other data sources to look at. I wasn't terribly excited with the p0f fingerprint output, and while browser user agents may not be the best data source, I decided to start by looking at platform tokens and reporting on those instead of the p0f data. This is my first-ish bro script and it is by no means a complete script (it only matches a handful of Windows OS). I'm wondering if folks see anything in the attached that would misbehave badly if used on live traffic instead of pcaps?

Regards,

[browser-platform.bro](https://community.zeek.org/uploads/short-url/bkhkE07cLvLIfrTZUNdfQP5euvo.bro) (1.84 KB)

---

<div class="post-metadata">

**Author:** ![Gary\_Faulkner](https://avatars.discourse-cdn.com/v4/letter/g/4bbf92/32.png) [@Gary\_Faulkner](https://community.zeek.org/u/Gary_Faulkner)\
**Post date:** [January 31, 2014, 7:01pm UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982/2 "2014-01-31T19:01:04Z")

</div>

I've tried the below script now on a Bro 2.2 (release version) cluster watching about 8Gbps of traffic and it seems to do what I intended which is to create a separate log file that keeps track of IP to OS mappings, for a handful of Windows Desktop OS versions, as pulled from user agent strings sent over HTTP. In the first full day in production I successfully logged around 227,000 unique IP to OS mappings. I have since slightly modified it to only log IPs represented in local nets to reduce log volume as I'm mostly interested in my own networks.

One issue I'm running into is in keeping track of IP to OS mappings and only logging them once per day. I've set an expire timer for 1 day, but in production it seems to only keep track of those IPs for the duration of a log rotation interval which is set to 20 minutes. I have observed that without the expire timer each mapping will log continuously, so it appears to get used in some way, but just seems to be tied to log rotation instead of the explicit value in the script. I'm guessing I need to do something different, but not sure what. Thought? Script is pasted below.

================= Begin Script ==============  
@load base/utils/site

module BrowserPlatform;

export  
{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# The fully resolved name for this log will be BrowserPlatform::LOG  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redef enum Log::ID += { LOG };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type Info: record {  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ts: time &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uid: string &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;host: addr &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform\_token: string &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unparsed\_version: string &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# A set of seen IP + OS combinations. Used to prevent logging the same combo repeatedly.  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;global seen\_browser\_platforms: set[string] &create\_expire = 1.0 day &synchronized &redef;  
}

event bro\_init() &priority=5  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Log::create\_stream(BrowserPlatform::LOG,[$columns=Info]);  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

event http\_header(c: connection, is\_orig: bool, name: string, value: string)  
{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local platform = "Unknown OS";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( is\_orig && Site::is\_local\_addr(c$id$orig\_h) )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( name == "USER-AGENT" && /Windows NT 5.1/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows XP";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( name == "USER-AGENT" && /Windows NT 6.0/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows Vista";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( name == "USER-AGENT" && /Windows NT 6.1/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 7";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( name == "USER-AGENT" && /Windows NT 6.2/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 8";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( name == "USER-AGENT" && /Windows NT 6.3/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 8.1";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local saw = cat(c$id$orig\_h,platform); #There is probably a less ugly way to do this than cat, but it seems to work  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( platform != "Unknown OS" && saw !in seen\_browser\_platforms )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local rec: BrowserPlatform::Info = [$ts=network\_time(), $uid=c$uid, $host=c$id$orig\_h, $platform\_token=platform, $unparsed\_version=value];  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Log::write(BrowserPlatform::LOG, rec);  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add seen\_browser\_platforms[saw];  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
}

================ End script ==================

Thanks,

Gary Faulkner  
UW Madison  
Office of Campus Information Security

---

<div class="post-metadata">

**Author:** ![Azoff\_Justin](https://avatars.discourse-cdn.com/v4/letter/a/5fc32e/32.png) [@Azoff\_Justin](https://community.zeek.org/u/Azoff_Justin)\
**Post date:** [February 1, 2014, 12:12am UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982/3 "2014-02-01T00:12:42Z")

</div>

..

Modifying the http\_header event handler as follows will increase performance:

event http\_header(c: connection, is\_orig: bool, name: string, value: string)  
{  
&nbsp;&nbsp;&nbsp;&nbsp;if(!is\_orig || name != "USER-AGENT")  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return;  
&nbsp;&nbsp;&nbsp;&nbsp;if(/Windows NT 5.1/ in value)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows XP";  
&nbsp;&nbsp;&nbsp;&nbsp;else if ...

FWIW, I used to do this kind of thing outside of bro using splunk:

[https://github.com/JustinAzoff/splunk-scripts/blob/master/ua2os.py](https://github.com/JustinAzoff/splunk-scripts/blob/master/ua2os.py)

One thing you may want to do is rather than use the http\_header event  
use

event log\_software(rec: Info)  
{  
&nbsp;&nbsp;&nbsp;&nbsp;...  
}

which will be raised every time a new software version is seen. The  
software framework is already pulling most of the info out that you  
might need, so you can piggy back on the work that it is doing.

---

<div class="post-metadata">

**Author:** ![Gary\_Faulkner](https://avatars.discourse-cdn.com/v4/letter/g/4bbf92/32.png) [@Gary\_Faulkner](https://community.zeek.org/u/Gary_Faulkner)\
**Post date:** [February 1, 2014, 4:56am UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982/4 "2014-02-01T04:56:37Z")

</div>

Thanks for the suggestions, that cleans that bit up quite nicely. I actually started by trying to deconstruct the various software.bro scripts and work my way backwards through the framework to see what was doing what. I'm still trying to navigate my way through that code, but I agree that it would make more sense to leverage it directly than create a derivative just to pull out a specific bit of the data. I'm not currently running Splunk in any production sense, but that is pretty much what I'm trying to do in Bro. Thanks for sharing it!

Regards,  
Gary

---

<div class="post-metadata">

**Author:** ![Gary\_Faulkner](https://avatars.discourse-cdn.com/v4/letter/g/4bbf92/32.png) [@Gary\_Faulkner](https://community.zeek.org/u/Gary_Faulkner)\
**Post date:** [February 10, 2014, 5:50pm UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982/5 "2014-02-10T17:50:53Z")

</div>

After running various iterations of the original script against several pcaps of our local traffic (and a couple days of live traffic) I ended up finding a lot of user agents that would match against the desktop/server OS rules, but were not necessarily desktops or servers. I ended up adding to the matching rules in part to parse out these things and also to detect other things we were interested in. Checking for more things seems to incur a performance penalty, so I also made some effort to move some of the more common matches sooner in the if/else statements to avoid having to check all of the less likely items first. The create\_expire statement still doesn't behave as I expected, as each match is logged once per log rotation as opposed to once per day, but the matching seems to work with the exception that it doesn't check for every possible user agent case. I may also be missing explicitly including scripts that are already commonly loaded.

======================== Begin Script ========================  
@load base/utils/site

module BrowserPlatform;

export  
{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# The fully resolved name for this log will be BrowserPlatform::LOG  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redef enum Log::ID += { LOG };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type Info: record {  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ts: time &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uid: string &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;host: addr &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform\_token: string &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unparsed\_version: string &log &optional;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# A set of seen IP + OS combinations. Used to prevent logging the same combo repeatedly.  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;global seen\_browser\_platforms: set[string] &create\_expire = 1.0 day &synchronized &redef;  
}

event bro\_init() &priority=5  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Log::create\_stream(BrowserPlatform::LOG,[$columns=Info]);  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

event http\_header(c: connection, is\_orig: bool, name: string, value: string)  
{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local platform = "Unknown OS";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (!is\_orig || name != "USER-AGENT" || !Site::is\_local\_addr(c$id$orig\_h))  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return;

# Parse out Apple IOS and Android variants first as some apps will dispay as compatible with a desktop OS version

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( /iPhone/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "iPhone";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /iPad/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "iPad";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /iPod/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "iPod";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Android/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Android";

# Once we've parsed out mobiles move onto desktop/server OS  
# User agents listed in order of expected use or to pre-parse user-agents that might otherwise match multiple rules.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( /Xbox/ in value ) # often includes a Windows OS version or identifies as a Mobile browser  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Xbox";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Phone/ in value || /Mobile/ in value ) # often includes Windows OS version  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows Phone";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows NT 6.1/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 7";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows NT 5.1/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows XP";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows NT 5.2/ in value && /WOW64/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows XP x64";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows NT 6.0/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows Vista";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows NT 6.2/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 8";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows NT 6.3/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 8.1";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows 95/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 95";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows 98/ in value && /4.90/ !in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 98";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Win 9x 4.90/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows Me";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows NT 4.0/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows NT 4.0";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Windows NT 5.0/ in value || /Windows 2000/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Windows 2000";  
# Catch-all for identifying less common user-agents. Can be noisy.  
# else  
# platform = "Windows Other";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Mac OS X/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( /Mac OS X 10\_9/ in value || /Mac OS X 10.9/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Mac OS X 10.9";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Mac OS X 10\_8/ in value || /Mac OS X 10.8/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Mac OS X 10.8";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Mac OS X 10\_7/ in value || /Mac OS X 10.7/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Mac OS X 10.7";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Mac OS X 10\_6/ in value || /Mac OS X 10.6/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Mac OS X 10.6";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Mac OS X 10\_5/ in value || /Mac OS X 10.5/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Mac OS X 10.5";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Mac OS X 10\_4/ in value || /Mac OS X 10.4/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Mac OS X 10.4";  
# Catch-all for identifying less common user-agents. Can be noisy.  
# else  
# platform = "Mac OS X Other";  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if ( /Linux/ in value )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;platform = "Linux";

# Check to see if IP+OS combo already logged and if not log it and add it to the list of tracked combos.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local saw = cat(c$id$orig\_h,platform); #There is probably a less ugly way to do this than cat, but it seems to work  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ( platform != "Unknown OS" && saw !in seen\_browser\_platforms )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local rec: BrowserPlatform::Info = [$ts=network\_time(), $uid=c$uid, $host=c$id$orig\_h, $platform\_token=platform, $unparsed\_version=value];  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Log::write(BrowserPlatform::LOG, rec);  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add seen\_browser\_platforms[saw];  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
}

======================== End Script ========================

---

<div class="post-metadata">

**Author:** ![Ryan](https://avatars.discourse-cdn.com/v4/letter/r/a8b319/32.png) [@Ryan](https://community.zeek.org/u/Ryan)\
**Post date:** [April 2, 2014, 1:36pm UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982/6 "2014-04-02T13:36:04Z")

</div>

Gary,

This looks very nice. I’m curious if you had any more updates or improvements for this?

---

<div class="post-metadata">

**Author:** ![Gary\_Faulkner](https://avatars.discourse-cdn.com/v4/letter/g/4bbf92/32.png) [@Gary\_Faulkner](https://community.zeek.org/u/Gary_Faulkner)\
**Post date:** [April 2, 2014, 3:30pm UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982/7 "2014-04-02T15:30:59Z")

</div>

I haven't updated it from this point yet as I've been struggling with hooking into the existing software logging as well as having problems keeping track of state to prevent/reduce duplicate log entries. There were also some performance concerns raised so I've been hesitant to post any in progress work that might inadvertently cause someone else grief.

My observations after running the script continuously for the last month is that it probably needs the ability to exclude specific subnets. An example might be wireless networks that may have high client IP churn, short DHCP lease times, are more likely to have mobile devices with apps that have ugly user-agents, and just generally likely to provide unreliable data.

Regards,  
Gary

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex011/uploads/zeek/original/1X/f09d732bc2cc7c7cc7e35db67cf4e1d5233ce7a7.png) [@system](https://community.zeek.org/u/system)\
**Post date:** [May 6, 2022, 3:41pm UTC](https://community.zeek.org/t/sanity-check-grabbing-platform-tokens-from-browser-user-agents-was-p0f/2982/8 "2022-05-06T15:41:32Z")

</div>


