Skip to content

Wifi based heuristics - #1042

Open
jacklund wants to merge 27 commits into
EFForg:mainfrom
jacklund:wifi-based-heuristics
Open

jacklund wants to merge 27 commits into
EFForg:mainfrom
jacklund:wifi-based-heuristics

Conversation

@jacklund

Copy link
Copy Markdown
Contributor

First pass at a solution for issue #1000.

Some notes:

  • I've managed to shoehorn my code into the analysis flow, although whether it's in the best place I'll leave up to y'all. I've added a WifiOUIAnalyzer type, which implements Analyzer, and which gets added to the analyzer harness by default (and is configurable). I've also added the configured OUIs to the AnalyzerConfig which felt weird, since everything else on that are booleans, but there didn't seem to be a better place to put that and still have it available in the Harness code.

  • I've added a config line to specify the target OUIs as a comma-separated list of strings. I don't currently validate the OUIs (which should be done), because I'm not certain that's the best way to specify them. At some point there should probably be lists of well-known OUIs for specific types of target devices (similar to what Colonel Panic does in his OUI-spy apps), but until we have those lists, this might have to do.

  • I've also marked a OUI match as a HIGH event type, which might not be what is wanted here, it just seemed like a good place to start.

  • There's some code duplicated from DiagTask::process_container where I send the information to the notification channel and the display, which is not great, but I figured that some refactoring of the analysis flow to make this code fit better might make that go away anyway.

  • Also, I'm using my fork of wifi-station for this PR - I have a PR pending on that repo which will add the BSSID to the list of items returned when scanning. Hopefully when/if that PR is merged, we can put that back.

Pull Request Checklist

  • The Rayhunter team has recently expressed interest in reviewing a PR for this.
    • If not, this PR may be closed due our limited resources and need to prioritize how we spend them.
  • Added or updated any documentation as needed to support the changes in this PR.
  • Code has been linted and run through cargo fmt.
  • If any new functionality has been added, unit tests were also added.
  • CONTRIBUTING.md has been read.
  • Your pull request is fewer than ~400 lines of code.

You must check one of:

  • [ x No generative AI (including LLMs) tools were used to create this PR.
  • Generative AI was used to create this PR. I certify that I have read and understand the code, and that all comments and descriptions were authored by myself and are not the product of generative AI.

@cooperq
cooperq self-requested a review May 19, 2026 17:43
@cooperq cooperq self-assigned this May 19, 2026
@jacklund
jacklund force-pushed the wifi-based-heuristics branch from b981cc8 to 08f07b6 Compare May 23, 2026 21:22
@jacklund

Copy link
Copy Markdown
Contributor Author

Rebased against upstream main

@jacklund
jacklund force-pushed the wifi-based-heuristics branch 2 times, most recently from 4be8c5b to 4eabcd7 Compare May 31, 2026 15:50
@cooperq

cooperq commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

@jacklund sorry for the delay on reviewing this I'm having a look at it now

@cooperq

cooperq commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

I like the direction of your approach here. A few issues:

  1. alerts are reflected in the screen UI and the notifications but not in the web UI. We probably want to preserve the alerts and the OUI that triggered them. but we also want to update the web UI when we alert on an OUI. One way to do this would be to write it to the ndjson file but I think those currently get erased when we do a rescan. Another way would be to write it to its own separate wifi log which might be a better idea. It would be cool if we could write something to the pcap somehow from this but I will consider that a stretch goal for now.

  2. the list of mac addresses the wifi client sees is very limited right now, I'm not really sure whats up with this but I supsect its stuck on one channel and perhaps not channel hopping? I will have to look into this.

  3. Obviously this needs more documentation :) also the alert level for this should probably be low or informational for now as this is only useful in conjunction with other alerts.

@cooperq

cooperq commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

One of the main imsi catcher manufacturers is keyw corporation who has an unusual OUI of https://maclookup.app/macaddress/70b3d57cb 70:b3:d5:7c:b

It would be cool to be able to match against an oui like this that is >3 bytes but also the half byte at the end (crazy stuff I know)

Also we should have a sane default list of OUIs. I can provide this.

@cooperq cooperq left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please address the changes in my above comments.

@jacklund

jacklund commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@jacklund sorry for the delay on reviewing this I'm having a look at it now

No worries, I know y'all are slammed.

@jacklund

jacklund commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

