rayhunter icon indicating copy to clipboard operation
rayhunter copied to clipboard

check: add `--format` flag, json reporter

Open adrienthebo opened this issue 3 months ago • 4 comments

Pull Request Checklist

  • [x] 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.
  • [x] Added or updated any documentation as needed to support the changes in this PR.
  • [x] Code has been linted and run through cargo fmt
  • [ ] If any new functionality has been added, unit tests were also added
    • No tests are present in ./check but I can add them if needed
  • [x] ./CONTRIBUTING.md has been read

Problem

Rayhunter check output is emitted as plain text and doesn't lend itself to easy analysis by other tools.

Solution

Implement a --report flag, preserve the existing behavior as the default (manually invoked with --report log), and add an ndjson formatter.

Fixes #570.

Verification

Example output
❯ cargo run -- --path ../../rayhunter-traces/1749319445-goodsim.ndjson --report ndjson
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
     Running `/Users/adrienthebo/personal/projects/radio/rayhunter-work/rayhunter/target/debug/rayhunter-check --path ../../rayhunter-traces/1749319445-goodsim.ndjson --report ndjson`
INFO  [rayhunter_check] Analyzers:
INFO  [rayhunter_check]     - Identity (IMSI or IMEI) requested in suspicious manner (v3): Tests whether the ME sends an Identity Request NAS message without either an associated attach request or auth accept message
INFO  [rayhunter_check]     - Connection Release/Redirected Carrier 2G Downgrade (v1): Tests if a cell releases our connection and redirects us to a 2G cell.
INFO  [rayhunter_check]     - LTE SIB 6/7 Downgrade (v1): Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.
INFO  [rayhunter_check]     - Null Cipher (v1): Tests whether the cell suggests using a null cipher (EEA0)
INFO  [rayhunter_check]     - NAS Null Cipher Requested (v1): Tests whether the MME requests to use a null cipher in the NAS security mode command
INFO  [rayhunter_check]     - Incomplete SIB (v2): Tests whether a SIB1 message contains a full chain of followup sibs

1749319445-goodsim.ndjson

{"analyzers":[{"name":"Identity (IMSI or IMEI) requested in suspicious manner","description":"Tests whether the ME sends an Identity Request NAS message without either an associated attach request or auth accept message","version":3},{"name":"Connection Release/Redirected Carrier 2G Downgrade","description":"Tests if a cell releases our connection and redirects us to a 2G cell.","version":1},{"name":"LTE SIB 6/7 Downgrade","description":"Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.","version":1},{"name":"Null Cipher","description":"Tests whether the cell suggests using a null cipher (EEA0)","version":1},{"name":"NAS Null Cipher Requested","description":"Tests whether the MME requests to use a null cipher in the NAS security mode command","version":1},{"name":"Incomplete SIB","description":"Tests whether a SIB1 message contains a full chain of followup sibs","version":2}],"rayhunter":{"rayhunter_version":"0.7.1","system_os":"Darwin 24.6.0","arch":"arm64"},"report_version":2}
{"packet_timestamp":"2025-06-07T21:03:24.743Z","events":[{"event_type":"Informational","message":"Identity Request happened but its not suspicious yet. (packet 12761)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-07T21:35:41.969Z","events":[{"event_type":"Informational","message":"Identity Request happened but its not suspicious yet. (packet 15242)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-07T22:10:20.991Z","events":[{"event_type":"Informational","message":"Identity Request happened but its not suspicious yet. (packet 18097)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-07T22:10:23.290Z","events":[{"event_type":"Informational","message":"Identity Request happened but its not suspicious yet. (packet 18138)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T03:30:56.706Z","events":[{"event_type":"Informational","message":"SIB1 scheduling info list was malformed (packet 30097)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T03:30:57.067Z","events":[{"event_type":"Informational","message":"SIB1 scheduling info list was malformed (packet 30098)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T03:30:59.228Z","events":[{"event_type":"Informational","message":"SIB1 scheduling info list was malformed (packet 30102)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T08:46:57.462Z","events":[{"event_type":"High","message":"Identity requested after auth request (packet 30765)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T08:46:58.205Z","events":[{"event_type":"High","message":"Disconnected after Identity Request without Auth Accept (packet 30769)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T09:22:13.650Z","events":[{"event_type":"High","message":"Identity requested after auth request (packet 32055)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T09:22:14.535Z","events":[{"event_type":"High","message":"Disconnected after Identity Request without Auth Accept (packet 32059)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T12:20:30.716Z","events":[{"event_type":"High","message":"Identity requested after auth request (packet 35219)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T12:20:31.447Z","events":[{"event_type":"High","message":"Disconnected after Identity Request without Auth Accept (packet 35224)"}],"skipped_message_reason":null}
{"packet_timestamp":"2025-06-08T21:22:34.877Z","events":[{"event_type":"High","message":"Identity requested after auth request (packet 121675)"}],"skipped_message_reason":null}

Considerations

  • [x] ~~The use of a dyn Trait feels a bit clumsy~~
  • [x] Reconcile status logging
    • log macros now write to stderr
    • --report now writes a file so there's no issue of redirection writing extra logs.
  • [x] Deal with multiple files
    • as #662 indicates the --path flag can be either a file or directory so this may need to emit multiple json reports in one shot
    • We write one report per file

adrienthebo avatar Oct 19 '25 02:10 adrienthebo

I'm pretty slammed with work right now, will follow up on this within the next week.

adrienthebo avatar Oct 22 '25 20:10 adrienthebo

so far it looks good to me. I am not worried about the use of dyn, ok to keep as is. I would recommend moving all logging to stderr though (unconditionally)

untitaker avatar Oct 22 '25 20:10 untitaker

This is ready for review; I haven't written rust in a few years and I'm unattached to this implementation so I'm happy to steer this change wherever needed.

adrienthebo avatar Oct 27 '25 04:10 adrienthebo

are you still interested in working on this or should we close it?

cooperq avatar Dec 01 '25 18:12 cooperq