Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48e73a0a41 | ||
|
|
a36863e002 | ||
|
|
48aac0f0bb | ||
|
|
5749c305c6 | ||
|
|
f53688086d | ||
|
|
bd2e0b4394 | ||
|
|
1eea086199 | ||
|
|
d36c1f10cd | ||
|
|
8d8d2bd8ec | ||
|
|
f2b722ad5f | ||
|
|
5e2058e7ac | ||
|
|
60daf4b716 | ||
|
|
4df317b028 | ||
|
|
d7fb8b9c85 | ||
|
|
d399532494 | ||
|
|
45df91a364 | ||
|
|
672ed8c6c6 | ||
|
|
5c7c7cd766 | ||
|
|
f41a8d38fe | ||
|
|
f9c8c4671e | ||
|
|
723b20541e | ||
|
|
272a4aeabf | ||
|
|
6ae70556ba | ||
|
|
2915dea9e9 | ||
|
|
6941bc57b6 | ||
|
|
5b9dd856a8 | ||
|
|
5007cb0b36 | ||
|
|
1b244122df | ||
|
|
3c4cb56ce6 | ||
|
|
58843413b5 | ||
|
|
4ee504fed7 | ||
|
|
894af5da0d | ||
|
|
d810e8e3c0 | ||
|
|
8755d5694c | ||
|
|
70a7d81d05 | ||
|
|
c182543dfa | ||
|
|
056f4c02e5 | ||
|
|
237983a8cb | ||
|
|
9967f93af2 | ||
|
|
3358a06454 | ||
|
|
382702a9ee | ||
|
|
67c3eb7d91 | ||
|
|
98b05bfdb0 | ||
|
|
01d10b87b3 | ||
|
|
410e902848 | ||
|
|
f03f9fcdae | ||
|
|
4b68c30ed3 | ||
|
|
b5481331c2 | ||
|
|
ace65a8e55 | ||
|
|
920044a5b2 | ||
|
|
6cb9a195ed | ||
|
|
90e2bddbbb | ||
|
|
3fa583f671 | ||
|
|
8e6b86b26f | ||
|
|
d40d4fb9c1 | ||
|
|
a12bc4075e | ||
|
|
51327917b0 | ||
|
|
4982463b57 | ||
|
|
68aafd41e1 | ||
|
|
8b053a9ef8 | ||
|
|
ace325a38a | ||
|
|
6d02731a81 | ||
|
|
69b7fecb17 | ||
|
|
279169257d | ||
|
|
9a60e3f820 | ||
|
|
77e51ec2f6 | ||
|
|
c9c92706bc | ||
|
|
643fa9f979 | ||
|
|
96a02763e4 | ||
|
|
ff421de127 | ||
|
|
635c8a0188 | ||
|
|
5b8a0ef8d4 | ||
|
|
757b053a33 | ||
|
|
5d9bc27ac9 | ||
|
|
7d45be4f0c |
@@ -15,18 +15,27 @@ rustflags = ["-C", "target-feature=+crt-static"]
|
|||||||
linker = "rust-lld"
|
linker = "rust-lld"
|
||||||
rustflags = ["-C", "target-feature=+crt-static"]
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
[target.x86_64-apple-darwin]
|
# Disable rust-lld for x86 macOS because the linker crashers when compiling
|
||||||
linker = "rust-lld"
|
# the installer in release mode with debug info on.
|
||||||
rustflags = ["-C", "target-feature=+crt-static"]
|
# [target.x86_64-apple-darwin]
|
||||||
|
# linker = "rust-lld"
|
||||||
|
# rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
[target.x86_64-unknown-linux-musl]
|
[target.x86_64-unknown-linux-musl]
|
||||||
linker = "rust-lld"
|
linker = "rust-lld"
|
||||||
rustflags = ["-C", "target-feature=+crt-static"]
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
# optimizations to reduce the binary size
|
# keep line numbers in stack traces for non-firmware binaries
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
debug = "limited"
|
||||||
|
|
||||||
|
# optimizations to reduce the binary size of firmware binaries
|
||||||
|
[profile.firmware]
|
||||||
|
inherits = "release"
|
||||||
strip = true
|
strip = true
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
debug = false
|
||||||
|
|
||||||
|
|||||||
154
.github/workflows/build-release.yml
vendored
@@ -1,154 +0,0 @@
|
|||||||
name: Build Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, "release-*"]
|
|
||||||
pull_request:
|
|
||||||
branches: ["main"]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
FILE_ROOTSHELL: ../../rootshell/rootshell
|
|
||||||
FILE_RAYHUNTER_DAEMON_ORBIC: ../../rayhunter-daemon-orbic/rayhunter-daemon
|
|
||||||
FILE_RAYHUNTER_DAEMON_TPLINK: ../../rayhunter-daemon-tplink/rayhunter-daemon
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_rayhunter_check:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
platform:
|
|
||||||
- name: ubuntu-24
|
|
||||||
os: ubuntu-latest
|
|
||||||
target: x86_64-unknown-linux-musl
|
|
||||||
- name: ubuntu-24-aarch64
|
|
||||||
os: ubuntu-24.04-arm
|
|
||||||
target: aarch64-unknown-linux-musl
|
|
||||||
- name: macos-arm
|
|
||||||
os: macos-latest
|
|
||||||
target: aarch64-apple-darwin
|
|
||||||
- name: macos-intel
|
|
||||||
os: macos-13
|
|
||||||
target: x86_64-apple-darwin
|
|
||||||
- name: windows-x86_64
|
|
||||||
os: windows-latest
|
|
||||||
target: x86_64-pc-windows-gnu
|
|
||||||
runs-on: ${{ matrix.platform.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Build rayhunter-check
|
|
||||||
run: cargo build --bin rayhunter-check --release
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: rayhunter-check-${{ matrix.platform.name }}
|
|
||||||
path: target/release/rayhunter-check${{ matrix.platform.os == 'windows-latest' && '.exe' || '' }}
|
|
||||||
if-no-files-found: error
|
|
||||||
build_rootshell:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
targets: armv7-unknown-linux-musleabihf
|
|
||||||
- name: Build rootshell (arm32)
|
|
||||||
run: cargo build --bin rootshell --target armv7-unknown-linux-musleabihf --release
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: rootshell
|
|
||||||
path: target/armv7-unknown-linux-musleabihf/release/rootshell
|
|
||||||
if-no-files-found: error
|
|
||||||
build_rayhunter:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
device:
|
|
||||||
- name: tplink
|
|
||||||
- name: orbic
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
targets: armv7-unknown-linux-musleabihf
|
|
||||||
- name: Build rayhunter-daemon (arm32)
|
|
||||||
run: |
|
|
||||||
pushd bin/web
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
popd
|
|
||||||
cargo build --bin rayhunter-daemon --target armv7-unknown-linux-musleabihf --release --no-default-features --features ${{ matrix.device.name }}
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: rayhunter-daemon-${{ matrix.device.name }}
|
|
||||||
path: target/armv7-unknown-linux-musleabihf/release/rayhunter-daemon
|
|
||||||
if-no-files-found: error
|
|
||||||
build_rust_installer:
|
|
||||||
needs:
|
|
||||||
- build_rayhunter
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
platform:
|
|
||||||
- name: ubuntu-24
|
|
||||||
os: ubuntu-latest
|
|
||||||
target: x86_64-unknown-linux-musl
|
|
||||||
- name: ubuntu-24-aarch64
|
|
||||||
os: ubuntu-24.04-arm
|
|
||||||
target: aarch64-unknown-linux-musl
|
|
||||||
- name: macos-arm
|
|
||||||
os: macos-latest
|
|
||||||
target: aarch64-apple-darwin
|
|
||||||
- name: macos-intel
|
|
||||||
os: macos-13
|
|
||||||
target: x86_64-apple-darwin
|
|
||||||
- name: windows-x86_64
|
|
||||||
os: windows-latest
|
|
||||||
target: x86_64-pc-windows-gnu
|
|
||||||
runs-on: ${{ matrix.platform.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
targets: ${{ matrix.platform.target }}
|
|
||||||
- run: cargo build --bin installer --release --target ${{ matrix.platform.target }}
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: installer-${{ matrix.platform.name }}
|
|
||||||
path: target/${{ matrix.platform.target }}/release/installer${{ matrix.platform.os == 'windows-latest' && '.exe' || '' }}
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
build_release_zip:
|
|
||||||
needs:
|
|
||||||
- build_rayhunter_check
|
|
||||||
- build_rootshell
|
|
||||||
- build_rayhunter
|
|
||||||
- build_rust_installer
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
- name: Fix executable permissions on binaries
|
|
||||||
run: chmod +x installer-*/installer rayhunter-check-*/rayhunter-check rayhunter-daemon-*/rayhunter-daemon
|
|
||||||
- name: Get Rayhunter version
|
|
||||||
id: get_version
|
|
||||||
run: echo "VERSION=$(grep '^version' bin/Cargo.toml | head -n 1 | cut -d'"' -f2)" >> $GITHUB_ENV
|
|
||||||
- name: Setup versioned release directory
|
|
||||||
run: |
|
|
||||||
VERSIONED_DIR="rayhunter-v${{ env.VERSION }}"
|
|
||||||
mkdir "$VERSIONED_DIR"
|
|
||||||
mv rayhunter-daemon-* rootshell/rootshell installer-* "$VERSIONED_DIR"/
|
|
||||||
- name: Archive release directory as zip
|
|
||||||
run: |
|
|
||||||
VERSIONED_DIR="rayhunter-v${{ env.VERSION }}"
|
|
||||||
zip -r "$VERSIONED_DIR.zip" "$VERSIONED_DIR"
|
|
||||||
- name: Compute SHA256 of zip
|
|
||||||
run: |
|
|
||||||
VERSIONED_DIR="rayhunter-v${{ env.VERSION }}"
|
|
||||||
sha256sum "$VERSIONED_DIR.zip" > "$VERSIONED_DIR.zip.sha256"
|
|
||||||
# TODO: have this create a release directly
|
|
||||||
- name: Upload zip release and sha256
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: rayhunter-v${{ env.VERSION }}
|
|
||||||
path: |
|
|
||||||
rayhunter-v${{ env.VERSION }}.zip
|
|
||||||
rayhunter-v${{ env.VERSION }}.zip.sha256
|
|
||||||
if-no-files-found: error
|
|
||||||
54
.github/workflows/check-and-test.yml
vendored
@@ -1,54 +0,0 @@
|
|||||||
name: Check and Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
NO_FIRMWARE_BIN: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check_and_test:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
device:
|
|
||||||
- name: tplink
|
|
||||||
- name: orbic
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Check
|
|
||||||
run: |
|
|
||||||
pushd bin/web
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
popd
|
|
||||||
cargo check --verbose --no-default-features --features=${{ matrix.device.name }}
|
|
||||||
- name: Run tests
|
|
||||||
run: |
|
|
||||||
pushd bin/web
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
popd
|
|
||||||
cargo test --verbose --no-default-features --features=${{ matrix.device.name }}
|
|
||||||
- name: Run clippy
|
|
||||||
run: cargo clippy --verbose --no-default-features --features=${{ matrix.device.name }}
|
|
||||||
|
|
||||||
windows_installer_check_and_test:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: cargo check
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd installer
|
|
||||||
cargo check --verbose
|
|
||||||
- name: cargo test
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd installer
|
|
||||||
cargo test --verbose --no-default-features --features=${{ matrix.device.name }}
|
|
||||||
319
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
name: main
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
FILE_ROOTSHELL: ../../rootshell/rootshell
|
||||||
|
FILE_RAYHUNTER_DAEMON_ORBIC: ../../rayhunter-daemon-orbic/rayhunter-daemon
|
||||||
|
FILE_RAYHUNTER_DAEMON_TPLINK: ../../rayhunter-daemon-tplink/rayhunter-daemon
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
files_changed:
|
||||||
|
name: Detect file changes
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
outputs:
|
||||||
|
code_changed: ${{ steps.files_changed.outputs.code_count }}
|
||||||
|
daemon_changed: ${{ steps.files_changed.outputs.daemon_count }}
|
||||||
|
docs_changed: ${{ steps.files_changed.outputs.docs_count }}
|
||||||
|
installer_changed: ${{ steps.files_changed.outputs.installer_count }}
|
||||||
|
rootshell_changed: ${{ steps.files_changed.outputs.rootshell_count }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: detect file changes
|
||||||
|
id: files_changed
|
||||||
|
run: |
|
||||||
|
lcommit=${{ github.event.pull_request.base.sha || 'origin/main' }}
|
||||||
|
if [ ${{ github.ref }} = 'refs/heads/main' ]
|
||||||
|
then
|
||||||
|
echo "building everything"
|
||||||
|
echo code_count=forced >> "$GITHUB_OUTPUT"
|
||||||
|
echo daemon_count=forced >> "$GITHUB_OUTPUT"
|
||||||
|
echo docs_count=forced >> "$GITHUB_OUTPUT"
|
||||||
|
echo installer_count=forced >> "$GITHUB_OUTPUT"
|
||||||
|
echo rootshell_count=forced >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "code_count=$(git diff --name-only $lcommit...HEAD | grep -e ^bin -e ^installer -e ^lib -e ^rootshell -e ^telcom-parser | wc -l)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "daemon_count=$(git diff --name-only $lcommit...HEAD | grep -e ^bin -e ^lib -e ^telcom-parser | wc -l)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "docs_count=$(git diff --name-only $lcommit...HEAD | grep -e ^book.toml -e ^doc | wc -l)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "installer_count=$(git diff --name-only $lcommit...HEAD | grep -e ^installer | wc -l)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "rootshell_count=$(git diff --name-only $lcommit...HEAD | grep -e ^rootshell | wc -l)" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mdbook_test:
|
||||||
|
name: Test mdBook Documentation builds
|
||||||
|
needs: files_changed
|
||||||
|
if: needs.files_changed.outputs.docs_changed != '0'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install mdBook
|
||||||
|
run: |
|
||||||
|
cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
|
||||||
|
- name: Test mdBook
|
||||||
|
run: mdbook test
|
||||||
|
|
||||||
|
mdbook_publish:
|
||||||
|
name: Publish mdBook to Github Pages
|
||||||
|
needs: mdbook_test
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
permissions:
|
||||||
|
pages: write
|
||||||
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install mdBook
|
||||||
|
run: |
|
||||||
|
cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
|
||||||
|
|
||||||
|
- name: Build mdBook
|
||||||
|
run: mdbook build
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: book
|
||||||
|
- name: Deploy to Github Pages
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
|
|
||||||
|
check_and_test:
|
||||||
|
needs: files_changed
|
||||||
|
if: needs.files_changed.outputs.code_changed != '0'
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
device:
|
||||||
|
- name: tplink
|
||||||
|
- name: orbic
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Check formatting
|
||||||
|
run: cargo fmt --all --check
|
||||||
|
- name: Check
|
||||||
|
run: |
|
||||||
|
pushd bin/web
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
popd
|
||||||
|
NO_FIRMWARE_BIN=true cargo check --verbose --no-default-features --features=${{ matrix.device.name }}
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
NO_FIRMWARE_BIN=true cargo test --verbose --no-default-features --features=${{ matrix.device.name }}
|
||||||
|
- name: Run clippy
|
||||||
|
run: |
|
||||||
|
NO_FIRMWARE_BIN=true cargo clippy --verbose --no-default-features --features=${{ matrix.device.name }}
|
||||||
|
|
||||||
|
windows_installer_check_and_test:
|
||||||
|
needs: files_changed
|
||||||
|
if: needs.files_changed.outputs.installer_changed != '0'
|
||||||
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: cargo check
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cd installer
|
||||||
|
NO_FIRMWARE_BIN=true cargo check --verbose
|
||||||
|
- name: cargo test
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cd installer
|
||||||
|
NO_FIRMWARE_BIN=true cargo test --verbose --no-default-features
|
||||||
|
|
||||||
|
build_rayhunter_check:
|
||||||
|
if: needs.files_changed.outputs.daemon_changed != '0'
|
||||||
|
needs:
|
||||||
|
- check_and_test
|
||||||
|
- files_changed
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- name: ubuntu-24
|
||||||
|
os: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
- name: ubuntu-24-aarch64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
- name: macos-arm
|
||||||
|
os: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
- name: macos-intel
|
||||||
|
os: macos-13
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
- name: windows-x86_64
|
||||||
|
os: windows-latest
|
||||||
|
target: x86_64-pc-windows-gnu
|
||||||
|
runs-on: ${{ matrix.platform.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build rayhunter-check
|
||||||
|
run: cargo build --bin rayhunter-check --release
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: rayhunter-check-${{ matrix.platform.name }}
|
||||||
|
path: target/release/rayhunter-check${{ matrix.platform.os == 'windows-latest' && '.exe' || '' }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build_rootshell:
|
||||||
|
if: needs.files_changed.outputs.rootshell_changed != '0'
|
||||||
|
needs:
|
||||||
|
- check_and_test
|
||||||
|
- files_changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: armv7-unknown-linux-musleabihf
|
||||||
|
- name: Build rootshell (arm32)
|
||||||
|
run: cargo build --bin rootshell --target armv7-unknown-linux-musleabihf --profile=firmware
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: rootshell
|
||||||
|
path: target/armv7-unknown-linux-musleabihf/firmware/rootshell
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build_rayhunter:
|
||||||
|
if: needs.files_changed.outputs.daemon_changed != '0'
|
||||||
|
needs:
|
||||||
|
- check_and_test
|
||||||
|
- files_changed
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
device:
|
||||||
|
- name: tplink
|
||||||
|
- name: orbic
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: armv7-unknown-linux-musleabihf
|
||||||
|
- name: Build rayhunter-daemon (arm32)
|
||||||
|
run: |
|
||||||
|
pushd bin/web
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
popd
|
||||||
|
# Run with -p so that cargo will select the minimum feature set for this package.
|
||||||
|
#
|
||||||
|
# Otherwise, it will consider the union of all requested features
|
||||||
|
# from all packages in the workspace. For example, if installer
|
||||||
|
# requires tokio with "full" feature, it will be included no matter
|
||||||
|
# what the feature selection in rayhunter-daemon is.
|
||||||
|
#
|
||||||
|
# https://github.com/rust-lang/cargo/issues/4463
|
||||||
|
cargo build -p rayhunter-daemon --bin rayhunter-daemon --target armv7-unknown-linux-musleabihf --profile=firmware --no-default-features --features ${{ matrix.device.name }}
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: rayhunter-daemon-${{ matrix.device.name }}
|
||||||
|
path: target/armv7-unknown-linux-musleabihf/firmware/rayhunter-daemon
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build_rust_installer:
|
||||||
|
if: needs.files_changed.outputs.installer_changed != '0'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs:
|
||||||
|
- build_rayhunter
|
||||||
|
- build_rootshell
|
||||||
|
- files_changed
|
||||||
|
- windows_installer_check_and_test
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- name: ubuntu-24
|
||||||
|
os: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
- name: ubuntu-24-aarch64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
- name: macos-arm
|
||||||
|
os: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
- name: macos-intel
|
||||||
|
os: macos-13
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
- name: windows-x86_64
|
||||||
|
os: windows-latest
|
||||||
|
target: x86_64-pc-windows-gnu
|
||||||
|
runs-on: ${{ matrix.platform.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.platform.target }}
|
||||||
|
- run: cargo build --bin installer --release --target ${{ matrix.platform.target }}
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: installer-${{ matrix.platform.name }}
|
||||||
|
path: target/${{ matrix.platform.target }}/release/installer${{ matrix.platform.os == 'windows-latest' && '.exe' || '' }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build_release_zip:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs:
|
||||||
|
- build_rayhunter_check
|
||||||
|
- build_rootshell
|
||||||
|
- build_rayhunter
|
||||||
|
- build_rust_installer
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
- name: Fix executable permissions on binaries
|
||||||
|
run: chmod +x installer-*/installer rayhunter-check-*/rayhunter-check rayhunter-daemon-*/rayhunter-daemon
|
||||||
|
- name: Get Rayhunter version
|
||||||
|
id: get_version
|
||||||
|
run: echo "VERSION=$(grep '^version' bin/Cargo.toml | head -n 1 | cut -d'"' -f2)" >> $GITHUB_ENV
|
||||||
|
- name: Setup versioned release directory
|
||||||
|
run: |
|
||||||
|
VERSIONED_DIR="rayhunter-v${{ env.VERSION }}"
|
||||||
|
mkdir "$VERSIONED_DIR"
|
||||||
|
mv rayhunter-daemon-* rootshell/rootshell installer-* dist/* installer/install.ps1 "$VERSIONED_DIR"/
|
||||||
|
- name: Archive release directory as zip
|
||||||
|
run: |
|
||||||
|
VERSIONED_DIR="rayhunter-v${{ env.VERSION }}"
|
||||||
|
zip -r "$VERSIONED_DIR.zip" "$VERSIONED_DIR"
|
||||||
|
- name: Compute SHA256 of zip
|
||||||
|
run: |
|
||||||
|
VERSIONED_DIR="rayhunter-v${{ env.VERSION }}"
|
||||||
|
sha256sum "$VERSIONED_DIR.zip" > "$VERSIONED_DIR.zip.sha256"
|
||||||
|
# TODO: have this create a release directly
|
||||||
|
- name: Upload zip release and sha256
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: rayhunter-v${{ env.VERSION }}
|
||||||
|
path: |
|
||||||
|
rayhunter-v${{ env.VERSION }}.zip
|
||||||
|
rayhunter-v${{ env.VERSION }}.zip.sha256
|
||||||
|
if-no-files-found: error
|
||||||
47
.github/workflows/mdbook.yaml
vendored
@@ -1,47 +0,0 @@
|
|||||||
# On Repository Settings > Pages > Build and deployment
|
|
||||||
# Set "Source" to GitHub Actions.
|
|
||||||
name: Documentation
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
pull_request:
|
|
||||||
branches: ["main"]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
mdbook_test:
|
|
||||||
name: Test mdBook Documentation builds
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Install mdBook
|
|
||||||
run: |
|
|
||||||
cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
|
|
||||||
- name: Test mdBook
|
|
||||||
run: mdbook test
|
|
||||||
|
|
||||||
mdbook_publish:
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
needs: mdbook_test
|
|
||||||
permissions:
|
|
||||||
pages: write
|
|
||||||
contents: write
|
|
||||||
id-token: write
|
|
||||||
name: Publish mdBook to Github Pages
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Install mdBook
|
|
||||||
run: |
|
|
||||||
cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
|
|
||||||
|
|
||||||
- name: Build mdBook
|
|
||||||
run: mdbook build
|
|
||||||
|
|
||||||
- name: Setup Pages
|
|
||||||
uses: actions/configure-pages@v4
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
path: book
|
|
||||||
- name: Deploy to Github Pages
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
48
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# To use: navigate on Github to Actions, select "Release rayhunter" on the left, click "Run workflow" > "Run workflow" on the right.
|
||||||
|
# https://github.com/EFForg/rayhunter/actions/workflows/release.yml
|
||||||
|
name: Release rayhunter
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_version_same:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Ensure all Cargo.toml files have the same version defined.
|
||||||
|
run: |
|
||||||
|
defined_versions=$(find lib bin installer rootshell telcom-parser -name Cargo.toml -exec grep ^version {} \; | sort -u | wc -l)
|
||||||
|
find lib bin installer rootshell telcom-parser -name Cargo.toml -exec grep ^version {} \;
|
||||||
|
echo number of defined versions = $defined_versions
|
||||||
|
if [ $defined_versions != "1" ]
|
||||||
|
then
|
||||||
|
echo "all Cargo.toml files must have the same version defined"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
main:
|
||||||
|
needs: check_version_same
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
packages: write
|
||||||
|
pages: write
|
||||||
|
uses: ./.github/workflows/main.yml
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: main
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
- name: Create release
|
||||||
|
run: |
|
||||||
|
version=$(grep ^version lib/Cargo.toml | cut -d' ' -f3 | tr -d '"')
|
||||||
|
gh release create --generate-notes -t "Rayhunter v$version" "v$version" rayhunter-v${version}/rayhunter-*
|
||||||
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
/book
|
/book
|
||||||
|
.DS_Store
|
||||||
|
|||||||
97
Cargo.lock
generated
@@ -5,7 +5,7 @@ version = 4
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "adb_client"
|
name = "adb_client"
|
||||||
version = "2.1.11"
|
version = "2.1.11"
|
||||||
source = "git+https://github.com/gaykitty/adb_client.git?rev=1fb0f4f5cbcc95bbbb98db4ee2f1e53a1005aa81#1fb0f4f5cbcc95bbbb98db4ee2f1e53a1005aa81"
|
source = "git+https://github.com/EFForg/adb_client.git?rev=e511662394e4fa32865c154c40f81a3d846f700c#e511662394e4fa32865c154c40f81a3d846f700c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-io",
|
"async-io",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -175,7 +175,7 @@ checksum = "b1d056f38f3e98ffe9808e6a482d1527e4ded46e72b3dbe77305f5508e76c5f6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger 0.10.2",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
@@ -791,6 +791,16 @@ version = "1.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@@ -804,6 +814,19 @@ dependencies = [
|
|||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"jiff",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -921,7 +944,6 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-executor",
|
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@@ -944,17 +966,6 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-executor"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@@ -1003,13 +1014,10 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
@@ -1425,13 +1433,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "installer"
|
name = "installer"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adb_client",
|
"adb_client",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
"bytes",
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
|
"env_logger 0.11.8",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"md5",
|
"md5",
|
||||||
@@ -1503,6 +1512,30 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.101",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.33"
|
version = "0.1.33"
|
||||||
@@ -2051,6 +2084,21 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -2259,15 +2307,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayhunter"
|
name = "rayhunter"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"deku",
|
"deku",
|
||||||
"env_logger",
|
|
||||||
"futures",
|
"futures",
|
||||||
"futures-core",
|
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"nix",
|
"nix",
|
||||||
@@ -2280,14 +2326,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayhunter-daemon"
|
name = "rayhunter-daemon"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger 0.11.8",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-core",
|
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
"image",
|
"image",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
@@ -2408,7 +2453,7 @@ checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rootshell"
|
name = "rootshell"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix",
|
"nix",
|
||||||
]
|
]
|
||||||
@@ -2781,7 +2826,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "telcom-parser"
|
name = "telcom-parser"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"asn1-codecs",
|
"asn1-codecs",
|
||||||
"asn1-compiler",
|
"asn1-compiler",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rayhunter-daemon"
|
name = "rayhunter-daemon"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@@ -22,20 +22,19 @@ path = "src/check.rs"
|
|||||||
rayhunter = { path = "../lib" }
|
rayhunter = { path = "../lib" }
|
||||||
toml = "0.8.8"
|
toml = "0.8.8"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
tokio = { version = "1.44.2", features = ["full"] }
|
tokio = { version = "1.44.2", default-features = false, features = ["fs", "signal", "process", "rt-multi-thread"] }
|
||||||
axum = "0.8"
|
axum = { version = "0.8", default-features = false, features = ["http1", "tokio", "json"] }
|
||||||
futures-core = "0.3.30"
|
|
||||||
thiserror = "1.0.52"
|
thiserror = "1.0.52"
|
||||||
libc = "0.2.150"
|
libc = "0.2.150"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
env_logger = "0.10.1"
|
env_logger = { version = "0.11", default-features = false }
|
||||||
tokio-util = { version = "0.7.10", features = ["rt", "io"] }
|
tokio-util = { version = "0.7.10", features = ["rt", "io"] }
|
||||||
futures-macro = "0.3.30"
|
futures-macro = "0.3.30"
|
||||||
include_dir = "0.7.3"
|
include_dir = "0.7.3"
|
||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
tokio-stream = "0.1.14"
|
tokio-stream = { version = "0.1.14", default-features = false }
|
||||||
futures = "0.3.30"
|
futures = { version = "0.3.30", default-features = false }
|
||||||
clap = { version = "4.5.2", features = ["derive"] }
|
clap = { version = "4.5.2", features = ["derive"] }
|
||||||
serde_json = "1.0.114"
|
serde_json = "1.0.114"
|
||||||
image = { version = "0.25.1", default-features = false, features = ["png", "gif"] }
|
image = { version = "0.25.1", default-features = false, features = ["png", "gif"] }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub struct Config {
|
|||||||
pub ui_level: u8,
|
pub ui_level: u8,
|
||||||
pub enable_dummy_analyzer: bool,
|
pub enable_dummy_analyzer: bool,
|
||||||
pub colorblind_mode: bool,
|
pub colorblind_mode: bool,
|
||||||
|
pub key_input_mode: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
@@ -22,6 +23,7 @@ impl Default for Config {
|
|||||||
ui_level: 1,
|
ui_level: 1,
|
||||||
enable_dummy_analyzer: false,
|
enable_dummy_analyzer: false,
|
||||||
colorblind_mode: false,
|
colorblind_mode: false,
|
||||||
|
key_input_mode: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod diag;
|
|||||||
mod display;
|
mod display;
|
||||||
mod dummy_analyzer;
|
mod dummy_analyzer;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod key_input;
|
||||||
mod pcap;
|
mod pcap;
|
||||||
mod qmdl_store;
|
mod qmdl_store;
|
||||||
mod server;
|
mod server;
|
||||||
@@ -175,7 +176,7 @@ async fn main() -> Result<(), RayhunterError> {
|
|||||||
let store = init_qmdl_store(&config).await?;
|
let store = init_qmdl_store(&config).await?;
|
||||||
let analysis_status = AnalysisStatus::new(&store);
|
let analysis_status = AnalysisStatus::new(&store);
|
||||||
let qmdl_store_lock = Arc::new(RwLock::new(store));
|
let qmdl_store_lock = Arc::new(RwLock::new(store));
|
||||||
let (tx, rx) = mpsc::channel::<DiagDeviceCtrlMessage>(1);
|
let (diag_tx, diag_rx) = mpsc::channel::<DiagDeviceCtrlMessage>(1);
|
||||||
let (ui_update_tx, ui_update_rx) = mpsc::channel::<display::DisplayState>(1);
|
let (ui_update_tx, ui_update_rx) = mpsc::channel::<display::DisplayState>(1);
|
||||||
let (analysis_tx, analysis_rx) = mpsc::channel::<AnalysisCtrlMessage>(5);
|
let (analysis_tx, analysis_rx) = mpsc::channel::<AnalysisCtrlMessage>(5);
|
||||||
let mut maybe_ui_shutdown_tx = None;
|
let mut maybe_ui_shutdown_tx = None;
|
||||||
@@ -193,13 +194,17 @@ async fn main() -> Result<(), RayhunterError> {
|
|||||||
run_diag_read_thread(
|
run_diag_read_thread(
|
||||||
&task_tracker,
|
&task_tracker,
|
||||||
dev,
|
dev,
|
||||||
rx,
|
diag_rx,
|
||||||
ui_update_tx.clone(),
|
ui_update_tx.clone(),
|
||||||
qmdl_store_lock.clone(),
|
qmdl_store_lock.clone(),
|
||||||
|
analysis_tx.clone(),
|
||||||
config.enable_dummy_analyzer,
|
config.enable_dummy_analyzer,
|
||||||
);
|
);
|
||||||
info!("Starting UI");
|
info!("Starting UI");
|
||||||
display::update_ui(&task_tracker, &config, ui_shutdown_rx, ui_update_rx);
|
display::update_ui(&task_tracker, &config, ui_shutdown_rx, ui_update_rx);
|
||||||
|
|
||||||
|
info!("Starting Key Input service");
|
||||||
|
key_input::run_key_input_thread(&task_tracker, &config, diag_tx.clone());
|
||||||
}
|
}
|
||||||
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
|
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
|
||||||
info!("create shutdown thread");
|
info!("create shutdown thread");
|
||||||
@@ -213,7 +218,7 @@ async fn main() -> Result<(), RayhunterError> {
|
|||||||
);
|
);
|
||||||
run_ctrl_c_thread(
|
run_ctrl_c_thread(
|
||||||
&task_tracker,
|
&task_tracker,
|
||||||
tx.clone(),
|
diag_tx.clone(),
|
||||||
server_shutdown_tx,
|
server_shutdown_tx,
|
||||||
maybe_ui_shutdown_tx,
|
maybe_ui_shutdown_tx,
|
||||||
qmdl_store_lock.clone(),
|
qmdl_store_lock.clone(),
|
||||||
@@ -221,7 +226,7 @@ async fn main() -> Result<(), RayhunterError> {
|
|||||||
);
|
);
|
||||||
let state = Arc::new(ServerState {
|
let state = Arc::new(ServerState {
|
||||||
qmdl_store_lock: qmdl_store_lock.clone(),
|
qmdl_store_lock: qmdl_store_lock.clone(),
|
||||||
diag_device_ctrl_sender: tx,
|
diag_device_ctrl_sender: diag_tx,
|
||||||
ui_update_sender: ui_update_tx,
|
ui_update_sender: ui_update_tx,
|
||||||
debug_mode: config.debug_mode,
|
debug_mode: config.debug_mode,
|
||||||
analysis_status_lock,
|
analysis_status_lock,
|
||||||
|
|||||||
104
bin/src/diag.rs
@@ -7,7 +7,7 @@ use axum::http::header::CONTENT_TYPE;
|
|||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::{IntoResponse, Response};
|
||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info, warn};
|
||||||
use rayhunter::diag::DataType;
|
use rayhunter::diag::DataType;
|
||||||
use rayhunter::diag_device::DiagDevice;
|
use rayhunter::diag_device::DiagDevice;
|
||||||
use rayhunter::qmdl::QmdlWriter;
|
use rayhunter::qmdl::QmdlWriter;
|
||||||
@@ -24,7 +24,7 @@ use crate::server::ServerState;
|
|||||||
|
|
||||||
pub enum DiagDeviceCtrlMessage {
|
pub enum DiagDeviceCtrlMessage {
|
||||||
StopRecording,
|
StopRecording,
|
||||||
StartRecording((QmdlWriter<File>, File)),
|
StartRecording,
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ pub fn run_diag_read_thread(
|
|||||||
mut qmdl_file_rx: Receiver<DiagDeviceCtrlMessage>,
|
mut qmdl_file_rx: Receiver<DiagDeviceCtrlMessage>,
|
||||||
ui_update_sender: Sender<display::DisplayState>,
|
ui_update_sender: Sender<display::DisplayState>,
|
||||||
qmdl_store_lock: Arc<RwLock<RecordingStore>>,
|
qmdl_store_lock: Arc<RwLock<RecordingStore>>,
|
||||||
|
analysis_sender: Sender<AnalysisCtrlMessage>,
|
||||||
enable_dummy_analyzer: bool,
|
enable_dummy_analyzer: bool,
|
||||||
) {
|
) {
|
||||||
task_tracker.spawn(async move {
|
task_tracker.spawn(async move {
|
||||||
@@ -46,20 +47,53 @@ pub fn run_diag_read_thread(
|
|||||||
tokio::select! {
|
tokio::select! {
|
||||||
msg = qmdl_file_rx.recv() => {
|
msg = qmdl_file_rx.recv() => {
|
||||||
match msg {
|
match msg {
|
||||||
Some(DiagDeviceCtrlMessage::StartRecording((new_writer, new_analysis_file))) => {
|
Some(DiagDeviceCtrlMessage::StartRecording) => {
|
||||||
maybe_qmdl_writer = Some(new_writer);
|
let mut qmdl_store = qmdl_store_lock.write().await;
|
||||||
|
let (qmdl_file, new_analysis_file) = match qmdl_store.new_entry().await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
error!("couldn't create new qmdl entry: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
maybe_qmdl_writer = Some(QmdlWriter::new(qmdl_file));
|
||||||
|
|
||||||
if let Some(analysis_writer) = maybe_analysis_writer {
|
if let Some(analysis_writer) = maybe_analysis_writer {
|
||||||
analysis_writer.close().await.expect("failed to close analysis writer");
|
analysis_writer.close().await.expect("failed to close analysis writer");
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_analysis_writer = Some(AnalysisWriter::new(new_analysis_file, enable_dummy_analyzer).await
|
maybe_analysis_writer = Some(AnalysisWriter::new(new_analysis_file, enable_dummy_analyzer).await
|
||||||
.expect("failed to write to analysis file"));
|
.expect("failed to write to analysis file"));
|
||||||
|
|
||||||
|
if let Err(e) = ui_update_sender.send(display::DisplayState::Recording).await {
|
||||||
|
warn!("couldn't send ui update message: {}", e);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Some(DiagDeviceCtrlMessage::StopRecording) => {
|
Some(DiagDeviceCtrlMessage::StopRecording) => {
|
||||||
|
let mut qmdl_store = qmdl_store_lock.write().await;
|
||||||
|
if let Some((_, entry)) = qmdl_store.get_current_entry() {
|
||||||
|
if let Err(e) = analysis_sender
|
||||||
|
.send(AnalysisCtrlMessage::RecordingFinished(
|
||||||
|
entry.name.to_string(),
|
||||||
|
))
|
||||||
|
.await {
|
||||||
|
warn!("couldn't send analysis message: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(e) = qmdl_store.close_current_entry().await {
|
||||||
|
error!("couldn't close current entry: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
maybe_qmdl_writer = None;
|
maybe_qmdl_writer = None;
|
||||||
if let Some(analysis_writer) = maybe_analysis_writer {
|
if let Some(analysis_writer) = maybe_analysis_writer {
|
||||||
analysis_writer.close().await.expect("failed to close analysis writer");
|
analysis_writer.close().await.expect("failed to close analysis writer");
|
||||||
}
|
}
|
||||||
maybe_analysis_writer = None;
|
maybe_analysis_writer = None;
|
||||||
|
|
||||||
|
if let Err(e) = ui_update_sender.send(display::DisplayState::Paused).await {
|
||||||
|
warn!("couldn't send ui update message: {}", e);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// None means all the Senders have been dropped, so it's
|
// None means all the Senders have been dropped, so it's
|
||||||
// time to go
|
// time to go
|
||||||
@@ -125,37 +159,15 @@ pub async fn start_recording(
|
|||||||
if state.debug_mode {
|
if state.debug_mode {
|
||||||
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
||||||
}
|
}
|
||||||
let mut qmdl_store = state.qmdl_store_lock.write().await;
|
|
||||||
let (qmdl_file, analysis_file) = qmdl_store.new_entry().await.map_err(|e| {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("couldn't create new qmdl entry: {}", e),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let qmdl_writer = QmdlWriter::new(qmdl_file);
|
|
||||||
state
|
state
|
||||||
.diag_device_ctrl_sender
|
.diag_device_ctrl_sender
|
||||||
.send(DiagDeviceCtrlMessage::StartRecording((
|
.send(DiagDeviceCtrlMessage::StartRecording)
|
||||||
qmdl_writer,
|
|
||||||
analysis_file,
|
|
||||||
)))
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
(
|
(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
format!("couldn't send stop recording message: {}", e),
|
format!("couldn't send start recording message: {}", e),
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let display_state = display::DisplayState::Recording;
|
|
||||||
state
|
|
||||||
.ui_update_sender
|
|
||||||
.send(display_state)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("couldn't send ui update message: {}", e),
|
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -168,30 +180,6 @@ pub async fn stop_recording(
|
|||||||
if state.debug_mode {
|
if state.debug_mode {
|
||||||
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
||||||
}
|
}
|
||||||
let mut qmdl_store = state.qmdl_store_lock.write().await;
|
|
||||||
match qmdl_store.get_current_entry() {
|
|
||||||
Some((_, entry)) => {
|
|
||||||
state
|
|
||||||
.analysis_sender
|
|
||||||
.send(AnalysisCtrlMessage::RecordingFinished(
|
|
||||||
entry.name.to_string(),
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("couldn't send AnalysisCtrlMessage: {}", e),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
None => todo!(),
|
|
||||||
}
|
|
||||||
qmdl_store.close_current_entry().await.map_err(|e| {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("couldn't close current qmdl entry: {}", e),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
state
|
state
|
||||||
.diag_device_ctrl_sender
|
.diag_device_ctrl_sender
|
||||||
.send(DiagDeviceCtrlMessage::StopRecording)
|
.send(DiagDeviceCtrlMessage::StopRecording)
|
||||||
@@ -202,16 +190,6 @@ pub async fn stop_recording(
|
|||||||
format!("couldn't send stop recording message: {}", e),
|
format!("couldn't send stop recording message: {}", e),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
state
|
|
||||||
.ui_update_sender
|
|
||||||
.send(display::DisplayState::Paused)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("couldn't send ui update message: {}", e),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok((StatusCode::ACCEPTED, "ok".to_string()))
|
Ok((StatusCode::ACCEPTED, "ok".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
100
bin/src/key_input.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use log::error;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use tokio::fs::File;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
|
use tokio_util::task::TaskTracker;
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
|
use crate::diag::DiagDeviceCtrlMessage;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Event {
|
||||||
|
KeyDown,
|
||||||
|
KeyUp,
|
||||||
|
}
|
||||||
|
|
||||||
|
const INPUT_EVENT_SIZE: usize = 32;
|
||||||
|
|
||||||
|
pub fn run_key_input_thread(
|
||||||
|
task_tracker: &TaskTracker,
|
||||||
|
config: &config::Config,
|
||||||
|
diag_tx: Sender<DiagDeviceCtrlMessage>,
|
||||||
|
) {
|
||||||
|
if config.key_input_mode == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
task_tracker.spawn(async move {
|
||||||
|
// Open the input device
|
||||||
|
let mut file = match File::open("/dev/input/event0").await {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to open /dev/input/event0: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buffer = [0u8; INPUT_EVENT_SIZE];
|
||||||
|
let mut last_keyup: Option<Instant> = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Err(e) = file.read_exact(&mut buffer).await {
|
||||||
|
error!("failed to read key input: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let event = parse_event(buffer);
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::KeyUp => {
|
||||||
|
if last_keyup.is_some()
|
||||||
|
&& last_keyup.unwrap().elapsed() < Duration::from_millis(500)
|
||||||
|
{
|
||||||
|
if let Err(e) = diag_tx.send(DiagDeviceCtrlMessage::StopRecording).await {
|
||||||
|
error!("Failed to send StopRecording: {}", e);
|
||||||
|
}
|
||||||
|
if let Err(e) = diag_tx.send(DiagDeviceCtrlMessage::StartRecording).await {
|
||||||
|
error!("Failed to send StartRecording: {}", e);
|
||||||
|
}
|
||||||
|
last_keyup = None;
|
||||||
|
} else {
|
||||||
|
last_keyup = Some(Instant::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::KeyDown => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_event(input: [u8; INPUT_EVENT_SIZE]) -> Event {
|
||||||
|
if input[12] == 0 {
|
||||||
|
Event::KeyUp
|
||||||
|
} else {
|
||||||
|
Event::KeyDown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_event_keydown_m7350_v5() {
|
||||||
|
let input = [
|
||||||
|
0x57, 0x6c, 0x09, 0x00, 0x7c, 0xfb, 0x03, 0x00, 0x01, 0x00, 0x74, 0x00, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
assert!(matches!(parse_event(input), Event::KeyDown));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_event_keyup_m7350_v5() {
|
||||||
|
let input = [
|
||||||
|
0x57, 0x6c, 0x09, 0x00, 0x1b, 0x15, 0x05, 0x00, 0x01, 0x00, 0x74, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
assert!(matches!(parse_event(input), Event::KeyUp));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
use std::io::{self, ErrorKind};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use rayhunter::util::RuntimeMetadata;
|
use rayhunter::util::RuntimeMetadata;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{self, try_exists, File, OpenOptions},
|
fs::{self, try_exists, File, OpenOptions},
|
||||||
@@ -289,10 +291,10 @@ impl RecordingStore {
|
|||||||
self.write_manifest().await?;
|
self.write_manifest().await?;
|
||||||
let qmdl_filepath = entry_to_delete.get_qmdl_filepath(&self.path);
|
let qmdl_filepath = entry_to_delete.get_qmdl_filepath(&self.path);
|
||||||
let analysis_filepath = entry_to_delete.get_analysis_filepath(&self.path);
|
let analysis_filepath = entry_to_delete.get_analysis_filepath(&self.path);
|
||||||
tokio::fs::remove_file(qmdl_filepath)
|
remove_file_if_exists(&qmdl_filepath)
|
||||||
.await
|
.await
|
||||||
.map_err(RecordingStoreError::DeleteFileError)?;
|
.map_err(RecordingStoreError::DeleteFileError)?;
|
||||||
tokio::fs::remove_file(analysis_filepath)
|
remove_file_if_exists(&analysis_filepath)
|
||||||
.await
|
.await
|
||||||
.map_err(RecordingStoreError::DeleteFileError)?;
|
.map_err(RecordingStoreError::DeleteFileError)?;
|
||||||
Ok(entry_to_delete)
|
Ok(entry_to_delete)
|
||||||
@@ -303,22 +305,41 @@ impl RecordingStore {
|
|||||||
self.close_current_entry().await?;
|
self.close_current_entry().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut keep = Vec::new();
|
||||||
|
|
||||||
for entry in &self.manifest.entries {
|
for entry in &self.manifest.entries {
|
||||||
let qmdl_filepath = entry.get_qmdl_filepath(&self.path);
|
let qmdl_filepath = entry.get_qmdl_filepath(&self.path);
|
||||||
let analysis_filepath = entry.get_analysis_filepath(&self.path);
|
let analysis_filepath = entry.get_analysis_filepath(&self.path);
|
||||||
tokio::fs::remove_file(qmdl_filepath)
|
|
||||||
.await
|
if let Err(e) = remove_file_if_exists(&qmdl_filepath).await {
|
||||||
.map_err(RecordingStoreError::DeleteFileError)?;
|
log::warn!("failed to remove {qmdl_filepath:?}: {e:?}");
|
||||||
tokio::fs::remove_file(analysis_filepath)
|
keep.push(true);
|
||||||
.await
|
continue;
|
||||||
.map_err(RecordingStoreError::DeleteFileError)?;
|
}
|
||||||
|
|
||||||
|
if let Err(e) = remove_file_if_exists(&analysis_filepath).await {
|
||||||
|
log::warn!("failed to remove {analysis_filepath:?}: {e:?}");
|
||||||
|
keep.push(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
keep.push(false);
|
||||||
}
|
}
|
||||||
self.manifest.entries.drain(..);
|
|
||||||
|
let mut keep_iter = keep.into_iter();
|
||||||
|
self.manifest.entries.retain(|_| keep_iter.next().unwrap());
|
||||||
self.write_manifest().await?;
|
self.write_manifest().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn remove_file_if_exists(path: &Path) -> Result<(), io::Error> {
|
||||||
|
match tokio::fs::remove_file(path).await {
|
||||||
|
Err(e) if e.kind() == ErrorKind::NotFound => Ok(()),
|
||||||
|
res => res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -7,6 +7,6 @@
|
|||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents" class="m-4 xl:m-8">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function parse_finished_report(report_json: NewlineDeliminatedJson): Anal
|
|||||||
const events: Event[] = analysis_json.events.map((event_json: any): Event | null => {
|
const events: Event[] = analysis_json.events.map((event_json: any): Event | null => {
|
||||||
if (event_json === null) {
|
if (event_json === null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (event_json.event_type === "Informational") {
|
} else if (event_json.event_type.type === "Informational") {
|
||||||
num_informational_logs += 1;
|
num_informational_logs += 1;
|
||||||
return {
|
return {
|
||||||
type: EventType.Informational,
|
type: EventType.Informational,
|
||||||
@@ -82,8 +82,8 @@ export function parse_finished_report(report_json: NewlineDeliminatedJson): Anal
|
|||||||
num_warnings += 1;
|
num_warnings += 1;
|
||||||
return {
|
return {
|
||||||
type: EventType.Warning,
|
type: EventType.Warning,
|
||||||
severity: event_json.severity === "High" ? Severity.High :
|
severity: event_json.event_type.severity === "High" ? Severity.High :
|
||||||
event_json.severity === "Medium" ? Severity.Medium : Severity.Low,
|
event_json.event_type.severity === "Medium" ? Severity.Medium : Severity.Low,
|
||||||
message: event_json.message,
|
message: event_json.message,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
import { AnalysisStatus } from "$lib/analysisManager.svelte";
|
import { AnalysisStatus } from "$lib/analysisManager.svelte";
|
||||||
import { EventType } from "$lib/analysis.svelte";
|
import { EventType } from "$lib/analysis.svelte";
|
||||||
import type { ManifestEntry } from "$lib/manifest.svelte";
|
import type { ManifestEntry } from "$lib/manifest.svelte";
|
||||||
let { entry, onclick }: {
|
let { entry, onclick, analysis_visible}: {
|
||||||
entry: ManifestEntry,
|
entry: ManifestEntry,
|
||||||
onclick: () => void,
|
onclick: () => void,
|
||||||
|
analysis_visible: boolean,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let summary = $derived.by(() => {
|
let summary = $derived.by(() => {
|
||||||
@@ -41,12 +42,11 @@
|
|||||||
return finished && report_available;
|
return finished && report_available;
|
||||||
})
|
})
|
||||||
|
|
||||||
let button_class = $derived(ready ? "text-blue-600 underline" : '');
|
let button_class = $derived(ready ? "text-blue-600 border rounded-full px-2" : '');
|
||||||
</script>
|
</script>
|
||||||
|
<button class="flex flex-row gap-1 lg:gap-2" disabled={!ready} {onclick}>
|
||||||
<button class={button_class} disabled={!ready} {onclick}>
|
<span class="{button_class} {entry.get_num_warnings() < 1 ? 'text-green-700 border-green-500 bg-green-200' : 'text-red-700 border-red-500 bg-red-200'}">{summary}</span>
|
||||||
{summary}
|
<svg class="w-6 h-6 text-gray-800 transition-transform {analysis_visible ? 'rotate-180' : ''}" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
</button>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 9-7 7-7-7"/>
|
||||||
|
</svg>
|
||||||
<style>
|
</button>
|
||||||
</style>
|
|
||||||
@@ -25,60 +25,63 @@
|
|||||||
return map;
|
return map;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<div>
|
||||||
<p class="text-lg underline">Warnings and Informational Logs</p>
|
<p class="text-lg underline">Warnings and Informational Logs</p>
|
||||||
{#if report.statistics.num_warnings === 0 && report.statistics.num_informational_logs === 0}
|
{#if report.statistics.num_warnings === 0 && report.statistics.num_informational_logs === 0}
|
||||||
<p>Nothing to show!</p>
|
<p>Nothing to show!</p>
|
||||||
{:else}
|
{:else}
|
||||||
<table class="table-auto text-left border">
|
<table class="table-auto text-left">
|
||||||
<thead class="p-2">
|
<thead class="p-2">
|
||||||
<tr class="bg-gray-300">
|
<tr class="bg-gray-300">
|
||||||
<th scope="col">Timestamp</th>
|
<th class="p-2">Timestamp</th>
|
||||||
<th scope="col">Warning</th>
|
<th class="p-2">Warning</th>
|
||||||
<th scope="col">Severity</th>
|
<th class="p-2">Severity</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each report.rows as row, row_idx}
|
{#each report.rows as row, row_idx}
|
||||||
{#each row.analysis as analysis}
|
{#each row.analysis as analysis}
|
||||||
{@const parsed_date = new Date(analysis.timestamp)}
|
{@const parsed_date = new Date(analysis.timestamp)}
|
||||||
{#each analysis.events.filter(e => e !== null) as event}
|
{#each analysis.events.filter(e => e !== null) as event}
|
||||||
<tr class="even:bg-gray-200 border-b">
|
<tr class="even:bg-gray-200 odd:bg-white">
|
||||||
{#if event.type === EventType.Warning}
|
{#if event.type === EventType.Warning}
|
||||||
{@const severity = ['Low', 'Medium', 'High'][event.severity]}
|
{@const severity = ['Low', 'Medium', 'High'][event.severity]}
|
||||||
{@const severity_class = ['bg-red-200', 'bg-red-400', 'bg-red-600'][event.severity]}
|
{@const severity_class = ['bg-red-200', 'bg-red-400', 'bg-red-600'][event.severity]}
|
||||||
<th class="p-2">{date_formatter.format(parsed_date)}</th>
|
<td class="p-2">{date_formatter.format(parsed_date)}</td>
|
||||||
<td class="p-2">{event.message}</td>
|
<td class="p-2">{event.message}</td>
|
||||||
<td class="p-2 {severity_class}">{severity}</td>
|
<td class="p-2 {severity_class} text-center">{severity}</td>
|
||||||
{:else if event.type === EventType.Informational}
|
{:else if event.type === EventType.Informational}
|
||||||
<th class="p-2">{date_formatter.format(parsed_date)}</th>
|
<td class="p-2">{date_formatter.format(parsed_date)}</td>
|
||||||
<td class="p-2">{event.message}</td>
|
<td class="p-2">{event.message}</td>
|
||||||
<td class="p-2">Info</td>
|
<td class="p-2">Info</td>
|
||||||
{/if}
|
{/if}
|
||||||
</tr>
|
</tr>
|
||||||
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
{#if report.statistics.num_skipped_packets > 0}
|
{#if report.statistics.num_skipped_packets > 0}
|
||||||
|
<div>
|
||||||
<p class="text-lg underline">Unparsed Messages</p>
|
<p class="text-lg underline">Unparsed Messages</p>
|
||||||
<p>These are due to a limitation or bug in Rayhunter's parser, and aren't ususally a problem.</p>
|
<p>These are due to a limitation or bug in Rayhunter's parser, and aren't ususally a problem.</p>
|
||||||
<table class="table-auto text-left border">
|
<table class="table-auto text-left">
|
||||||
<thead class="p-2">
|
<thead class="p-2">
|
||||||
<tr class="bg-gray-300">
|
<tr class="bg-gray-300">
|
||||||
<th scope="col"># of messages affected</th>
|
<th scope="col" class="p-2">Total Msgs Affected</th>
|
||||||
<th scope="col">Reason/Error</th>
|
<th scope="col">Reason/Error</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each skipped_messages.entries() as [message, count]}
|
{#each skipped_messages.entries() as [message, count]}
|
||||||
<tr class="even:bg-gray-200 border-b">
|
<tr class="even:bg-gray-200 odd:bg-white">
|
||||||
<td>{count}</td>
|
<td class="text-center">{count}</td>
|
||||||
<td>{message}</td>
|
<td>{message}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -13,32 +13,34 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container max-h-96 overflow-auto">
|
<div class="container mt-2">
|
||||||
{#if entry.analysis_report === undefined}
|
{#if entry.analysis_report === undefined}
|
||||||
<p>Report unavailable, try refreshing.</p>
|
<p>Report unavailable, try refreshing.</p>
|
||||||
{:else if typeof(entry.analysis_report) === 'string'}
|
{:else if typeof(entry.analysis_report) === 'string'}
|
||||||
<p>Error getting analysis report: {entry.analysis_report}</p>
|
<p>Error getting analysis report: {entry.analysis_report}</p>
|
||||||
{:else}
|
{:else}
|
||||||
{@const metadata: ReportMetadata = entry.analysis_report.metadata}
|
{@const metadata: ReportMetadata = entry.analysis_report.metadata}
|
||||||
<div class="flex flex-col pl-2 pr-10 w-full">
|
<div class="flex flex-col gap-2">
|
||||||
{#if entry.analysis_report.rows.length > 0}
|
{#if entry.analysis_report.rows.length > 0}
|
||||||
<AnalysisTable report={entry.analysis_report} />
|
<AnalysisTable report={entry.analysis_report} />
|
||||||
{:else}
|
{:else}
|
||||||
<p>No warnings to display!</p>
|
<p>No warnings to display!</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if metadata !== undefined && metadata.rayhunter !== undefined}
|
||||||
<div>
|
<div>
|
||||||
<p class="text-lg underline">Metadata</p>
|
<p class="text-lg underline">Metadata</p>
|
||||||
{#if metadata !== undefined && metadata.rayhunter !== undefined}
|
<p>Analysis by Rayhunter version {metadata.rayhunter.rayhunter_version}</p>
|
||||||
<p>Analysis by Rayhunter version {metadata.rayhunter.rayhunter_version}</p>
|
<p><b>Device system OS:</b> {metadata.rayhunter.system_os}</p>
|
||||||
<p><b>Device system OS:</b> {metadata.rayhunter.system_os}</p>
|
|
||||||
<p class="text-lg underline">Analyzers</p>
|
|
||||||
{#each metadata.analyzers as analyzer}
|
|
||||||
<p><b>{analyzer.name}:</b> {analyzer.description}</p>
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
<p>N/A (analysis generated by an older version of rayhunter)</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-lg underline">Analyzers</p>
|
||||||
|
{#each metadata.analyzers as analyzer}
|
||||||
|
<p><b>{analyzer.name}:</b> {analyzer.description}</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p>N/A (analysis generated by an older version of rayhunter)</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { req } from "$lib/utils.svelte";
|
|
||||||
import DeleteButton from "./DeleteButton.svelte";
|
|
||||||
import RecordingControls from "./RecordingControls.svelte";
|
|
||||||
let { server_is_recording }: {
|
|
||||||
server_is_recording: boolean;
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
function confirmDelete() {
|
|
||||||
if (window.confirm(`Permanently delete ALL entries?`)) {
|
|
||||||
req('POST', '/api/delete-all-recordings')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<RecordingControls {server_is_recording} />
|
|
||||||
<DeleteButton
|
|
||||||
text="Delete ALL Entries"
|
|
||||||
prompt={`Are you sure you want to delete ALL entries?`}
|
|
||||||
url={`/api/delete-all-recordings`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
18
bin/web/src/lib/components/DeleteAllButton.svelte
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { req } from "$lib/utils.svelte";
|
||||||
|
import DeleteButton from "./DeleteButton.svelte";
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
if (window.confirm(`Permanently delete ALL recordings?`)) {
|
||||||
|
req('POST', '/api/delete-all-recordings')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-row justify-end gap-2">
|
||||||
|
<DeleteButton
|
||||||
|
text="Delete ALL Recordings"
|
||||||
|
prompt={`Are you sure you want to delete ALL recordings?`}
|
||||||
|
url={`/api/delete-all-recordings`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let { url, text }: {
|
let { url, text, full_button=false }: {
|
||||||
url: string;
|
url: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
full_button?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
function download() {
|
function download() {
|
||||||
@@ -9,8 +10,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="text-blue-600 flex flex-row underline" onclick={download}>
|
<button class="flex flex-row {full_button ? 'bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md' : 'text-blue-600 underline'}" onclick={download}>
|
||||||
{text} <svg class="fill-current w-4 h-4 m-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
{text}
|
||||||
|
<svg class="fill-current w-4 h-4 m-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||||
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/>
|
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
74
bin/web/src/lib/components/ManifestCard.svelte
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ManifestEntry } from "$lib/manifest.svelte";
|
||||||
|
import DownloadLink from '$lib/components/DownloadLink.svelte';
|
||||||
|
import DeleteButton from "$lib/components/DeleteButton.svelte";
|
||||||
|
import AnalysisStatus from "./AnalysisStatus.svelte";
|
||||||
|
import AnalysisView from "./AnalysisView.svelte";
|
||||||
|
import RecordingControls from "./RecordingControls.svelte";
|
||||||
|
let { entry, current, i, server_is_recording }: {
|
||||||
|
entry: ManifestEntry;
|
||||||
|
current: boolean;
|
||||||
|
i: number;
|
||||||
|
server_is_recording: boolean;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
// passing `undefined` as the locale uses the browser default
|
||||||
|
const date_formatter = new Intl.DateTimeFormat(undefined, {
|
||||||
|
timeStyle: "long",
|
||||||
|
dateStyle: "short",
|
||||||
|
});
|
||||||
|
let status_row_color = $derived.by(() => {
|
||||||
|
const num_warnings = entry.get_num_warnings();
|
||||||
|
if (num_warnings !== undefined && num_warnings > 0) {
|
||||||
|
return "bg-red-100";
|
||||||
|
}
|
||||||
|
return current ? "bg-green-100" : "bg-gray-100"
|
||||||
|
});
|
||||||
|
let status_border_color = $derived.by(() => {
|
||||||
|
const num_warnings = entry.get_num_warnings();
|
||||||
|
if (num_warnings !== undefined && num_warnings > 0) {
|
||||||
|
return "border-red-100";
|
||||||
|
}
|
||||||
|
return current ? "border-green-100" : "border-gray-100"
|
||||||
|
});
|
||||||
|
let analysis_visible = $state(false);
|
||||||
|
function toggle_analysis_visibility() {
|
||||||
|
analysis_visible = !analysis_visible;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="{status_row_color} {status_border_color} drop-shadow p-4 flex flex-col gap-2 border rounded-md flex-1">
|
||||||
|
{#if current}
|
||||||
|
<div class="flex flex-row justify-between gap-2">
|
||||||
|
<span class="text-xl mb-2">Current Recording</span>
|
||||||
|
<span class=""><AnalysisStatus onclick={toggle_analysis_visibility} entry={entry} analysis_visible={analysis_visible}/></span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex flex-row justify-between">
|
||||||
|
<span class="font-bold">ID: {entry.name}</span>
|
||||||
|
{#if !current}
|
||||||
|
<span class=""><AnalysisStatus onclick={toggle_analysis_visibility} entry={entry} analysis_visible={analysis_visible}/></span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<span class="">{entry.get_readable_qmdl_size()}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="">Start: {date_formatter.format(entry.start_time)}</span>
|
||||||
|
<span class="">Last Message: {date_formatter.format(entry.last_message_time)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row justify-between lg:justify-end gap-2 mt-2">
|
||||||
|
<DownloadLink url={entry.get_pcap_url()} text="pcap" full_button=true />
|
||||||
|
<DownloadLink url={entry.get_qmdl_url()} text="qmdl" full_button=true />
|
||||||
|
{#if current}
|
||||||
|
<RecordingControls {server_is_recording} />
|
||||||
|
{:else}
|
||||||
|
<DeleteButton
|
||||||
|
prompt={`Are you sure you want to delete entry ${entry.name}?`}
|
||||||
|
url={entry.get_delete_url()}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="border-b {analysis_visible ? '' : 'hidden'}">
|
||||||
|
<AnalysisView {entry} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,32 +1,37 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Manifest, ManifestEntry } from "$lib/manifest.svelte";
|
import { Manifest, ManifestEntry } from "$lib/manifest.svelte";
|
||||||
import TableRow from "./ManifestTableRow.svelte";
|
import TableRow from "./ManifestTableRow.svelte";
|
||||||
|
import Card from "./ManifestCard.svelte"
|
||||||
interface Props {
|
interface Props {
|
||||||
entries: ManifestEntry[];
|
entries: ManifestEntry[];
|
||||||
current_entry: ManifestEntry | undefined;
|
server_is_recording: boolean;
|
||||||
}
|
}
|
||||||
let { entries, current_entry }: Props = $props();
|
let { entries, server_is_recording }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<table class="table-auto text-left border">
|
<!--For larger screens we use a table-->
|
||||||
<thead class="p-2">
|
<table class="hidden table-auto text-left lg:table">
|
||||||
<tr class="bg-gray-300">
|
<thead>
|
||||||
<th class='p-2' scope="col">Name</th>
|
<tr class="bg-gray-100 drop-shadow">
|
||||||
<th class='p-2' scope="col">Date Started</th>
|
<th class='p-2' scope="col">ID</th>
|
||||||
<th class='p-2' scope="col">Date of Last Message</th>
|
<th class='p-2' scope="col">Started</th>
|
||||||
<th class='p-2' scope="col">Size (bytes)</th>
|
<th class='p-2' scope="col">Last Message</th>
|
||||||
|
<th class='p-2' scope="col">Size</th>
|
||||||
<th class='p-2' scope="col">PCAP</th>
|
<th class='p-2' scope="col">PCAP</th>
|
||||||
<th class='p-2' scope="col">QMDL</th>
|
<th class='p-2' scope="col">QMDL</th>
|
||||||
<th class='p-2' scope="col">Analysis</th>
|
<th class='p-2' scope="col">Analysis</th>
|
||||||
<th class='p-2' scope="col">Delete</th>
|
<th class='p-2' scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#if current_entry !== undefined}
|
|
||||||
<TableRow entry={current_entry} current={true} i={0} />
|
|
||||||
{/if}
|
|
||||||
{#each entries as entry, i}
|
{#each entries as entry, i}
|
||||||
<TableRow {entry} current={false} {i} />
|
<TableRow {entry} current={false} {i} />
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<!--For smaller screens we use cards-->
|
||||||
|
<div class="lg:hidden flex flex-col gap-4">
|
||||||
|
{#each entries as entry, i}
|
||||||
|
<Card {entry} current={false} {i} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
let { entry, current, i }: {
|
let { entry, current, i }: {
|
||||||
entry: ManifestEntry;
|
entry: ManifestEntry;
|
||||||
current: boolean;
|
current: boolean;
|
||||||
i: number
|
i: number;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
// passing `undefined` as the locale uses the browser default
|
// passing `undefined` as the locale uses the browser default
|
||||||
@@ -29,14 +29,14 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<tr class="{status_row_color}">
|
<tr class="{status_row_color} drop-shadow">
|
||||||
<th class="font-bold p-2 bg-blue-100" scope='row'>{entry.name}</th>
|
<td class="p-2">{entry.name}</td>
|
||||||
<td class="p-2">{date_formatter.format(entry.start_time)}</td>
|
<td class="p-2">{date_formatter.format(entry.start_time)}</td>
|
||||||
<td class="p-2">{date_formatter.format(entry.last_message_time)}</td>
|
<td class="p-2">{date_formatter.format(entry.last_message_time)}</td>
|
||||||
<td class="p-2">{entry.qmdl_size_bytes}</td>
|
<td class="p-2">{entry.get_readable_qmdl_size()}</td>
|
||||||
<td class="p-2"><DownloadLink url={entry.get_pcap_url()} text="pcap" /></td>
|
<td class="p-2"><DownloadLink url={entry.get_pcap_url()} text="pcap" /></td>
|
||||||
<td class="p-2"><DownloadLink url={entry.get_qmdl_url()} text="qmdl" /></td>
|
<td class="p-2"><DownloadLink url={entry.get_qmdl_url()} text="qmdl" /></td>
|
||||||
<td class="p-2"><AnalysisStatus onclick={toggle_analysis_visibility} entry={entry} /></td>
|
<td class="p-2"><AnalysisStatus onclick={toggle_analysis_visibility} entry={entry} analysis_visible={analysis_visible}/></td>
|
||||||
{#if current}
|
{#if current}
|
||||||
<td class="p-2"></td>
|
<td class="p-2"></td>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -49,8 +49,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="{alternating_row_color} border-b {analysis_visible ? '' : 'hidden'}">
|
<tr class="{alternating_row_color} border-b {analysis_visible ? '' : 'hidden'}">
|
||||||
<td class="font-bold p-2 bg-blue-100"></td>
|
<td class="border-t border-dashed p-2" colspan="8">
|
||||||
<td class="border-t border-dashed p-2" colspan="7">
|
|
||||||
<AnalysisView {entry} />
|
<AnalysisView {entry} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
client_set_recording = false;
|
client_set_recording = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stop_recording_classes = "bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-md";
|
const recording_button_classes = "text-white font-bold py-2 px-4 rounded-md flex flex-row gap-1";
|
||||||
const start_recording_classes = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -27,9 +26,19 @@
|
|||||||
{server_is_recording ? "Stopping..." : "Starting..."}
|
{server_is_recording ? "Stopping..." : "Starting..."}
|
||||||
</button>
|
</button>
|
||||||
{:else if server_is_recording}
|
{:else if server_is_recording}
|
||||||
<button class={stop_recording_classes} onclick={stop_recording}>Stop Recording</button>
|
<button class="{recording_button_classes} bg-red-500 hover:bg-red-700" onclick={stop_recording}>
|
||||||
|
<span>Stop</span>
|
||||||
|
<svg class="w-6 h-6 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M7 5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H7Z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class={start_recording_classes} onclick={start_recording}>Start Recording</button>
|
<button class="{recording_button_classes} bg-blue-500 hover:bg-blue-700" onclick={start_recording}>
|
||||||
|
<span>Start</span>
|
||||||
|
<svg class="w-6 h-6 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path fill-rule="evenodd" d="M8.6 5.2A1 1 0 0 0 7 6v12a1 1 0 0 0 1.6.8l8-6a1 1 0 0 0 0-1.6l-8-6Z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,31 +4,32 @@
|
|||||||
stats: SystemStats;
|
stats: SystemStats;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
|
const table_cell_classes = "border p-1 lg:p-2";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="flex-1 drop-shadow p-4 flex flex-col gap-2 border rounded-md bg-gray-100 border-gray-100">
|
||||||
<p class="text-xl">System Stats</p>
|
<p class="text-xl mb-2">System Information</p>
|
||||||
<table class="table-auto border">
|
<table class="table-auto border">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="border">
|
<tr class="border">
|
||||||
<th class="border">
|
<th class={table_cell_classes}>
|
||||||
Rayhunter version
|
Rayhunter Version
|
||||||
</th>
|
</th>
|
||||||
<td class="border">{stats.runtime_metadata.rayhunter_version}</td>
|
<td class={table_cell_classes}>{stats.runtime_metadata.rayhunter_version}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="border">
|
<tr class="border">
|
||||||
<th class="border">
|
<th class={table_cell_classes}>
|
||||||
Storage
|
Storage
|
||||||
</th>
|
</th>
|
||||||
<td class="border">
|
<td class={table_cell_classes}>
|
||||||
{stats.disk_stats.used_percent} used ({stats.disk_stats.used_size} / {stats.disk_stats.available_size})
|
{stats.disk_stats.used_percent} used ({stats.disk_stats.used_size} used / {stats.disk_stats.available_size} available)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="border-b">
|
<tr class="border-b">
|
||||||
<th class="border">
|
<th class={table_cell_classes}>
|
||||||
Memory (RAM)
|
Memory (RAM)
|
||||||
</th>
|
</th>
|
||||||
<td class="border">
|
<td class={table_cell_classes}>
|
||||||
Free: {stats.memory_stats.free}, Used: {stats.memory_stats.used}
|
Free: {stats.memory_stats.free}, Used: {stats.memory_stats.used}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -69,6 +69,15 @@ export class ManifestEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_readable_qmdl_size(): string {
|
||||||
|
if (this.qmdl_size_bytes === 0) return "0 Bytes";
|
||||||
|
const k = 1024;
|
||||||
|
const dm = 2 || 2;
|
||||||
|
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
|
const i = Math.floor(Math.log(this.qmdl_size_bytes) / Math.log(k));
|
||||||
|
return `${Number.parseFloat((this.qmdl_size_bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
||||||
|
}
|
||||||
|
|
||||||
get_num_warnings(): number | undefined {
|
get_num_warnings(): number | undefined {
|
||||||
if (this.analysis_report === undefined || typeof(this.analysis_report) === 'string') {
|
if (this.analysis_report === undefined || typeof(this.analysis_report) === 'string') {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
import { ManifestEntry } from "$lib/manifest.svelte";
|
import { ManifestEntry } from "$lib/manifest.svelte";
|
||||||
import { get_manifest, get_system_stats } from "$lib/utils.svelte";
|
import { get_manifest, get_system_stats } from "$lib/utils.svelte";
|
||||||
import ManifestTable from "$lib/components/ManifestTable.svelte";
|
import ManifestTable from "$lib/components/ManifestTable.svelte";
|
||||||
|
import Card from "$lib/components/ManifestCard.svelte";
|
||||||
import type { SystemStats } from "$lib/systemStats";
|
import type { SystemStats } from "$lib/systemStats";
|
||||||
import { AnalysisManager } from "$lib/analysisManager.svelte";
|
import { AnalysisManager } from "$lib/analysisManager.svelte";
|
||||||
import SystemStatsTable from "$lib/components/SystemStatsTable.svelte";
|
import SystemStatsTable from "$lib/components/SystemStatsTable.svelte";
|
||||||
import ControlBar from "$lib/components/ControlBar.svelte";
|
import DeleteAllButton from "$lib/components/DeleteAllButton.svelte";
|
||||||
|
import RecordingControls from "$lib/components//RecordingControls.svelte";
|
||||||
|
|
||||||
let manager: AnalysisManager = new AnalysisManager();
|
let manager: AnalysisManager = new AnalysisManager();
|
||||||
let loaded = $state(false);
|
let loaded = $state(false);
|
||||||
@@ -30,13 +32,53 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="ml-8 mt-8 text-4xl font-extrabold">Rayhunter Dashboard</h1>
|
<div class="p-4 xl:px-8 bg-rayhunter-blue drop-shadow flex flex-row justify-between items-center">
|
||||||
<div class="p-8 flex flex-col gap-2">
|
<img src="/rayhunter_text.png" class="h-10 xl:h-12"/>
|
||||||
|
<div class="flex flex-row gap-4">
|
||||||
|
<a class="flex flex-row gap-1 group" href="https://github.com/EFForg/rayhunter/issues" target="_blank">
|
||||||
|
<span class="hidden text-white group-hover:text-gray-400 lg:flex">Report Issue</span>
|
||||||
|
<svg class="w-6 h-6 text-white group-hover:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path fill-rule="evenodd" d="M12.006 2a9.847 9.847 0 0 0-6.484 2.44 10.32 10.32 0 0 0-3.393 6.17 10.48 10.48 0 0 0 1.317 6.955 10.045 10.045 0 0 0 5.4 4.418c.504.095.683-.223.683-.494 0-.245-.01-1.052-.014-1.908-2.78.62-3.366-1.21-3.366-1.21a2.711 2.711 0 0 0-1.11-1.5c-.907-.637.07-.621.07-.621.317.044.62.163.885.346.266.183.487.426.647.71.135.253.318.476.538.655a2.079 2.079 0 0 0 2.37.196c.045-.52.27-1.006.635-1.37-2.219-.259-4.554-1.138-4.554-5.07a4.022 4.022 0 0 1 1.031-2.75 3.77 3.77 0 0 1 .096-2.713s.839-.275 2.749 1.05a9.26 9.26 0 0 1 5.004 0c1.906-1.325 2.74-1.05 2.74-1.05.37.858.406 1.828.101 2.713a4.017 4.017 0 0 1 1.029 2.75c0 3.939-2.339 4.805-4.564 5.058a2.471 2.471 0 0 1 .679 1.897c0 1.372-.012 2.477-.012 2.814 0 .272.18.592.687.492a10.05 10.05 0 0 0 5.388-4.421 10.473 10.473 0 0 0 1.313-6.948 10.32 10.32 0 0 0-3.39-6.165A9.847 9.847 0 0 0 12.007 2Z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a class="flex flex-row gap-1 group" href="https://efforg.github.io/rayhunter/" target="_blank">
|
||||||
|
<span class="hidden text-white group-hover:text-gray-400 lg:flex">Docs</span>
|
||||||
|
<svg class="w-6 h-6 text-white group-hover:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19V4a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v13H7a2 2 0 0 0-2 2Zm0 0a2 2 0 0 0 2 2h12M9 3v14m7 0v4"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="m-4 xl:mx-8 flex flex-col gap-4">
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<ControlBar server_is_recording={recording} />
|
<div class="flex flex-col lg:flex-row gap-4">
|
||||||
<SystemStatsTable stats={system_stats!} />
|
{#if recording}
|
||||||
<ManifestTable entries={entries} current_entry={current_entry} />
|
<Card entry={current_entry} current={true} i={0} server_is_recording={recording}/>
|
||||||
|
{:else}
|
||||||
|
<div class="bg-red-100 border-red-100 drop-shadow p-4 flex flex-col gap-2 border rounded-md flex-1 justify-between">
|
||||||
|
<span class="text-2xl font-bold mb-2 flex flex-row items-center gap-2 text-red-600">
|
||||||
|
<svg class="w-8 h-8 text-red-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path fill-rule="evenodd" d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm11-4a1 1 0 1 0-2 0v5a1 1 0 1 0 2 0V8Zm-1 7a1 1 0 1 0 0 2h.01a1 1 0 1 0 0-2H12Z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
WARNING: Not Running
|
||||||
|
</span>
|
||||||
|
<span>Rayhunter is not currently running and will not detect abnormal behavior!</span>
|
||||||
|
<div class="flex flex-row justify-end mt-2">
|
||||||
|
<RecordingControls {recording} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<SystemStatsTable stats={system_stats!} />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<span class="text-xl">History</span>
|
||||||
|
<ManifestTable entries={entries} server_is_recording={recording} />
|
||||||
|
</div>
|
||||||
|
<DeleteAllButton/>
|
||||||
{:else}
|
{:else}
|
||||||
<p>Loading...</p>
|
<div class="flex flex-col justify-center items-center">
|
||||||
|
<img src="/rayhunter_orca_only.png" class="h-48 animate-spin"/>
|
||||||
|
<p class="text-xl">Loading...</p>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
bin/web/static/rayhunter_icon.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
bin/web/static/rayhunter_orca_only.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
bin/web/static/rayhunter_text.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
6
dist/config.toml.example
vendored
@@ -14,5 +14,9 @@ colorblind_mode = false
|
|||||||
#
|
#
|
||||||
# TP-Link with one-bit display:
|
# TP-Link with one-bit display:
|
||||||
# 0 = invisible mode
|
# 0 = invisible mode
|
||||||
# 1..3 = show emoji for status. :) for running, :( for warnings, no mouth for paused.
|
# 1..3 = show emoji for status. :) for running, ! for warnings, no mouth for paused.
|
||||||
ui_level = 1
|
ui_level = 1
|
||||||
|
|
||||||
|
# 0 = rayhunter does not read button presses
|
||||||
|
# 1 = double-tapping the power button starts/stops recordings
|
||||||
|
key_input_mode = 1
|
||||||
|
|||||||
BIN
doc/Rayhunter_0.3.2.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
@@ -1,139 +1,32 @@
|
|||||||
# Installing from the latest release (Windows)
|
# Installing from the latest release (Windows)
|
||||||
|
|
||||||
1. Install the [Zadig WinUSB driver](https://zadig.akeo.ie/).
|
Windows support in Rayhunter's installer is a work-in-progress. Depending on the device, the installation instructions differ.
|
||||||
2. Download the latest `release.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases).
|
|
||||||
3. Unzip `release.zip`.
|
## TP-Link
|
||||||
4. Save the `install.ps1` file below in the same directory as `install.sh`.
|
|
||||||
5. Run the install script by double clicking on `install.ps1`. A powershell window will launch.
|
1. Connect the device via WiFi or USB Tethering -- you should be able to view the TP-Link admin page on [http://192.168.0.1](http://192.168.0.1).
|
||||||
The device will restart multiple times over the next few minutes.
|
2. Download the latest release (must be at least 0.3.0), and unpack the zipfile.
|
||||||
You will know it is done when you see terminal output that says `checking for rayhunter server...success!`
|
3. Open PowerShell or CMD in that extracted folder, and run the binary appropriate for your operating system: `./installer-windows-x86_64/installer tplink`
|
||||||
|
4. Follow the instructions on the screen, if there are any.
|
||||||
|
|
||||||
|
## Orbic
|
||||||
|
|
||||||
|
1. Connect the device to your computer using the provided USB cable.
|
||||||
|
1. Install the [Zadig WinUSB driver installer](https://zadig.akeo.ie/).
|
||||||
|
1. Open Zadig, click options->show all devices
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Select 'RNDIS (Interface 0)'
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Click 'install driver' and wait for it to finish.
|
||||||
|
2. Download the latest `rayhunter-vX.X.X.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases). The version you download will have numbers instead of X
|
||||||
|
3. Unzip `rayhunter-vX.X.X` .
|
||||||
|
1. Open a powershell terminal by pressing Win+R and typing `powershell` and hitting enter.
|
||||||
|
5. Type `cd ~\Downloads\rayhunter-v<x.x.x>\installer-windows-x86_64` (**Replace <x.x.x> with the rayhunter version you just unzipped**) and hit enter.
|
||||||
|
5. Run the install script: `.\installer.exe orbic` and hit enter.
|
||||||
|
- The device will restart multiple times over the next few minutes.
|
||||||
|
- You will know it is done when you see terminal output that says `checking for rayhunter server...success!`
|
||||||
6. Rayhunter should now be running! You can verify this by following the instructions below to [view the web UI](#usage-viewing-the-web-ui). You should also see a green line flash along the top of top the display on the device.
|
6. Rayhunter should now be running! You can verify this by following the instructions below to [view the web UI](#usage-viewing-the-web-ui). You should also see a green line flash along the top of top the display on the device.
|
||||||
|
|
||||||
# `install.ps1`
|
|
||||||
```powershell
|
|
||||||
|
|
||||||
$global:adb = "./platform-tools-latest-windows/platform-tools/adb.exe"
|
|
||||||
$global:serial = "./serial-windows-x86_64/serial.exe"
|
|
||||||
|
|
||||||
function _adb_push {
|
|
||||||
$proc = start-process -passthru -wait $global:adb -argumentlist "push", $args[0], $args[1]
|
|
||||||
if ($proc.exitcode -ne 0) {
|
|
||||||
write-host "push exited with exit code $($proc.exitcode)"
|
|
||||||
}
|
|
||||||
return $proc.exitcode
|
|
||||||
}
|
|
||||||
|
|
||||||
function _adb_shell {
|
|
||||||
$proc = start-process -passthru -wait $global:adb -argumentlist "shell", $args[0]
|
|
||||||
if ($proc.exitcode -ne 0) {
|
|
||||||
write-host "shell exited with exit code $($proc.exitcode)"
|
|
||||||
}
|
|
||||||
return $proc.exitcode
|
|
||||||
}
|
|
||||||
|
|
||||||
function _wait_for_adb_shell {
|
|
||||||
do {
|
|
||||||
start-sleep -seconds 1
|
|
||||||
} until ((_adb_shell "cat /etc/ver.conf") -eq 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function _wait_for_atfwd_daemon {
|
|
||||||
do {
|
|
||||||
start-sleep -seconds 1
|
|
||||||
} until ((_adb_shell "pgrep atfwd_daemon") -eq 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function force_debug_mode {
|
|
||||||
write-host "Using adb at $($global:adb)"
|
|
||||||
write-host "Forcing a switch into debug mode to enable ADB"
|
|
||||||
&$global:serial "--root" | Out-Host
|
|
||||||
write-host "adb enabled, waiting for reboot..." -nonewline
|
|
||||||
_wait_for_adb_shell
|
|
||||||
write-host " it's alive!"
|
|
||||||
write-host "waiting for atfwd_daemon to start ..." -nonewline
|
|
||||||
_wait_for_atfwd_daemon
|
|
||||||
write-host " done!"
|
|
||||||
}
|
|
||||||
|
|
||||||
function setup_rootshell {
|
|
||||||
_adb_push "rootshell" "/tmp"
|
|
||||||
write-host "cp..."
|
|
||||||
&$global:serial "AT+SYSCMD=cp /tmp/rootshell /bin/rootshell" | Out-Host
|
|
||||||
start-sleep -seconds 1
|
|
||||||
write-host "chown..."
|
|
||||||
&$global:serial "AT+SYSCMD=chown root /bin/rootshell" | Out-Host
|
|
||||||
start-sleep -seconds 1
|
|
||||||
write-host "chmod..."
|
|
||||||
&$global:serial "AT+SYSCMD=chmod 4755 /bin/rootshell" | Out-Host
|
|
||||||
start-sleep -seconds 1
|
|
||||||
_adb_shell '/bin/rootshell -c id'
|
|
||||||
write-host "we have root!"
|
|
||||||
}
|
|
||||||
|
|
||||||
function setup_rayhunter {
|
|
||||||
&$global:serial "AT+SYSCMD=mkdir -p /data/rayhunter" | Out-Host
|
|
||||||
_adb_push "config.toml.example" "/tmp/config.toml"
|
|
||||||
&$global:serial "AT+SYSCMD=mv /tmp/config.toml /data/rayhunter" | Out-Host
|
|
||||||
_adb_push "rayhunter-daemon-orbic/rayhunter-daemon" "/tmp/rayhunter-daemon"
|
|
||||||
&$global:serial "AT+SYSCMD=mv /tmp/rayhunter-daemon /data/rayhunter" | Out-Host
|
|
||||||
_adb_push "scripts/rayhunter_daemon" "/tmp/rayhunter_daemon"
|
|
||||||
&$global:serial "AT+SYSCMD=mv /tmp/rayhunter_daemon /etc/init.d/rayhunter_daemon" | Out-Host
|
|
||||||
_adb_push "scripts/misc-daemon" "/tmp/misc-daemon"
|
|
||||||
&$global:serial "AT+SYSCMD=mv /tmp/misc-daemon /etc/init.d/misc-daemon" | Out-Host
|
|
||||||
|
|
||||||
&$global:serial "AT+SYSCMD=chmod 755 /data/rayhunter/rayhunter-daemon" | Out-Host
|
|
||||||
&$global:serial "AT+SYSCMD=chmod 755 /etc/init.d/rayhunter_daemon" | Out-Host
|
|
||||||
&$global:serial "AT+SYSCMD=chmod 755 /etc/init.d/misc-daemon" | Out-Host
|
|
||||||
|
|
||||||
write-host "waiting for reboot..."
|
|
||||||
&$global:serial "AT+SYSCMD=shutdown -r -t 1 now" | Out-Host
|
|
||||||
do {
|
|
||||||
start-sleep -seconds 1
|
|
||||||
} until ((_adb_shell "true 2> /dev/null") -ne 0)
|
|
||||||
|
|
||||||
_wait_for_adb_shell
|
|
||||||
write-host "done!"
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_rayhunter {
|
|
||||||
$URL = "http://localhost:8080"
|
|
||||||
$fproc = start-process $global:adb -argumentlist "forward", "tcp:8080", "tcp:8080" -wait -passthru
|
|
||||||
if ($fproc.exitcode -ne 0) {
|
|
||||||
write-host "adb forward tcp:8080 tcp:8080 failed with exit code $($proc.exitcode)"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
write-host "checking for rayhunter server..." -nonewline
|
|
||||||
$seconds = 0
|
|
||||||
do {
|
|
||||||
$resp = invoke-webrequest -uri $URL
|
|
||||||
if ($resp.statuscode -eq 200) {
|
|
||||||
write-host "success!"
|
|
||||||
write-host "you can access rayhunter at $($URL)"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
start-sleep 1
|
|
||||||
$seconds = $seconds + 1
|
|
||||||
} until ($seconds -eq 30)
|
|
||||||
write-host "timeout reached! failed to reach rayhunter url $($URL), something went wrong :("
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_android_tools {
|
|
||||||
write-host "adb not found, downloading local copy"
|
|
||||||
invoke-webrequest "https://dl.google.com/android/repository/platform-tools-latest-windows.zip" -outfile ./platform-tools-latest-windows.zip
|
|
||||||
expand-archive -force -path "platform-tools-latest-windows.zip"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (test-path -path $global:serial)) {
|
|
||||||
write-error "can't find serial, aborting"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (test-path -path $global:adb)) {
|
|
||||||
get_android_tools
|
|
||||||
}
|
|
||||||
|
|
||||||
force_debug_mode
|
|
||||||
setup_rootshell
|
|
||||||
setup_rayhunter
|
|
||||||
test_rayhunter
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
Make sure you've got one of Rayhunter's [supported devices](./supported-devices.md). These instructions have only been tested on macOS and Ubuntu 24.04. If they fail, you will need to [install Rayhunter from source](./installing-from-source.md).
|
Make sure you've got one of Rayhunter's [supported devices](./supported-devices.md). These instructions have only been tested on macOS and Ubuntu 24.04. If they fail, you will need to [install Rayhunter from source](./installing-from-source.md).
|
||||||
|
|
||||||
1. Download the latest `release.tar` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases)
|
1. Download the latest `rayhunter-vX.X.X.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases)
|
||||||
2. Decompress the `release.tar` archive. Open the terminal and navigate to the folder
|
2. Decompress the `rayhunter-vX.X.X.zip` archive. Open the terminal and navigate to the folder. (Be sure to replace X.X.X with the correct version number!)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir ~/Downloads/release
|
unzip ~/Downloads/rayhunter-vX.X.X.zip
|
||||||
tar -xvf ~/Downloads/release.tar -C ~/Downloads/release
|
cd ~/Downloads/rayhunter-vX.X.X
|
||||||
cd ~/Downloads/release
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Turn on your device by holding the power button on the front.
|
3. Turn on your device by holding the power button on the front.
|
||||||
@@ -18,17 +17,32 @@ Make sure you've got one of Rayhunter's [supported devices](./supported-devices.
|
|||||||
|
|
||||||
4. Run the install script for your operating system:
|
4. Run the install script for your operating system:
|
||||||
|
|
||||||
|
First, enter the correct subfolder for your operating system:
|
||||||
|
- for Ubuntu on x64 arhitecture: `cd installer-ubuntu-24`
|
||||||
|
- for Ubuntu on ARM64 arhitecture: `cd installer-ubuntu-24-aarch64`
|
||||||
|
- for MacOS on Intel (old macbooks) architecture: `cd installer-macos-intel`
|
||||||
|
- for MacOS on ARM (M1/M2 etc.) achitecture: `cd installer-macos-arm`
|
||||||
|
- for Windows: `cd installer-windows-x86_64`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./install orbic
|
# On MacOS, you must first remove the quarantine bit
|
||||||
# or: ./install tplink
|
xattr -d com.apple.quarantine install
|
||||||
|
```
|
||||||
|
Then run the installer:
|
||||||
|
```bash
|
||||||
|
./installer orbic
|
||||||
|
# or: ./installer tplink
|
||||||
```
|
```
|
||||||
|
|
||||||
The device will restart multiple times over the next few minutes.
|
The device will restart multiple times over the next few minutes.
|
||||||
|
|
||||||
You will know it is done when you see terminal output that says `Testing rayhunter... done`
|
You will know it is done when you see terminal output that says `Testing Rayhunter... done`
|
||||||
|
|
||||||
5. Rayhunter should now be running! You can verify this by [viewing Rayhunter's web UI](./using-rayhunter). You should also see a green line flash along the top of top the display on the device.
|
5. Rayhunter should now be running! You can verify this by [viewing Rayhunter's web UI](./using-rayhunter). You should also see a green line flash along the top of top the display on the device.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
* On macOS if you encounter an error that says "No Orbic device found," it may because you the "Allow accessories to connect" security setting set to "Ask for approval." You may need to temporarily change it to "Always" for the script to run. Make sure to change it back to a more secure setting when you're done.
|
* On MacOS if you encounter an error that says "No Orbic device found," it may because you have the "Allow accessories to connect" security setting set to "Ask for approval." You may need to temporarily change it to "Always" for the script to run. Make sure to change it back to a more secure setting when you're done.
|
||||||
|
|
||||||
|
./installer --help
|
||||||
|
./installer util --help
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ rustup target add x86_64-pc-windows-gnu
|
|||||||
Now you can root your device and install Rayhunter by running:
|
Now you can root your device and install Rayhunter by running:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo build --bin rayhunter-daemon --target armv7-unknown-linux-musleabihf --release --no-default-features --features orbic
|
cargo build --bin rayhunter-daemon --target armv7-unknown-linux-musleabihf --profile firmware --no-default-features --features orbic
|
||||||
|
|
||||||
cargo build --bin rootshell --target armv7-unknown-linux-musleabihf --release
|
cargo build --bin rootshell --target armv7-unknown-linux-musleabihf --profile firmware
|
||||||
|
|
||||||
cargo run --bin installer orbic
|
cargo run --bin installer orbic
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
# TP-Link M7350
|
# TP-Link M7350
|
||||||
|
|
||||||
The TP-Link M7350 is supported by Rayhunter as of 0.2.9. It supports many more frequency bands than Orbic and therefore works in Europe.
|
The TP-Link M7350 is supported by Rayhunter from 0.3.0 release. TP-Link M7350 supports many more frequency bands than Orbic and therefore works in Europe and also in some Asian and African countries.
|
||||||
|
|
||||||
You can get it from:
|
## Hardware versions
|
||||||
|
|
||||||
|
The TP-Link comes in many different *hardware versions*. Support for installation varies:
|
||||||
|
|
||||||
|
* `1.0`, `2.0`: **Not suported**, probably impossible to obtain anymore (even second-hand), however there is one report that installation is possible on `1.0` (but no reports if it is working or not)
|
||||||
|
* `3.0`, `3.2`, `5.0`, `5.2`, `7.0`, `8.0`: **Tested, no known issues since 0.3.0.**
|
||||||
|
* `6.2`: **One user reported it is working**
|
||||||
|
* `4.0`: **Manual firmware downgrade required** ([issue](https://github.com/EFForg/rayhunter/issues/332))
|
||||||
|
* `9.0`: **Working since 0.3.2.**
|
||||||
|
|
||||||
|
TP-Link versions newer than `3.0` have cyan packaging and a color display. Version `3.0` has a one-bit display and white packaging.
|
||||||
|
|
||||||
|
You can find the exact hardware version of each device under the battery or next to the barcode on the outer packaging, for example `V3.0` or `V5.2`.
|
||||||
|
|
||||||
|
When filing bug reports, particularly with the installer, please always specify the exact hardware version.
|
||||||
|
|
||||||
|
You can get your TP-Link M7350 from:
|
||||||
|
|
||||||
* First check for used offers on Ebay or equivalent, sometimes it's much cheaper there.
|
* First check for used offers on Ebay or equivalent, sometimes it's much cheaper there.
|
||||||
* [Geizhals price comparison](https://geizhals.eu/?fs=tp-link+m7350)
|
* [Geizhals price comparison](https://geizhals.eu/?fs=tp-link+m7350)
|
||||||
@@ -10,9 +26,9 @@ You can get it from:
|
|||||||
|
|
||||||
## Installation & Usage
|
## Installation & Usage
|
||||||
|
|
||||||
Follow the [release installation guide](./installing-from-release.md). Substitute `./installer orbic` for `./installer tplink` in other documentation. The rayhunter UI will be available at [http://192.168.0.1:8080](http://192.168.0.1:8080).
|
Follow the [release installation guide](./installing-from-release.md). Substitute `./installer orbic` for `./installer tplink` in other documentation. The Rayhunter UI will be available at [http://192.168.0.1:8080](http://192.168.0.1:8080).
|
||||||
|
|
||||||
Unlike on Orbic, the installer will not enable ADB. Instead, you can do this to obtain a root shell:
|
Unlike on Orbic, the installer will not enable ADB. Instead, you can obtain a root shell with the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./installer util tplink-start-telnet
|
./installer util tplink-start-telnet
|
||||||
@@ -21,34 +37,40 @@ telnet 192.168.0.1
|
|||||||
|
|
||||||
## Display states
|
## Display states
|
||||||
|
|
||||||
If your device has a color display, Rayhunter will show the same
|
If your device has a color display, Rayhunter will show the same red/green/white line at the top of the display as it does on Orbic, each color meaning "warning"/"recording"/"paused" respectively. See [Using Rayhunter](./using-rayhunter.md).
|
||||||
red/green/white line at the top of the display as it does on Orbic, each color
|
|
||||||
meaning "warning"/"recording"/"paused" respectively. See [Using Rayhunter](./using-rayhunter.md).
|
|
||||||
|
|
||||||
If your device has a one-bit (black-and-white) display, Rayhunter will instead
|
If your device has a one-bit (black-and-white) display, Rayhunter will instead show an emoji to indicate status:
|
||||||
show an emoji to indicate status:
|
|
||||||
|
|
||||||
* `!` means "warning (potential IMSI catcher)"
|
* `!` means "warning (potential IMSI catcher)"
|
||||||
* `:)` (smiling) means "recording"
|
* `:)` (smiling) means "recording"
|
||||||
* `:` (face with no mouth) means "paused"
|
* `:` (face with no mouth) means "paused"
|
||||||
|
|
||||||
## Hardware versions
|
## Configuration
|
||||||
|
|
||||||
The TP-Link comes in many different *hardware versions*. Support for installation varies:
|
Displaying status can be changed in the configuration (`config.toml`) file, where UI level (`ui_level` variable) could be changed to:
|
||||||
|
- `0`: invisible mode, no indicator that Rayhunter is running
|
||||||
|
- `1`: subtle mode, display a green line at the top of the screen when Rayhunter is running
|
||||||
|
- `2`: demo mode, display a fun Orca GIF
|
||||||
|
- `3`: display the EFF logo
|
||||||
|
|
||||||
* `1.0-2.0`: Not tested, probably impossible to obtain anymore (even second-hand)
|
You can also change `colorblind_mode` (default is `false`) to `true`. In that case there will be blue line instead of green line.
|
||||||
* `3.0`, `3.2`, `5.0`, `5.2`, `7.0`, `8.0`: Tested, no issues.
|
|
||||||
* `9.0`: Recording might be broken, could be fixed if there is demand.
|
|
||||||
|
|
||||||
TP-Link versions newer than `3.0` have cyan packaging and a color display.
|
You can change the `port` (default is `8080`) where Rayhunter is listening for incoming connections and more advanced users can change the variables `qmdl_store_path` and `debug_mode`. However, change those variables only if you know what you are doing.
|
||||||
Version `3.0` has a one-bit display and white packaging.
|
|
||||||
|
|
||||||
You can find the exact hardware version of each device under the battery or
|
## Power-saving mode/sleep
|
||||||
next to the barcode on the outer packaging, for example `V3.0` or `V5.2`.
|
|
||||||
|
|
||||||
When filing bug reports, particularly with the installer, please always
|
By default the device will go to sleep after N minutes of no devices being connected. In that mode it will also turn off connections to cell phone towers.
|
||||||
specify the exact hardware version.
|
In order for Rayhunter to record continuously, you have to turn off this sleep mode in TP-Link's admin panel (go to **Advanced** - **Power Saving**) or keep e.g. your phone connectd on the TP-Link's WiFi.
|
||||||
|
|
||||||
|
## Port triggers
|
||||||
|
|
||||||
|
On hardware revisions starting with v4.0, the installer will modify settings to
|
||||||
|
add two port triggers. You can look at `Settings > NAT Settings > Port
|
||||||
|
Triggers` in TP-Link's admin UI to see them.
|
||||||
|
|
||||||
|
1. One port trigger "rayhunter-root" to launch the telnet shell. This is only needed for installation, and can be removed after upgrade. You can reinstall it using `./installer util tplink-start-telnet`.
|
||||||
|
2. One port trigger "rayhunter-daemon" to auto-start rayhunter on boot. If you remove this, rayhunter will have to be started manually from shell.
|
||||||
|
|
||||||
## Other links
|
## Other links
|
||||||
|
|
||||||
For more information on the device and instructions on how to install Rayhunter without an installer, see [rayhunter-tplink-m7350](https://github.com/m0veax/rayhunter-tplink-m7350/)
|
For more information on the device and instructions on how to install Rayhunter without an installer (i.e. manually), please see [rayhunter-tplink-m7350](https://github.com/m0veax/rayhunter-tplink-m7350/)
|
||||||
|
|||||||
@@ -16,4 +16,9 @@ Your device is now Rayhunter-free, and should no longer be in a rooted ADB-enabl
|
|||||||
|
|
||||||
## TPLink
|
## TPLink
|
||||||
|
|
||||||
TODO
|
1. Run `./installer util tplink-start-telnet`
|
||||||
|
2. Telnet into the device `telnet 192.168.0.1`
|
||||||
|
3. `rm /data/rayhunter /etc/init.d/rayhunter_daemon`
|
||||||
|
4. `update-rc.d rayhunter_daemon remove`
|
||||||
|
5. (hardware revision v4.0+ only) In `Settings > NAT Settings > Port Triggers` in TP-Link's admin UI, remove any leftover port triggers.
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,30 @@
|
|||||||
|
|
||||||
Once installed, Rayhunter will run automatically whenever your device is running. You'll see a green line on top of the device's display to indicate that it's running and recording. [The line will turn red](#red) once a potential IMSI catcher has been found, until the device is rebooted or a new recording is started through the web UI.
|
Once installed, Rayhunter will run automatically whenever your device is running. You'll see a green line on top of the device's display to indicate that it's running and recording. [The line will turn red](#red) once a potential IMSI catcher has been found, until the device is rebooted or a new recording is started through the web UI.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
It also serves a web UI that provides some basic controls, such as being able to start/stop recordings, download captures, delete captures, and view heuristic analyses of captures.
|
It also serves a web UI that provides some basic controls, such as being able to start/stop recordings, download captures, delete captures, and view heuristic analyses of captures.
|
||||||
|
|
||||||
|
## The web UI
|
||||||
|
|
||||||
You can access this UI in one of two ways:
|
You can access this UI in one of two ways:
|
||||||
|
|
||||||
* **Connect over wifi:** Connect your phone/laptop to your device's wifi
|
* **Connect over WiFi:** Connect your phone/laptop to your device's WiFi
|
||||||
network and visit [http://192.168.1.1:8080](http://192.168.1.1:8080) (orbic)
|
network and visit [http://192.168.1.1:8080](http://192.168.1.1:8080) (orbic)
|
||||||
or [http://192.168.0.1:8080](http://192.168.0.1:8080) (tplink).
|
or [http://192.168.0.1:8080](http://192.168.0.1:8080) (tplink).
|
||||||
|
|
||||||
Click past your browser warning you about the connection not being secure, Rayhunter doesn't have HTTPS yet.
|
Click past your browser warning you about the connection not being secure, Rayhunter doesn't have HTTPS yet.
|
||||||
|
|
||||||
On the Orbic, you can find the wifi network password by going to the Orbic's menu > 2.4 GHz WIFI Info > Enter > find the 8-character password next to the lock 🔒 icon.
|
On the **Orbic**, you can find the WiFi network password by going to the Orbic's menu > 2.4 GHz WIFI Info > Enter > find the 8-character password next to the lock 🔒 icon.
|
||||||
* **Connect over USB (orbic):** Connect your device to your laptop via USB. Run `adb forward tcp:8080 tcp:8080`, then visit [http://localhost:8080](http://localhost:8080).
|
On the **TP-Link**, you can find the WiFi network password by going to the TP-Link's menu > Advanced > Wireless > Basic Settings.
|
||||||
|
|
||||||
|
* **Connect over USB (Orbic):** Connect your device to your laptop via USB. Run `adb forward tcp:8080 tcp:8080`, then visit [http://localhost:8080](http://localhost:8080).
|
||||||
* For this you will need to install the Android Debug Bridge (ADB) on your computer, you can copy the version that was downloaded inside the `releases/platform-tools/` folder to somewhere else in your path or you can install it manually.
|
* For this you will need to install the Android Debug Bridge (ADB) on your computer, you can copy the version that was downloaded inside the `releases/platform-tools/` folder to somewhere else in your path or you can install it manually.
|
||||||
* You can find instructions for doing so on your platform [here](https://www.xda-developers.com/install-adb-windows-macos-linux/#how-to-set-up-adb-on-your-computer), (don't worry about instructions for installing it on a phone/device yet).
|
* You can find instructions for doing so on your platform [here](https://www.xda-developers.com/install-adb-windows-macos-linux/#how-to-set-up-adb-on-your-computer), (don't worry about instructions for installing it on a phone/device yet).
|
||||||
* On macOS, the easiest way to install ADB is with Homebrew: First [install Homebrew](https://brew.sh/), then run `brew install android-platform-tools`.
|
* On MacOS, the easiest way to install ADB is with Homebrew: First [install Homebrew](https://brew.sh/), then run `brew install android-platform-tools`.
|
||||||
* **Connect over USB (tplink):** Plug in the TP-Link and use USB tethering to establish a network connection. ADB support can be enabled on the device, but the installer won't do it for you.
|
|
||||||
|
* **Connect over USB (TP-Link):** Plug in the TP-Link and use USB tethering to establish a network connection. ADB support can be enabled on the device, but the installer won't do it for you.
|
||||||
|
|
||||||
|
## Key shortcuts
|
||||||
|
|
||||||
|
As of 0.3.3, you can start a new recording by double-tapping the power button. Any current recording will be stopped and a new recording will be started, resetting the red line as well.
|
||||||
|
|||||||
BIN
doc/zadig.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
doc/zadig2.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
doc/zadig3.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
@@ -1,10 +1,11 @@
|
|||||||
#!/bin/bash -e
|
#!/bin/bash -e
|
||||||
pushd bin/web
|
cd bin/web
|
||||||
npm run build
|
npm run build
|
||||||
popd
|
cd ..
|
||||||
#docker build -t rayhunter-devenv -f tools/devenv.dockerfile .
|
docker build -t rayhunter-devenv -f tools/devenv.dockerfile .
|
||||||
docker run --user $UID:$GID -v ./:/workdir -w /workdir -it rayhunter-devenv sh -c 'cargo build --release --target="armv7-unknown-linux-gnueabihf"'
|
echo ' build!'
|
||||||
|
docker run --user $UID:$GID -v ./:/workdir -w /workdir -it rayhunter-devenv sh -c 'cargo build --release --target="armv7-unknown-linux-musleabihf"'
|
||||||
adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon stop"'
|
adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon stop"'
|
||||||
adb push target/armv7-unknown-linux-gnueabihf/release/rayhunter-daemon /data/rayhunter/rayhunter-daemon
|
adb push target/armv7-unknown-linux-musleabihf/release/rayhunter-daemon /data/rayhunter/rayhunter-daemon
|
||||||
echo "rebooting the device..."
|
echo "rebooting the device..."
|
||||||
adb shell '/bin/rootshell -c "reboot"'
|
adb shell '/bin/rootshell -c "reboot"'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "installer"
|
name = "installer"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -8,6 +8,7 @@ anyhow = "1.0.98"
|
|||||||
axum = "0.8.3"
|
axum = "0.8.3"
|
||||||
bytes = "1.10.1"
|
bytes = "1.10.1"
|
||||||
clap = { version = "4.5.37", features = ["derive"] }
|
clap = { version = "4.5.37", features = ["derive"] }
|
||||||
|
env_logger = "0.11.8"
|
||||||
hyper = "1.6.0"
|
hyper = "1.6.0"
|
||||||
hyper-util = "0.1.11"
|
hyper-util = "0.1.11"
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
@@ -20,13 +21,13 @@ tokio-retry2 = "0.5.7"
|
|||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies.adb_client]
|
[target.'cfg(target_os = "linux")'.dependencies.adb_client]
|
||||||
git = "https://github.com/gaykitty/adb_client.git"
|
git = "https://github.com/EFForg/adb_client.git"
|
||||||
rev = "1fb0f4f5cbcc95bbbb98db4ee2f1e53a1005aa81"
|
rev = "e511662394e4fa32865c154c40f81a3d846f700c"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["trans-nusb"]
|
features = ["trans-nusb"]
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies.adb_client]
|
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies.adb_client]
|
||||||
git = "https://github.com/gaykitty/adb_client.git"
|
git = "https://github.com/EFForg/adb_client.git"
|
||||||
rev = "1fb0f4f5cbcc95bbbb98db4ee2f1e53a1005aa81"
|
rev = "e511662394e4fa32865c154c40f81a3d846f700c"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["trans-libusb"]
|
features = ["trans-libusb"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ fn main() {
|
|||||||
println!("cargo::rerun-if-env-changed=NO_FIRMWARE_BIN");
|
println!("cargo::rerun-if-env-changed=NO_FIRMWARE_BIN");
|
||||||
let include_dir = Path::new(concat!(
|
let include_dir = Path::new(concat!(
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
"/../target/armv7-unknown-linux-musleabihf/release/"
|
"/../target/armv7-unknown-linux-musleabihf/firmware/"
|
||||||
));
|
));
|
||||||
set_binary_var(&include_dir, "FILE_ROOTSHELL", "rootshell");
|
set_binary_var(&include_dir, "FILE_ROOTSHELL", "rootshell");
|
||||||
set_binary_var(
|
set_binary_var(
|
||||||
|
|||||||
142
installer/install.ps1
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
$global:adb = ".\platform-tools-latest-windows\platform-tools\adb.exe"
|
||||||
|
$global:serial = ".\installer-windows-x86_64\installer.exe"
|
||||||
|
|
||||||
|
function _adb_push {
|
||||||
|
& $global:adb -d push @args *> $null
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
return $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
function _adb_shell {
|
||||||
|
& $global:adb -d shell @args *> $null
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
return $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
function _wait_for_adb_shell {
|
||||||
|
do {
|
||||||
|
start-sleep -seconds 1
|
||||||
|
$success = _adb_shell "uname -a"
|
||||||
|
} until ($success -eq 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _wait_for_atfwd_daemon {
|
||||||
|
do {
|
||||||
|
start-sleep -seconds 1
|
||||||
|
$success = _adb_shell "pgrep atfwd_daemon"
|
||||||
|
} until ($success -eq 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function force_debug_mode {
|
||||||
|
write-host "Using adb at $($global:adb)"
|
||||||
|
write-host "Forcing a switch into debug mode to enable ADB"
|
||||||
|
_serial "--root" | Out-Host
|
||||||
|
write-host "adb enabled, waiting for reboot..." -nonewline
|
||||||
|
_wait_for_adb_shell
|
||||||
|
write-host " it's alive!"
|
||||||
|
write-host "waiting for atfwd_daemon to start ..." -nonewline
|
||||||
|
_wait_for_atfwd_daemon
|
||||||
|
write-host " done!"
|
||||||
|
}
|
||||||
|
function _serial {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $false, ValueFromRemainingArguments = $true)]
|
||||||
|
[string[]]$Args
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build the full argument list
|
||||||
|
$allArgs = @("util", "serial") + $Args
|
||||||
|
|
||||||
|
# Call the serial executable
|
||||||
|
& $global:serial @allArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_rootshell {
|
||||||
|
write-host "setting up rootshell"
|
||||||
|
_adb_push "rootshell" "/tmp" | Out-null
|
||||||
|
write-host "cp..."
|
||||||
|
_serial "AT+SYSCMD=cp /tmp/rootshell /bin/rootshell" | Out-Host
|
||||||
|
start-sleep -seconds 1
|
||||||
|
write-host "chown..."
|
||||||
|
_serial "AT+SYSCMD=chown root /bin/rootshell" | Out-Host
|
||||||
|
start-sleep -seconds 1
|
||||||
|
write-host "chmod..."
|
||||||
|
_serial "AT+SYSCMD=chmod 4755 /bin/rootshell" | Out-Host
|
||||||
|
start-sleep -seconds 1
|
||||||
|
_adb_shell '/bin/rootshell -c id' | Out-null
|
||||||
|
write-host "we have root!"
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_rayhunter {
|
||||||
|
write-host "installing rayhunter..."
|
||||||
|
_serial "AT+SYSCMD=mkdir -p /data/rayhunter" | Out-Host
|
||||||
|
_adb_push "config.toml.example" "/tmp/config.toml" | Out-Null
|
||||||
|
_serial "AT+SYSCMD=mv /tmp/config.toml /data/rayhunter" | Out-Host
|
||||||
|
_adb_push "rayhunter-daemon-orbic/rayhunter-daemon" "/tmp/rayhunter-daemon" | Out-Null
|
||||||
|
_serial "AT+SYSCMD=mv /tmp/rayhunter-daemon /data/rayhunter" | Out-Host
|
||||||
|
_adb_push "scripts/rayhunter_daemon" "/tmp/rayhunter_daemon" | Out-Null
|
||||||
|
_serial "AT+SYSCMD=mv /tmp/rayhunter_daemon /etc/init.d/rayhunter_daemon" | Out-Host
|
||||||
|
_adb_push "scripts/misc-daemon" "/tmp/misc-daemon" | Out-Null
|
||||||
|
_serial "AT+SYSCMD=mv /tmp/misc-daemon /etc/init.d/misc-daemon" | Out-Host
|
||||||
|
|
||||||
|
_serial "AT+SYSCMD=chmod 755 /data/rayhunter/rayhunter-daemon" | Out-Host
|
||||||
|
_serial "AT+SYSCMD=chmod 755 /etc/init.d/rayhunter_daemon" | Out-Host
|
||||||
|
_serial "AT+SYSCMD=chmod 755 /etc/init.d/misc-daemon" | Out-Host
|
||||||
|
|
||||||
|
write-host "waiting for reboot..."
|
||||||
|
_serial "AT+SYSCMD=shutdown -r -t 1 now" | Out-Host
|
||||||
|
do {
|
||||||
|
start-sleep -seconds 1
|
||||||
|
} until ((_adb_shell "true 2> /dev/null") -ne 0)
|
||||||
|
|
||||||
|
_wait_for_adb_shell
|
||||||
|
write-host "done!"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_rayhunter {
|
||||||
|
$URL = "http://localhost:8080/index.html"
|
||||||
|
& $global:adb -d forward tcp:8080 tcp:8080
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
if ($exitCode -ne 0) {
|
||||||
|
write-host "adb forward tcp:8080 tcp:8080 failed with exit code $($exitCode)"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
write-host "checking for rayhunter server..." -nonewline
|
||||||
|
$seconds = 0
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
$resp = invoke-webrequest -uri $URL
|
||||||
|
} catch {
|
||||||
|
# Fail silently
|
||||||
|
$resp = $null
|
||||||
|
}
|
||||||
|
if ($resp.statuscode -eq 200) {
|
||||||
|
write-host "success!"
|
||||||
|
write-host "you can access rayhunter at $($URL)"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start-sleep 1
|
||||||
|
$seconds = $seconds + 1
|
||||||
|
} until ($seconds -eq 30)
|
||||||
|
write-host "timeout reached! failed to reach rayhunter url $($URL), something went wrong :("
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_android_tools {
|
||||||
|
write-host "adb not found, downloading local copy"
|
||||||
|
invoke-webrequest "https://dl.google.com/android/repository/platform-tools-latest-windows.zip" -outfile ./platform-tools-latest-windows.zip
|
||||||
|
expand-archive -force -path "platform-tools-latest-windows.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (test-path -path $global:serial)) {
|
||||||
|
write-error "can't find serial, aborting"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (test-path -path $global:adb)) {
|
||||||
|
get_android_tools
|
||||||
|
}
|
||||||
|
|
||||||
|
force_debug_mode
|
||||||
|
setup_rootshell
|
||||||
|
setup_rayhunter
|
||||||
|
test_rayhunter
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::{Context, Error, bail};
|
use anyhow::{Context, Error, bail};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
use env_logger::Env;
|
||||||
|
|
||||||
mod orbic;
|
mod orbic;
|
||||||
mod tplink;
|
mod tplink;
|
||||||
@@ -34,6 +35,17 @@ struct InstallTpLink {
|
|||||||
/// IP address for TP-Link admin interface, if custom.
|
/// IP address for TP-Link admin interface, if custom.
|
||||||
#[arg(long, default_value = "192.168.0.1")]
|
#[arg(long, default_value = "192.168.0.1")]
|
||||||
admin_ip: String,
|
admin_ip: String,
|
||||||
|
|
||||||
|
/// For advanced users: Specify the path of the SD card to be mounted explicitly.
|
||||||
|
///
|
||||||
|
/// The default (empty string) is to use whichever sdcard path the device would use natively to
|
||||||
|
/// mount storage on. On most TP-Link this is /media/card, but on hardware versions 9+ this is
|
||||||
|
/// /media/sdcard
|
||||||
|
///
|
||||||
|
/// Only override this when the installer does not work on your hardware version, as otherwise
|
||||||
|
/// your custom path may conflict with the builtin storage functionality.
|
||||||
|
#[arg(long, default_value = "")]
|
||||||
|
sdcard_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@@ -49,6 +61,8 @@ struct Util {
|
|||||||
enum UtilSubCommand {
|
enum UtilSubCommand {
|
||||||
/// Send a serial command to the Orbic.
|
/// Send a serial command to the Orbic.
|
||||||
Serial(Serial),
|
Serial(Serial),
|
||||||
|
/// Start an ADB shell
|
||||||
|
Shell(Shell),
|
||||||
/// Root the tplink and launch telnetd.
|
/// Root the tplink and launch telnetd.
|
||||||
TplinkStartTelnet(TplinkStartTelnet),
|
TplinkStartTelnet(TplinkStartTelnet),
|
||||||
}
|
}
|
||||||
@@ -67,7 +81,11 @@ struct Serial {
|
|||||||
command: Vec<String>,
|
command: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
struct Shell {}
|
||||||
|
|
||||||
async fn run() -> Result<(), Error> {
|
async fn run() -> Result<(), Error> {
|
||||||
|
env_logger::Builder::from_env(Env::default().default_filter_or("off")).init();
|
||||||
let Args { command } = Args::parse();
|
let Args { command } = Args::parse();
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
@@ -92,6 +110,7 @@ async fn run() -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
UtilSubCommand::Shell(_) => orbic::shell().await.context("\nFailed to open shell on Orbic RC400L")?,
|
||||||
UtilSubCommand::TplinkStartTelnet(options) => {
|
UtilSubCommand::TplinkStartTelnet(options) => {
|
||||||
tplink::start_telnet(&options.admin_ip).await?;
|
tplink::start_telnet(&options.admin_ip).await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,16 +22,24 @@ const ORBIC_BUSY: &str = r#"The Orbic is plugged in but is being used by another
|
|||||||
Please close any program that might be using your USB devices.
|
Please close any program that might be using your USB devices.
|
||||||
If you have adb installed you may need to kill the adb daemon"#;
|
If you have adb installed you may need to kill the adb daemon"#;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||||
const ORBIC_BUSY_MAC: &str = r#"Permission denied.
|
const ORBIC_BUSY_MAC: &str = r#"Permission denied.
|
||||||
|
|
||||||
On macOS this might be caused by another program using the Orbic.
|
On macOS or windows this might be caused by another program using the Orbic.
|
||||||
Please close any program that might be using your Orbic.
|
Please close any program that might be using your Orbic.
|
||||||
If you have adb installed you may need to kill the adb daemon"#;
|
If you have adb installed you may need to kill the adb daemon"#;
|
||||||
|
|
||||||
const VENDOR_ID: u16 = 0x05c6;
|
const VENDOR_ID: u16 = 0x05c6;
|
||||||
const PRODUCT_ID: u16 = 0xf601;
|
const PRODUCT_ID: u16 = 0xf601;
|
||||||
|
|
||||||
|
const INTERFACE: u8 = 1;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
const RNDIS_INTERFACE: u8 = 0;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
const RNDIS_INTERFACE: u8 = 1;
|
||||||
|
|
||||||
macro_rules! echo {
|
macro_rules! echo {
|
||||||
($($arg:tt)*) => {
|
($($arg:tt)*) => {
|
||||||
print!($($arg)*);
|
print!($($arg)*);
|
||||||
@@ -41,12 +49,11 @@ macro_rules! echo {
|
|||||||
|
|
||||||
pub async fn install() -> Result<()> {
|
pub async fn install() -> Result<()> {
|
||||||
let mut adb_device = force_debug_mode().await?;
|
let mut adb_device = force_debug_mode().await?;
|
||||||
let serial_interface = open_orbic()?.ok_or_else(|| anyhow!(ORBIC_NOT_FOUND))?;
|
|
||||||
echo!("Installing rootshell... ");
|
echo!("Installing rootshell... ");
|
||||||
setup_rootshell(&serial_interface, &mut adb_device).await?;
|
setup_rootshell(&mut adb_device).await?;
|
||||||
println!("done");
|
println!("done");
|
||||||
echo!("Installing rayhunter... ");
|
echo!("Installing rayhunter... ");
|
||||||
let mut adb_device = setup_rayhunter(&serial_interface, adb_device).await?;
|
let mut adb_device = setup_rayhunter(adb_device).await?;
|
||||||
println!("done");
|
println!("done");
|
||||||
echo!("Testing rayhunter... ");
|
echo!("Testing rayhunter... ");
|
||||||
test_rayhunter(&mut adb_device).await?;
|
test_rayhunter(&mut adb_device).await?;
|
||||||
@@ -54,11 +61,19 @@ pub async fn install() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn shell() -> Result<()> {
|
||||||
|
println!("opening shell");
|
||||||
|
let mut adb_device = get_adb().await?;
|
||||||
|
adb_device.shell(&mut std::io::stdin(), Box::new(std::io::stdout()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn force_debug_mode() -> Result<ADBUSBDevice> {
|
async fn force_debug_mode() -> Result<ADBUSBDevice> {
|
||||||
println!("Forcing a switch into the debug mode to enable ADB");
|
println!("Forcing a switch into the debug mode to enable ADB");
|
||||||
enable_command_mode()?;
|
enable_command_mode()?;
|
||||||
echo!("ADB enabled, waiting for reboot... ");
|
echo!("ADB enabled, waiting for reboot... ");
|
||||||
let mut adb_device = get_adb().await?;
|
let mut adb_device = get_adb().await?;
|
||||||
|
adb_setup_serial(&mut adb_device).await?;
|
||||||
println!("it's alive!");
|
println!("it's alive!");
|
||||||
echo!("Waiting for atfwd_daemon to startup... ");
|
echo!("Waiting for atfwd_daemon to startup... ");
|
||||||
adb_command(&mut adb_device, &["pgrep", "atfwd_daemon"])?;
|
adb_command(&mut adb_device, &["pgrep", "atfwd_daemon"])?;
|
||||||
@@ -66,23 +81,14 @@ async fn force_debug_mode() -> Result<ADBUSBDevice> {
|
|||||||
Ok(adb_device)
|
Ok(adb_device)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_rootshell(
|
async fn setup_rootshell(adb_device: &mut ADBUSBDevice) -> Result<()> {
|
||||||
serial_interface: &Interface,
|
|
||||||
adb_device: &mut ADBUSBDevice,
|
|
||||||
) -> Result<()> {
|
|
||||||
let rootshell_bin = include_bytes!(env!("FILE_ROOTSHELL"));
|
let rootshell_bin = include_bytes!(env!("FILE_ROOTSHELL"));
|
||||||
|
|
||||||
install_file(
|
install_file(adb_device, "/bin/rootshell", rootshell_bin).await?;
|
||||||
serial_interface,
|
|
||||||
adb_device,
|
|
||||||
"/bin/rootshell",
|
|
||||||
rootshell_bin,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
at_syscmd(serial_interface, "chown root /bin/rootshell").await?;
|
adb_at_syscmd(adb_device, "chown root /bin/rootshell").await?;
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
at_syscmd(serial_interface, "chmod 4755 /bin/rootshell").await?;
|
adb_at_syscmd(adb_device, "chmod 4755 /bin/rootshell").await?;
|
||||||
let output = adb_command(adb_device, &["/bin/rootshell", "-c", "id"])?;
|
let output = adb_command(adb_device, &["/bin/rootshell", "-c", "id"])?;
|
||||||
if !output.contains("uid=0") {
|
if !output.contains("uid=0") {
|
||||||
bail!("rootshell is not giving us root.");
|
bail!("rootshell is not giving us root.");
|
||||||
@@ -90,46 +96,39 @@ async fn setup_rootshell(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_rayhunter(
|
async fn setup_rayhunter(mut adb_device: ADBUSBDevice) -> Result<ADBUSBDevice> {
|
||||||
serial_interface: &Interface,
|
|
||||||
mut adb_device: ADBUSBDevice,
|
|
||||||
) -> Result<ADBUSBDevice> {
|
|
||||||
let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON_ORBIC"));
|
let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON_ORBIC"));
|
||||||
|
|
||||||
at_syscmd(serial_interface, "mkdir -p /data/rayhunter").await?;
|
adb_at_syscmd(&mut adb_device, "mkdir -p /data/rayhunter").await?;
|
||||||
install_file(
|
install_file(
|
||||||
serial_interface,
|
|
||||||
&mut adb_device,
|
&mut adb_device,
|
||||||
"/data/rayhunter/rayhunter-daemon",
|
"/data/rayhunter/rayhunter-daemon",
|
||||||
rayhunter_daemon_bin,
|
rayhunter_daemon_bin,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
install_file(
|
install_file(
|
||||||
serial_interface,
|
|
||||||
&mut adb_device,
|
&mut adb_device,
|
||||||
"/data/rayhunter/config.toml",
|
"/data/rayhunter/config.toml",
|
||||||
CONFIG_TOML.as_bytes(),
|
CONFIG_TOML.as_bytes(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
install_file(
|
install_file(
|
||||||
serial_interface,
|
|
||||||
&mut adb_device,
|
&mut adb_device,
|
||||||
"/etc/init.d/rayhunter_daemon",
|
"/etc/init.d/rayhunter_daemon",
|
||||||
RAYHUNTER_DAEMON_INIT.as_bytes(),
|
RAYHUNTER_DAEMON_INIT.as_bytes(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
install_file(
|
install_file(
|
||||||
serial_interface,
|
|
||||||
&mut adb_device,
|
&mut adb_device,
|
||||||
"/etc/init.d/misc-daemon",
|
"/etc/init.d/misc-daemon",
|
||||||
include_bytes!("../../dist/scripts/misc-daemon"),
|
include_bytes!("../../dist/scripts/misc-daemon"),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
at_syscmd(serial_interface, "chmod 755 /etc/init.d/rayhunter_daemon").await?;
|
adb_at_syscmd(&mut adb_device, "chmod 755 /etc/init.d/rayhunter_daemon").await?;
|
||||||
at_syscmd(serial_interface, "chmod 755 /etc/init.d/misc-daemon").await?;
|
adb_at_syscmd(&mut adb_device, "chmod 755 /etc/init.d/misc-daemon").await?;
|
||||||
println!("done");
|
println!("done");
|
||||||
echo!("Waiting for reboot... ");
|
echo!("Waiting for reboot... ");
|
||||||
at_syscmd(serial_interface, "shutdown -r -t 1 now").await?;
|
adb_at_syscmd(&mut adb_device, "shutdown -r -t 1 now").await?;
|
||||||
// first wait for shutdown (it can take ~10s)
|
// first wait for shutdown (it can take ~10s)
|
||||||
tokio::time::timeout(Duration::from_secs(30), async {
|
tokio::time::timeout(Duration::from_secs(30), async {
|
||||||
while let Ok(dev) = adb_echo_test(adb_device).await {
|
while let Ok(dev) = adb_echo_test(adb_device).await {
|
||||||
@@ -161,16 +160,11 @@ async fn test_rayhunter(adb_device: &mut ADBUSBDevice) -> Result<()> {
|
|||||||
bail!("timeout reached! failed to reach rayhunter, something went wrong :(")
|
bail!("timeout reached! failed to reach rayhunter, something went wrong :(")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn install_file(
|
async fn install_file(adb_device: &mut ADBUSBDevice, dest: &str, payload: &[u8]) -> Result<()> {
|
||||||
serial_interface: &Interface,
|
|
||||||
adb_device: &mut ADBUSBDevice,
|
|
||||||
dest: &str,
|
|
||||||
payload: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
const MAX_FAILURES: u32 = 5;
|
const MAX_FAILURES: u32 = 5;
|
||||||
let mut failures = 0;
|
let mut failures = 0;
|
||||||
loop {
|
loop {
|
||||||
match install_file_impl(serial_interface, adb_device, dest, payload).await {
|
match install_file_impl(adb_device, dest, payload).await {
|
||||||
Ok(()) => return Ok(()),
|
Ok(()) => return Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if failures > MAX_FAILURES {
|
if failures > MAX_FAILURES {
|
||||||
@@ -185,7 +179,6 @@ async fn install_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn install_file_impl(
|
async fn install_file_impl(
|
||||||
serial_interface: &Interface,
|
|
||||||
adb_device: &mut ADBUSBDevice,
|
adb_device: &mut ADBUSBDevice,
|
||||||
dest: &str,
|
dest: &str,
|
||||||
mut payload: &[u8],
|
mut payload: &[u8],
|
||||||
@@ -202,7 +195,7 @@ async fn install_file_impl(
|
|||||||
let file_hash_bytes = hasher.finalize();
|
let file_hash_bytes = hasher.finalize();
|
||||||
let file_hash = format!("{file_hash_bytes:x}");
|
let file_hash = format!("{file_hash_bytes:x}");
|
||||||
adb_device.push(&mut payload, &push_tmp_path)?;
|
adb_device.push(&mut payload, &push_tmp_path)?;
|
||||||
at_syscmd(serial_interface, &format!("mv {push_tmp_path} {dest}")).await?;
|
adb_at_syscmd(adb_device, &format!("mv {push_tmp_path} {dest}")).await?;
|
||||||
let file_info = adb_device
|
let file_info = adb_device
|
||||||
.stat(dest)
|
.stat(dest)
|
||||||
.context("Failed to stat transfered file")?;
|
.context("Failed to stat transfered file")?;
|
||||||
@@ -244,7 +237,7 @@ async fn get_adb() -> Result<ADBUSBDevice> {
|
|||||||
Err(RustADBError::IOError(e)) if e.kind() == ErrorKind::ResourceBusy => {
|
Err(RustADBError::IOError(e)) if e.kind() == ErrorKind::ResourceBusy => {
|
||||||
bail!(ORBIC_BUSY);
|
bail!(ORBIC_BUSY);
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||||
Err(RustADBError::IOError(e)) if e.kind() == ErrorKind::PermissionDenied => {
|
Err(RustADBError::IOError(e)) if e.kind() == ErrorKind::PermissionDenied => {
|
||||||
bail!(ORBIC_BUSY_MAC);
|
bail!(ORBIC_BUSY_MAC);
|
||||||
}
|
}
|
||||||
@@ -321,9 +314,58 @@ async fn wait_for_usb_device(vendor_id: u16, product_id: u16) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn at_syscmd(interface: &Interface, command: &str) -> Result<()> {
|
async fn adb_setup_serial(adb_device: &mut ADBUSBDevice) -> Result<()> {
|
||||||
send_serial_cmd(interface, &format!("AT+SYSCMD={command}")).await
|
Ok(adb_device.get_transport_mut().claim_interface(INTERFACE)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn adb_at_syscmd(adb_device: &mut ADBUSBDevice, command: &str) -> Result<()> {
|
||||||
|
adb_serial_cmd(adb_device, &format!("AT+SYSCMD={command}")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn adb_serial_cmd(adb_device: &mut ADBUSBDevice, command: &str) -> Result<()> {
|
||||||
|
let mut data = String::new();
|
||||||
|
data.push_str("\r\n");
|
||||||
|
data.push_str(command);
|
||||||
|
data.push_str("\r\n");
|
||||||
|
|
||||||
|
let timeout = Duration::from_secs(2);
|
||||||
|
let mut response = [0; 256];
|
||||||
|
|
||||||
|
// Set up the serial port appropriately
|
||||||
|
adb_device
|
||||||
|
.get_transport_mut()
|
||||||
|
.send_usb_class_control_msg(INTERFACE, 0x22, 3, 1, &[], timeout)
|
||||||
|
.context("Failed to send control request")?;
|
||||||
|
|
||||||
|
// Send the command
|
||||||
|
adb_device
|
||||||
|
.get_transport_mut()
|
||||||
|
.usb_bulk_write(INTERFACE, 0x2, data.as_bytes(), timeout)
|
||||||
|
.context("Failed to write command")?;
|
||||||
|
|
||||||
|
// Consume the echoed command
|
||||||
|
adb_device
|
||||||
|
.get_transport_mut()
|
||||||
|
.usb_bulk_read(INTERFACE, 0x82, &mut response, timeout)
|
||||||
|
.context("Failed to read submitted command")?;
|
||||||
|
|
||||||
|
// Read the actual response
|
||||||
|
adb_device
|
||||||
|
.get_transport_mut()
|
||||||
|
.usb_bulk_read(INTERFACE, 0x82, &mut response, timeout)
|
||||||
|
.context("Failed to read response")?;
|
||||||
|
|
||||||
|
// For some reason, on macOS the response buffer gets filled with garbage data that's
|
||||||
|
// rarely valid UTF-8. Luckily we only care about the first couple bytes, so just drop
|
||||||
|
// the garbage with `from_utf8_lossy` and look for our expected success string.
|
||||||
|
let responsestr = String::from_utf8_lossy(&response);
|
||||||
|
if !responsestr.contains("\r\nOK\r\n") {
|
||||||
|
bail!("Received unexpected response: {0}", responsestr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends an AT command to the usb device over the serial port
|
/// Sends an AT command to the usb device over the serial port
|
||||||
///
|
///
|
||||||
/// First establish a USB handle and context by calling `open_orbic(<T>)
|
/// First establish a USB handle and context by calling `open_orbic(<T>)
|
||||||
@@ -400,7 +442,7 @@ pub fn enable_command_mode() -> Result<()> {
|
|||||||
index: 0,
|
index: 0,
|
||||||
};
|
};
|
||||||
let interface = device
|
let interface = device
|
||||||
.detach_and_claim_interface(1)
|
.detach_and_claim_interface(RNDIS_INTERFACE)
|
||||||
.context("detach_and_claim_interface(1) failed")?;
|
.context("detach_and_claim_interface(1) failed")?;
|
||||||
if let Err(e) = interface.control_out_blocking(enable_command_mode, &[], timeout) {
|
if let Err(e) = interface.control_out_blocking(enable_command_mode, &[], timeout) {
|
||||||
// If the device reboots while the command is still executing we
|
// If the device reboots while the command is still executing we
|
||||||
@@ -421,7 +463,7 @@ pub fn open_orbic() -> Result<Option<Interface>> {
|
|||||||
// Device after initial mode switch
|
// Device after initial mode switch
|
||||||
if let Some(device) = open_usb_device(VENDOR_ID, PRODUCT_ID)? {
|
if let Some(device) = open_usb_device(VENDOR_ID, PRODUCT_ID)? {
|
||||||
let interface = device
|
let interface = device
|
||||||
.detach_and_claim_interface(1) // will reattach drivers on release
|
.detach_and_claim_interface(INTERFACE) // will reattach drivers on release
|
||||||
.context("detach_and_claim_interface(1) failed")?;
|
.context("detach_and_claim_interface(1) failed")?;
|
||||||
return Ok(Some(interface));
|
return Ok(Some(interface));
|
||||||
}
|
}
|
||||||
@@ -429,7 +471,7 @@ pub fn open_orbic() -> Result<Option<Interface>> {
|
|||||||
// Device with rndis enabled as well
|
// Device with rndis enabled as well
|
||||||
if let Some(device) = open_usb_device(VENDOR_ID, 0xf622)? {
|
if let Some(device) = open_usb_device(VENDOR_ID, 0xf622)? {
|
||||||
let interface = device
|
let interface = device
|
||||||
.detach_and_claim_interface(1) // will reattach drivers on release
|
.detach_and_claim_interface(INTERFACE) // will reattach drivers on release
|
||||||
.context("detach_and_claim_interface(1) failed")?;
|
.context("detach_and_claim_interface(1) failed")?;
|
||||||
return Ok(Some(interface));
|
return Ok(Some(interface));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,11 @@ pub async fn main_tplink(
|
|||||||
InstallTpLink {
|
InstallTpLink {
|
||||||
skip_sdcard,
|
skip_sdcard,
|
||||||
admin_ip,
|
admin_ip,
|
||||||
|
sdcard_path,
|
||||||
}: InstallTpLink,
|
}: InstallTpLink,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
start_telnet(&admin_ip).await?;
|
let is_v3 = start_telnet(&admin_ip).await?;
|
||||||
tplink_run_install(skip_sdcard, admin_ip).await
|
tplink_run_install(skip_sdcard, admin_ip, sdcard_path, is_v3).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -38,7 +39,7 @@ struct V3RootResponse {
|
|||||||
result: u64,
|
result: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_telnet(admin_ip: &str) -> Result<(), Error> {
|
pub async fn start_telnet(admin_ip: &str) -> Result<bool, Error> {
|
||||||
let qcmap_web_cgi_endpoint = format!("http://{admin_ip}/cgi-bin/qcmap_web_cgi");
|
let qcmap_web_cgi_endpoint = format!("http://{admin_ip}/cgi-bin/qcmap_web_cgi");
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
@@ -51,7 +52,9 @@ pub async fn start_telnet(admin_ip: &str) -> Result<(), Error> {
|
|||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if response.status() == 404 {
|
let is_v3 = response.status() != 404;
|
||||||
|
|
||||||
|
if !is_v3 {
|
||||||
println!("Got a 404 trying to run exploit for hardware revision v3, trying v5 exploit");
|
println!("Got a 404 trying to run exploit for hardware revision v3, trying v5 exploit");
|
||||||
tplink_launch_telnet_v5(admin_ip).await?;
|
tplink_launch_telnet_v5(admin_ip).await?;
|
||||||
} else {
|
} else {
|
||||||
@@ -82,20 +85,49 @@ pub async fn start_telnet(admin_ip: &str) -> Result<(), Error> {
|
|||||||
println!(
|
println!(
|
||||||
"Succeeded in rooting the device! Now you can use 'telnet {admin_ip}' to get a root shell. Use './installer util tplink-start-telnet' to root again without installing rayhunter."
|
"Succeeded in rooting the device! Now you can use 'telnet {admin_ip}' to get a root shell. Use './installer util tplink-start-telnet' to root again without installing rayhunter."
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(is_v3)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn tplink_run_install(skip_sdcard: bool, admin_ip: String) -> Result<(), Error> {
|
async fn tplink_run_install(
|
||||||
|
skip_sdcard: bool,
|
||||||
|
admin_ip: String,
|
||||||
|
mut sdcard_path: String,
|
||||||
|
is_v3: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
println!("Connecting via telnet to {admin_ip}");
|
println!("Connecting via telnet to {admin_ip}");
|
||||||
let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap();
|
let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap();
|
||||||
|
|
||||||
if !skip_sdcard {
|
if !skip_sdcard {
|
||||||
println!("Mounting sdcard");
|
if sdcard_path.is_empty() {
|
||||||
if telnet_send_command(addr, "mount | grep -q /media/card", "exit code 0")
|
if telnet_send_command(addr, "ls /media/card", "exit code 0")
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_ok()
|
||||||
|
{
|
||||||
|
// TP-Link hardware less than v9.0
|
||||||
|
sdcard_path = "/media/card".to_owned();
|
||||||
|
} else if telnet_send_command(addr, "ls /media/sdcard", "exit code 0")
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
// TP-Link hardware v9.0
|
||||||
|
sdcard_path = "/media/sdcard".to_owned();
|
||||||
|
} else {
|
||||||
|
anyhow::bail!(
|
||||||
|
"unable to determine sdcard path. this is a bug. please file an issue with your hardware version."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Mounting sdcard on {sdcard_path}");
|
||||||
|
if telnet_send_command(
|
||||||
|
addr,
|
||||||
|
&format!("mount | grep -q {sdcard_path}"),
|
||||||
|
"exit code 0",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
{
|
{
|
||||||
telnet_send_command(addr, "mount /dev/mmcblk0p1 /media/card", "exit code 0").await.context("Rayhunter needs a FAT-formatted SD card to function for more than a few minutes. Insert one and rerun this installer, or pass --skip-sdcard")?;
|
telnet_send_command(addr, &format!("mount /dev/mmcblk0p1 {sdcard_path}"), "exit code 0").await.context("Rayhunter needs a FAT-formatted SD card to function for more than a few minutes. Insert one and rerun this installer, or pass --skip-sdcard")?;
|
||||||
} else {
|
} else {
|
||||||
println!("sdcard already mounted");
|
println!("sdcard already mounted");
|
||||||
}
|
}
|
||||||
@@ -105,28 +137,38 @@ async fn tplink_run_install(skip_sdcard: bool, admin_ip: String) -> Result<(), E
|
|||||||
// expects things to be at this location
|
// expects things to be at this location
|
||||||
telnet_send_command(addr, "rm -rf /data/rayhunter", "exit code 0").await?;
|
telnet_send_command(addr, "rm -rf /data/rayhunter", "exit code 0").await?;
|
||||||
telnet_send_command(addr, "mkdir -p /data", "exit code 0").await?;
|
telnet_send_command(addr, "mkdir -p /data", "exit code 0").await?;
|
||||||
telnet_send_command(addr, "ln -sf /media/card /data/rayhunter", "exit code 0").await?;
|
telnet_send_command(
|
||||||
|
addr,
|
||||||
|
&format!("ln -sf {sdcard_path} /data/rayhunter"),
|
||||||
|
"exit code 0",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
telnet_send_file(
|
telnet_send_file(
|
||||||
addr,
|
addr,
|
||||||
"/media/card/config.toml",
|
&format!("{sdcard_path}/config.toml"),
|
||||||
crate::CONFIG_TOML.as_bytes(),
|
crate::CONFIG_TOML.as_bytes(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON_TPLINK"));
|
let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON_TPLINK"));
|
||||||
|
|
||||||
telnet_send_file(addr, "/media/card/rayhunter-daemon", rayhunter_daemon_bin).await?;
|
telnet_send_file(
|
||||||
|
addr,
|
||||||
|
&format!("{sdcard_path}/rayhunter-daemon"),
|
||||||
|
rayhunter_daemon_bin,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
telnet_send_file(
|
telnet_send_file(
|
||||||
addr,
|
addr,
|
||||||
"/etc/init.d/rayhunter_daemon",
|
"/etc/init.d/rayhunter_daemon",
|
||||||
get_rayhunter_daemon().as_bytes(),
|
get_rayhunter_daemon(&sdcard_path).as_bytes(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
telnet_send_command(
|
telnet_send_command(
|
||||||
addr,
|
addr,
|
||||||
"chmod ugo+x /media/card/rayhunter-daemon",
|
&format!("chmod ugo+x {sdcard_path}/rayhunter-daemon"),
|
||||||
"exit code 0",
|
"exit code 0",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -136,7 +178,13 @@ async fn tplink_run_install(skip_sdcard: bool, admin_ip: String) -> Result<(), E
|
|||||||
"exit code 0",
|
"exit code 0",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
telnet_send_command(addr, "update-rc.d rayhunter_daemon defaults", "exit code 0").await?;
|
|
||||||
|
// if the device is not v3, the JS-based root exploit already added rayhunter_daemon as a
|
||||||
|
// startup script. tplink v9 does not have update-rc.d, and it was reported that *sometimes* it
|
||||||
|
// is unreliable on other hardware revisions too.
|
||||||
|
if is_v3 {
|
||||||
|
telnet_send_command(addr, "update-rc.d rayhunter_daemon defaults", "exit code 0").await?;
|
||||||
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Done. Rebooting device. After it's started up again, check out the web interface at http://{admin_ip}:8080"
|
"Done. Rebooting device. After it's started up again, check out the web interface at http://{admin_ip}:8080"
|
||||||
@@ -278,6 +326,7 @@ async fn handler(state: State<AppState>, mut req: Request) -> Result<Response, S
|
|||||||
// inject some javascript into the admin UI to get us a telnet shell.
|
// inject some javascript into the admin UI to get us a telnet shell.
|
||||||
data.extend(br#";window.rayhunterPoll = window.setInterval(() => {
|
data.extend(br#";window.rayhunterPoll = window.setInterval(() => {
|
||||||
Globals.models.PTModel.add({applicationName: "rayhunter-root", enableState: 1, entryId: 1, openPort: "2300-2400", openProtocol: "TCP", triggerPort: "$(busybox telnetd -l /bin/sh)", triggerProtocol: "TCP"});
|
Globals.models.PTModel.add({applicationName: "rayhunter-root", enableState: 1, entryId: 1, openPort: "2300-2400", openProtocol: "TCP", triggerPort: "$(busybox telnetd -l /bin/sh)", triggerProtocol: "TCP"});
|
||||||
|
Globals.models.PTModel.add({applicationName: "rayhunter-daemon", enableState: 1, entryId: 2, openPort: "2400-2500", openProtocol: "TCP", triggerPort: "$(/etc/init.d/rayhunter_daemon start)", triggerProtocol: "TCP"});
|
||||||
alert("Success! You can go back to the rayhunter installer.");
|
alert("Success! You can go back to the rayhunter installer.");
|
||||||
window.clearInterval(window.rayhunterPoll);
|
window.clearInterval(window.rayhunterPoll);
|
||||||
}, 1000);"#);
|
}, 1000);"#);
|
||||||
@@ -324,7 +373,7 @@ async fn tplink_launch_telnet_v5(admin_ip: &str) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rayhunter_daemon() -> String {
|
fn get_rayhunter_daemon(sdcard_path: &str) -> String {
|
||||||
// Even though TP-Link eventually auto-mounts the SD card, it sometimes does so too late. And
|
// Even though TP-Link eventually auto-mounts the SD card, it sometimes does so too late. And
|
||||||
// changing the order in which daemons are started up seems to not work reliably.
|
// changing the order in which daemons are started up seems to not work reliably.
|
||||||
//
|
//
|
||||||
@@ -332,12 +381,12 @@ fn get_rayhunter_daemon() -> String {
|
|||||||
// specific to a particular hardware revision here.
|
// specific to a particular hardware revision here.
|
||||||
crate::RAYHUNTER_DAEMON_INIT.replace(
|
crate::RAYHUNTER_DAEMON_INIT.replace(
|
||||||
"#RAYHUNTER-PRESTART",
|
"#RAYHUNTER-PRESTART",
|
||||||
"mount /dev/mmcblk0p1 /media/card || true",
|
&format!("mount /dev/mmcblk0p1 {sdcard_path} || true"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_rayhunter_daemon() {
|
fn test_get_rayhunter_daemon() {
|
||||||
let s = get_rayhunter_daemon();
|
let s = get_rayhunter_daemon("/media/card");
|
||||||
assert!(s.contains("mount /dev/mmcblk0p1 /media/card"));
|
assert!(s.contains("mount /dev/mmcblk0p1 /media/card"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rayhunter"
|
name = "rayhunter"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Realtime cellular data decoding and analysis for IMSI catcher detection"
|
description = "Realtime cellular data decoding and analysis for IMSI catcher detection"
|
||||||
|
|
||||||
@@ -19,14 +19,12 @@ bytes = "1.5.0"
|
|||||||
chrono = "0.4.31"
|
chrono = "0.4.31"
|
||||||
crc = "3.0.1"
|
crc = "3.0.1"
|
||||||
deku = { version = "0.18.0", features = ["logging"] }
|
deku = { version = "0.18.0", features = ["logging"] }
|
||||||
env_logger = "0.10.1"
|
|
||||||
libc = "0.2.150"
|
libc = "0.2.150"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
nix = { version = "0.29.0", features = ["feature"] }
|
nix = { version = "0.29.0", features = ["feature"] }
|
||||||
pcap-file-tokio = "0.1.0"
|
pcap-file-tokio = "0.1.0"
|
||||||
thiserror = "1.0.50"
|
thiserror = "1.0.50"
|
||||||
telcom-parser = { path = "../telcom-parser" }
|
telcom-parser = { path = "../telcom-parser" }
|
||||||
tokio = { version = "1.44.2", features = ["full"] }
|
tokio = { version = "1.44.2", default-features = false }
|
||||||
futures-core = "0.3.30"
|
futures = { version = "0.3.30", default-features = false }
|
||||||
futures = "0.3.30"
|
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::hdlc::hdlc_encapsulate;
|
|||||||
use crate::log_codes;
|
use crate::log_codes;
|
||||||
|
|
||||||
use deku::prelude::*;
|
use deku::prelude::*;
|
||||||
use futures_core::TryStream;
|
use futures::TryStream;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::os::fd::AsRawFd;
|
use std::os::fd::AsRawFd;
|
||||||
@@ -251,6 +251,7 @@ impl DiagDevice {
|
|||||||
//
|
//
|
||||||
// TPLINK M7350 v5 source code can be downloaded at https://www.tp-link.com/de/support/gpl-code/?app=omada
|
// TPLINK M7350 v5 source code can be downloaded at https://www.tp-link.com/de/support/gpl-code/?app=omada
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct diag_logging_mode_param_t {
|
struct diag_logging_mode_param_t {
|
||||||
req_mode: u32,
|
req_mode: u32,
|
||||||
peripheral_mask: u32,
|
peripheral_mask: u32,
|
||||||
@@ -261,30 +262,41 @@ struct diag_logging_mode_param_t {
|
|||||||
fn enable_frame_readwrite(fd: i32, mode: u32) -> DiagResult<()> {
|
fn enable_frame_readwrite(fd: i32, mode: u32) -> DiagResult<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if libc::ioctl(fd, DIAG_IOCTL_SWITCH_LOGGING, mode, 0, 0, 0) < 0 {
|
if libc::ioctl(fd, DIAG_IOCTL_SWITCH_LOGGING, mode, 0, 0, 0) < 0 {
|
||||||
let mut params = if cfg!(feature = "tplink") {
|
let try_params: &[diag_logging_mode_param_t] = &[
|
||||||
|
// tplink M7350 HW revision 3-8 need this mode
|
||||||
|
#[cfg(feature = "tplink")]
|
||||||
diag_logging_mode_param_t {
|
diag_logging_mode_param_t {
|
||||||
req_mode: mode,
|
req_mode: mode,
|
||||||
peripheral_mask: 0,
|
peripheral_mask: 0,
|
||||||
mode_param: 1,
|
mode_param: 1,
|
||||||
}
|
},
|
||||||
} else {
|
// tplink M7350 HW revision v9 requires the same parameters as orbic
|
||||||
diag_logging_mode_param_t {
|
diag_logging_mode_param_t {
|
||||||
req_mode: mode,
|
req_mode: mode,
|
||||||
peripheral_mask: u32::MAX,
|
peripheral_mask: u32::MAX,
|
||||||
mode_param: 0,
|
mode_param: 0,
|
||||||
}
|
},
|
||||||
};
|
];
|
||||||
|
|
||||||
|
let mut ret = 0;
|
||||||
|
|
||||||
|
for params in try_params {
|
||||||
|
let mut params = *params;
|
||||||
|
ret = libc::ioctl(
|
||||||
|
fd,
|
||||||
|
DIAG_IOCTL_SWITCH_LOGGING,
|
||||||
|
&mut params as *mut diag_logging_mode_param_t,
|
||||||
|
std::mem::size_of::<diag_logging_mode_param_t>(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if ret == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ret = libc::ioctl(
|
|
||||||
fd,
|
|
||||||
DIAG_IOCTL_SWITCH_LOGGING,
|
|
||||||
&mut params as *mut _,
|
|
||||||
std::mem::size_of::<diag_logging_mode_param_t>(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"DIAG_IOCTL_SWITCH_LOGGING ioctl failed with error code {}",
|
"DIAG_IOCTL_SWITCH_LOGGING ioctl failed with error code {}",
|
||||||
|
|||||||
4
make.sh
@@ -2,8 +2,8 @@
|
|||||||
pushd bin/web
|
pushd bin/web
|
||||||
npm run build
|
npm run build
|
||||||
popd
|
popd
|
||||||
cargo build --release --target="armv7-unknown-linux-musleabihf" #--features debug
|
cargo build --profile firmware --target="armv7-unknown-linux-musleabihf" #--features debug
|
||||||
adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon stop"'
|
adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon stop"'
|
||||||
adb push target/armv7-unknown-linux-musleabihf/release/rayhunter-daemon /data/rayhunter/rayhunter-daemon
|
adb push target/armv7-unknown-linux-musleabihf/firmware/rayhunter-daemon /data/rayhunter/rayhunter-daemon
|
||||||
echo "rebooting the device..."
|
echo "rebooting the device..."
|
||||||
adb shell '/bin/rootshell -c "reboot"'
|
adb shell '/bin/rootshell -c "reboot"'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rootshell"
|
name = "rootshell"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "telcom-parser"
|
name = "telcom-parser"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||