One of the main imsi catcher manufacturers is keyw corporation who has an unusual OUI of https://maclookup.app/macaddress/70b3d57cb 70:b3:d5:7c:b

Wow, that's crazy.

It would be cool to be able to match against an oui like this that is >3 bytes but also the half byte at the end (crazy stuff I know)

Well, right now I do a startswith() (IIRC) for the OUI matching. I was considering doing some more stuff to restrict it to 3 bytes, but I guess leaving it for now makes sense for stuff like that.

Also we should have a sane default list of OUIs. I can provide this.

👍

@jacklund

jacklund commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

I like the direction of your approach here. A few issues:

1. alerts are reflected in the screen UI and the notifications but not in the web UI. We probably want to preserve the alerts and the OUI that triggered them. but we also want to update the web UI when we alert on an OUI. One way to do this would be to write it to the ndjson file but I think those currently get erased when we do a rescan. Another way would be to write it to its own separate wifi log which might be a better idea. It would be cool if we could write something to the pcap somehow from this but I will consider that a stretch goal for now.

Sounds good. I'm going to have to use a separate logger in that case, not a big deal, since y'all redirect the default logging from stdout to the rayhunter logs. I'll probably use whatever's simplest for now, although if you have a preference for a particular logger, I can use that. I'll also add something to be able to look at the wifi logs similar to what's done in the web UI for the rayhunter logs.

I think to be really useful, we'd need to include both the OUI and the manufacturer in the alert, which means we'd need to configure it with both (i.e. my simple config of a list of OUIs probably won't cut it). I'll probably change that to a list of manufacturer => list of OUIs, unless you think that's overkill.

2. the list of mac addresses the wifi client sees is very limited right now, I'm not really sure whats up with this but I supsect its stuck on one channel and perhaps not channel hopping? I will have to look into this.

Hadn't noticed that. I can look at that as well.

3. Obviously this needs more documentation :) also the alert level for this should probably be low or informational for now as this is only useful in conjunction with other alerts.

Yep, I figured I'd wait until we settled on a set of stable features before tackling documenting it, but I can probably start with some of what we've got now. I'll also change the alert level.

Thanks for the feedback!

@jacklund
jacklund force-pushed the wifi-based-heuristics branch from 391f1a6 to 282738b Compare June 21, 2026 16:45
@jacklund

Copy link
Copy Markdown
Contributor Author

@cooperq, per your change requests:

Another way would be to write it to its own separate wifi log which might be a better idea.

I've got the wifi analyzer writing to /data/rayhunter/wifi.log, using the same format the rayhunter logs use. I've added a button to the web UI to show the wifi logs as well. To do this, I also changed the /api/log API to /api/log/{name}, where {name} is the filename of the log, either rayhunter.log or wifi.log. I'm reusing the same Svelte widget to show both.

the list of mac addresses the wifi client sees is very limited right now

I can have a look at this as well, haven't really had the time, but should be able to now that the other stuff is somewhat done.

Obviously this needs more documentation

I've added a little blurb to heuristic.md about it, but happy to add elsewhere if you have something in mind.

It would be cool to be able to match against an oui like this that is >3 bytes but also the half byte at the end (crazy stuff I know)

The current implementation does that (purely accidentally I assure you :-) ), but I added a test for it as well

Also we should have a sane default list of OUIs. I can provide this.

