196 lines
9.0 KiB
Markdown
196 lines
9.0 KiB
Markdown
# RVBox - A Reverse Shell Solution for LLM Agent
|
|
|
|
* It's a C/S architecture, the clients are the one connects to the server, and are controlled by it.
|
|
* The clients runs the `rvbox` daemon, actively and persistently connects to a configured server. It registers and identifies itself to the server first, if accepted, then keeps a long connection and accepting commands from the server.
|
|
* The server runs the `rvbox-server` daemon, listening on a `wss://` endpoint, waiting for incoming `rvbox` clients. An LLM agent on the server machine then can use `rvc` command to communicate with the server daemon, issuing commands to do all sorts of things:
|
|
- listing clients,
|
|
- issuing/terminating commands on specific client,
|
|
- retrieving running/stopping status of a command,
|
|
- getting stdout/stderr, or return code from a command,
|
|
- inputing into stdin of a command,
|
|
|
|
* commands can be issued in two modes: foreground or background, for foreground ones, `rvc` command wait for the return, otherwise, `rvc` returns immediately with metadata of the issued command process on the client.
|
|
* for each issued command from `rvc`, the server daemon attaches an `issue_uuid` to it, and sends it to the related client, along with these fields: `issue_timestamp`, `shell_type`, `shell_command_text`. The server also records all these fields into its data store, with another `target_client_id`, to track the full states of every command.
|
|
* The client's daemon runs the issued commands, and reports stdout/stderr of all issued commands to the server in real-time, meaning no waiting for new-line flush. Also, multiple commands can be issued to a client daemon, and the client daemon can execute them concurrently. For each running command, the daemon appends new stdout and stderr updates to the server in every short 3-5s window, or only reports the command's status in a longer 10-15s window if no new updates. The server keeps on track of everything, so it can reconstruct the full context/history correctly for each command, when demanded so by `rvc` .
|
|
* The connection should be guarded by a properly designed heartbeat mechanism on both C/S sides.
|
|
* `rvc` is just a cli command that communicates with the server daemon through local unixsock. Also, the same server daemon control interface can be accessed through an optional simple HTTP json rpc endpoint, e.g. `--json-rpc http://127.0.0.1:6900` by default, so that we can interact with the server in a more structured way when complex or large batch operations are needed.
|
|
|
|
### Example usages of `rvc` on server side
|
|
|
|
1. Listing clients
|
|
|
|
```bash
|
|
$ rvc stat
|
|
CLIENT Commands Running Connected Time
|
|
client_a 5 10 days
|
|
client_b 0 10 min
|
|
...
|
|
```
|
|
|
|
2. Stating a client
|
|
|
|
```bash
|
|
$ rvc stat client_a
|
|
Command ID Command Snippet Status Issue Time
|
|
<uuid_1> "cp /path1/file /path2/" Running 10s ago
|
|
<uuid_2> "python very_long_computing.py" Running 2 days ago
|
|
<uuid_3> "sha256sum /path/very_large_file" Hung 10min ago
|
|
```
|
|
|
|
3. Stating all commands of a client. Note this `--all` can return very long history, so it has a optional pager `--page <NO>` arg. By default the optional `--per-page` is at 20, and accepts 100 at max.
|
|
|
|
```bash
|
|
$ rvc stat client_a --all
|
|
Page 1/34
|
|
Command ID Command Snippet Status Issue Time
|
|
<uuid_1> "cp /path1/file /path2/" Running 10s ago
|
|
<uuid_2> "python very_long_computing.py" Running 2 days ago
|
|
<uuid_3> "sha256sum /path/very_large_file" Hung(I/O) 10min ago
|
|
<uuid_4> "curl -fsLO https://..." Done 15min ago
|
|
<uuid_5> "culr -fsLO https://..." Failed(127) 15min ago
|
|
<uuid_6> "curl -fsLO https://..." Terminated(130) 15min ago
|
|
...
|
|
```
|
|
|
|
4. Stating some commands of client_a
|
|
|
|
```bash
|
|
$ rvc stat client_a <uuid_1>
|
|
Command Text: "cp /path1/file /path2/"
|
|
Status: Running
|
|
Issue Time: 20xx-01-01T10:01:01Z
|
|
CWD: "/..."
|
|
CPU TIME: 0:00.23
|
|
RES RAM: 12K
|
|
```
|
|
|
|
```bash
|
|
$ rvc stat client_a <uuid_2>
|
|
Command Text: "python very_long_computing.py"
|
|
Status: Running
|
|
Issue Time: 20xx-12-30T12:01:01Z
|
|
CWD: "/..."
|
|
CPU TIME: 12h34:45
|
|
RES RAM: 892M
|
|
```
|
|
|
|
```bash
|
|
$ rvc stat client_a <uuid_3>
|
|
Command Text: "sha256sum /path/very_large_file"
|
|
Status: Hung
|
|
Reason: I/O
|
|
Issue Time: 20xx-01-01T10:01:01Z
|
|
CWD: "/..."
|
|
CPU TIME: 0:10.24
|
|
RES RAM: 125K
|
|
```
|
|
|
|
```bash
|
|
$ rvc stat client_a <uuid_4>
|
|
Command Text: "curl -fsLO https://<download_url>"
|
|
Status: Success
|
|
Issue Time: 20xx-01-01T10:01:01Z
|
|
CWD: "/..."
|
|
CPU TIME: 0:10.24
|
|
RES RAM: 125K
|
|
```
|
|
|
|
```bash
|
|
$ rvc stat client_a <uuid_5>
|
|
Command Text: "culr -fsLO https://<download_url>"
|
|
Status: Failure
|
|
Exit Code: 127
|
|
Issue Time: 20xx-01-01T10:01:01Z
|
|
Return Time: 20xx-01-01T10:01:01Z
|
|
CWD: "/..."
|
|
```
|
|
|
|
```bash
|
|
$ rvc stat client_a <uuid_6>
|
|
Command Text: "curl -fsLO https://<download url>"
|
|
Status: Terminated
|
|
Exit Code: 130
|
|
Issue Time: 20xx-01-01T10:01:01Z
|
|
Return Time: 20xx-01-01T10:02:01Z
|
|
CWD: "/..."
|
|
```
|
|
|
|
5. Issue a new command to a client.
|
|
|
|
- By design, the commands triggered/executed by the client daemon should inherit its user/permission settings.
|
|
|
|
- By default, the optional `--current-work-dir/--cwd` of the issued command is inherited from the client daemon.
|
|
- If `--script` is used to issue a whole script, the script will first be put into the `--cwd` dir, then executed by the client daemon. As soon as it's exited, the script will be reclaimed.
|
|
|
|
```bash
|
|
# wait until return by default
|
|
$ rvc run client_a --cwd '/home/user' 'ls'
|
|
Desktop Documents Downloads Pictures...
|
|
# run in background, return the command id immediately
|
|
$ rvc run --background client_a 'python -c "for i in range(15): print(\'hi\'); import time; time.sleep(1)"'
|
|
Command ID: <uuid_x>
|
|
# if the command is too long or the symbol escapings are too complicated, write a whole script then issue.
|
|
$ cat << 'EOF' > ./py_script
|
|
heredoc> #!/usr/bin/env python
|
|
heredoc> print("what's your name?")
|
|
heredoc> name = input("type your name here: ")
|
|
heredoc> print(f"Hello, {name}!")
|
|
heredoc> EOF
|
|
$ rvc run client_a --script ./py_script --cwd /tmp --background
|
|
Command ID: <uuid_y>
|
|
```
|
|
|
|
6. Get stdout and/or stderr of a command,
|
|
|
|
- optionally with `--timestamped` to get a leading timestamp on each line,
|
|
- and optionally follow new updates with `--follow/-f`, just like the `tail -f` command
|
|
- the output of `--stdout` and `--stderr` are paged, with `--lines-per-page` defaults to 100 and `--page` defaults to 1.
|
|
|
|
```bash
|
|
$ rvc stat client_a <uuid_x> --stdout --stderr --timestamped --page 2 --lines-per-page 3
|
|
Page 2/4
|
|
[20xx-01-01T10:21:01Z stdout] hi
|
|
[20xx-01-01T10:21:02Z stdout] hi
|
|
[20xx-01-01T10:21:03Z stdout] hi
|
|
```
|
|
|
|
7. Write new content into stdin of a command
|
|
|
|
```bash
|
|
# write the string into its stdin, and a \n will be appended to the end automatically
|
|
$ rvc append client_a <uuid_y> 'Alice'
|
|
$ rvc stat client_a <uuid_y> --stdout --stderr --timestamped
|
|
[20xx-01-01T10:23:11Z stdout] type your name here:
|
|
[20xx-01-01T10:24:09Z stdout] Hello, Alice!
|
|
# or write a file content into its stdin
|
|
$ rvc append client_a <uuid_y> --file ./name_file
|
|
# or patch the current stdin into it
|
|
$ rvc append client_a <uuid_y> --attach
|
|
Bob
|
|
```
|
|
|
|
8. Send a signal to a command.
|
|
|
|
- For unix/linux client daemons, this `kill` subcommand behaves just like the normal `kill` command. It sends SIGTERM by default, and can be used to send other signals with args like `-1/-HUP`, `-2/-INT`, etc.
|
|
- On windows client daemons, it should only accept SIGTERM and SIGKILL, equivalent to `taskkill /IM` and `taskkill /F /IM`, respectively.
|
|
|
|
```bash
|
|
$ rvc kill client_a <uuid_3>
|
|
```
|
|
|
|
|
|
|
|
### Implementation requirements
|
|
|
|
1. We should first do proper design on the protocol - both the C/S interface and the server daemon unixsock/jsonrpc control interface. protobuf should be a solid choice to do the protocol design.
|
|
2. Currently we'll implement both C/S daemon in Golang. We'll later implement a Rust version client to adapt on low power devices.
|
|
|
|
|
|
### Technical nuances
|
|
|
|
1. The `rvbox-server` daemon and the `rvbox` client daemon should always be responsive. The server daemon should always be dispatching clients, recording the status, and listening for requests from `rvc`, etc. And the client daemons should keep on receiving/dispatching incoming commands/requests, monitoring ongoing commands, reporting new updates/status to the server. In ABSOLUTELY NO circumstances should the daemons themselves be crashed, blocked, hung, flooded, unresponsive, etc., due to any possible reason.
|
|
|
|
2. The heartbeat mechanism that keeps and guards the websocket connection should be robust, clean and correct. When connection exceptions/interruptions/hangs happen, the heartbeat mechanism should detect them in time, and trigger the reconnect, re-register route correctly. It should guard on both the inbound and the outbound traffic. It should not disrupt, hog, block, interrupt, mutate the normal traffic in any way.
|
|
|
|
|