That'd be cool, I can def add them when you have time to get this to me. Also, right now I'm just using the raw OUIs, not really having any association with the particular type of catcher. Was thinking that having an association of the particular OUIs with the catcher manufacturer would be cool, but was unsure how to do that in a reasonable way in the web UI (for the record, I'm definitely not particularly good at front-end design), so I was interested on your thoughts on the matter, when you have time.

@cooperq

cooperq commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Sorry please ignore this last comment I was being dumb.

@cooperq

cooperq commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Okay so this does trigger a ntfy message and an update to the screen UI, but doesn't trigger any update in the web UI or the downloaded logs. When other LTE heurisitcs are triggered they do four things:

  1. write that notification to the ndjson file like so: {"packet_timestamp":"1980-01-06T00:01:56.900Z","skipped_message_reason":null,"events":[{"event_type":"High","message":"Disconnected after Identity Request without Auth Accept (packet 37)"},null,null,null,null,null,null]}
  2. Update the screen UI
  3. send ntfy (optional)
  4. update the web UI

Currently this heuristic is only doing 2 and 3, luckily for us doing number 1 (updating the ndjson file) will take care of 2, 3, and 4 automatically, and in a much cleaner manner! I was trying to find in the code where this happens for regular heuristics and I'm still trying to find it but I think that's the refactor needed here.

But even when we do these 4 steps we will be faced with another problem: wifi OUIs won't show up on a reanalysis. There is not record of the wifi OUI ever having existed in the zip file once it is downloaded, writing to the ndjson file could solve this but we need to also include the ndjson file in the zip download (which is something I want to do in any case, see #1078). But this raises another problem which is that if the user hits the 'reanalyze' button the alert which is theoretically stored in the ndjson file will go away! This is because the analysis engine reads the qmdl file for its store of truth. Since we are not writing wifi information to the QMDL file and frankly can't, we can't make the solution work this way.

I think that the correct solution here is to log the triggering wifi packet in a pcap or txt or something alongside each recording instead of the monolithic wifi log. When the analyzer runs instead of checking the qmdl it should check the wifi log file and use that to write to the ndjson file. This means that the wifi log file for each run also needs to be included in the zip and webdav uploads. I think it would be awesome if the wifi log file was actually a pcap but I will accept even a json or text format for now.

tl;dr: write the wifi scans to a dedicated log file for each run, have the heuristic check against that log (instead of against the qmdl as the other heuristics do). If there is a match write the alert to the ndjson file and the rest of the notifications will happen automatically.

@cooperq

cooperq commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Also please come join us in the mattermost dev channel if you aren't already there, happy to think this through more. I think it's almost there!

@cooperq

cooperq commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Also we should have a sane default list of OUIs. I can provide this.

That'd be cool, I can def add them when you have time to get this to me. Also, right now I'm just using the raw OUIs, not really having any association with the particular type of catcher. Was thinking that having an association of the particular OUIs with the catcher manufacturer would be cool, but was unsure how to do that in a reasonable way in the web UI

After giving this some thought I think we should ship with a fixed list of OUIs instead of asking the user to add their own OUIs. In which case for readability we could ship the oui list as a list of tuples of (OUI, manufacturer) and then in the alert surface the manufacturer string.

If we wan't user customizable OUI alerts I think we could do that as a separate, off by default heuristic.

Also for testing I think it's fine to put this as a low severity heuristic.

@jacklund

jacklund commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

I think that the correct solution here is to log the triggering wifi packet in a pcap or txt or something alongside each recording instead of the monolithic wifi log. When the analyzer runs instead of checking the qmdl it should check the wifi log file and use that to write to the ndjson file. This means that the wifi log file for each run also needs to be included in the zip and webdav uploads. I think it would be awesome if the wifi log file was actually a pcap but I will accept even a json or text format for now.

What I've got right now isn't really pcap-ish - what I did is hook into the existing wifi-station implementation to include the BSSIDs along with what it's already reporting. What wifi-station's doing isn't very sophisticated, it's just doing iw dev wlan0 scan and parsing the text that comes with it.

That being said, it's possible we could use the pcap crate to do actual packet capturing of the wifi packets, assuming the devices support that sort of thing. I'd be happy to try that out, and then we really could include it in the pcap files. It's a bit of a side quest, so if you think it's not worth pursuing, I can stick with text files for now and write to the ndjson file, as you said.

After giving this some thought I think we should ship with a fixed list of OUIs instead of asking the user to add their own OUIs. In which case for readability we could ship the oui list as a list of tuples of (OUI, manufacturer) and then in the alert surface the manufacturer string.

If we wan't user customizable OUI alerts I think we could do that as a separate, off by default heuristic.

Also for testing I think it's fine to put this as a low severity heuristic.

Yeah, that'll be much easier to do in the web UI. I'll put that together ASAP.

Also please come join us in the mattermost dev channel if you aren't already there, happy to think this through more. I think it's almost there!

Will do!

@jacklund

Copy link
Copy Markdown
Contributor Author

What I've got right now isn't really pcap-ish - what I did is hook into the existing wifi-station implementation to include the BSSIDs along with what it's already reporting. What wifi-station's doing isn't very sophisticated, it's just doing iw dev wlan0 scan and parsing the text that comes with it.

That being said, it's possible we could use the pcap crate to do actual packet capturing of the wifi packets, assuming the devices support that sort of thing. I'd be happy to try that out, and then we really could include it in the pcap files. It's a bit of a side quest, so if you think it's not worth pursuing, I can stick with text files for now and write to the ndjson file, as you said.

Welp, the pcap crate doesn't seem feasible, in that I'd need to cross-compile libpcap and install it for that to work. Feh.

@cooperq

cooperq commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Lets skip PCAP for now then, I think ND JSON should be good enough!

@jacklund
jacklund force-pushed the wifi-based-heuristics branch from d408ce7 to 637c497 Compare August 19, 2026 00:27
holdTheDoorHoid pushed a commit to holdTheDoorHoid/rayhunter that referenced this pull request Sep 1, 2026
Users add their own alert rules from the web UI. These live in their own
type and their own file, as the maintainers proposed on EFForg#1042: editing your
own rules cannot corrupt the shipped signatures, and a signature-pack update
cannot drop a rule you added. Rule ids are namespaced under "user." so an
alert's origin is never ambiguous.

Rules arrive over HTTP from a browser, so everything here treats its input
as hostile:

- No expression language, no regex, nothing that can execute. The strongest
  construct is a glob with * and ?, matched by a two-pointer scan with no
  recursion and no backtracking blow-up. A test asserts the classic
  pathological pattern stays fast.
- Hard bounds on rule count, field lengths, criteria per rule, wildcard
  count and cooldown, because this runs on a 160 MB device.
- A pattern of only wildcards is rejected rather than matching everything.
- Errors name the field and quote the offending value, so the UI can show
  them next to what the user was editing.

Hostile text in a rule name is stored verbatim, not mangled. Escaping is the
presentation layer's job and a test records that expectation rather than
half-solving it here.

User prefix rules deliberately skip the locally-administered guard that
curated vendor rules use: someone naming a specific device may be naming one
that randomises, and they are asserting the address rather than inferring a
vendor from it.

Also adds allowlists so a user can silence their own equipment.

Generative AI (Claude) was used to write this code. It has not yet been
reviewed line by line by a human, and must be before any upstream proposal.

Co-Authored-By: Claude Fable 5 <[email protected]>
@cooperq

cooperq commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this! I'm about to leave town for a few days but I will review this when I'm back on September 10th. Meanwhile there are some conflicts it seems like so maybe we can fix those meanwhile?

@jacklund
jacklund force-pushed the wifi-based-heuristics branch from c8155a6 to 34c963d Compare September 3, 2026 02:57
@jacklund

jacklund commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for this! I'm about to leave town for a few days but I will review this when I'm back on September 10th. Meanwhile there are some conflicts it seems like so maybe we can fix those meanwhile?

No worries. I'm fixing the conflicts now.

Apologies for disappearing - it was a combination of other commitments, a much-needed vacation, and about a week of diagramming how the pieces of this code fit together to get a better idea of how to approach what you're asking for.

I think I have a good idea of how to proceed. The "right way" would be to refactor a lot of the QMDL and analysis code to integrate the WiFi code better, but I don't feel competent enough in the codebase to take that on. Instead, I'm going to settle for a parallel-but-similar flow for the WiFi stuff, which will require a good bit of code duplication, but will get us to where we have the files being written and the analysis done the way you wanted. If that works and is acceptable, refactoring could be a separate task.

Enjoy your time away!

EFForg#1000
Added ability to scan wifi networks (with a change to the wifi-station
library, which I will PR once I get this working). Trying to integrate
it into the analysis setup didn't work well, because that's pretty much
only set up for packet analysis, so I'm instead sending the change to
the display state directly from the wifi analysis function, which
probably isn't really what's needed.
I added a UI panel for the wifi logs, reusing the `LogView.svelte`
modal. This required changing the API endpoint for `/api/log` to take a
logfile parameter, and passing both it and a title to the modal.
cooperq mentioned possibility for having OUIs greater than 3 bytes, such
as `70:b3:d5:7c:b`. Added test to ensure that current analyzer covers that.
I'm going to replicate, somewhat, the structure and flow of the QMDL
logging and analysis, now that I have some idea of how that works. This
is liable to be a little janky - to do this properly, I'd need to
completely refactor the QMDL flow, which I don't have the time nor the
background in this codebase to accomplish. Instead, I'm going to
somewhat graft on a duplicate flow, hoping that at some point the two
can be refactored reasonably.
@jacklund
jacklund force-pushed the wifi-based-heuristics branch from 0a18fbc to f2a982a Compare September 17, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants