mirror of
https://github.com/nicocha30/ligolo-ng
synced 2026-08-09 12:58:47 +00:00
rebase with ws
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
name: Generate Sponsors README
|
||||
on:
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Generate Sponsors 💖
|
||||
uses: JamesIves/github-sponsors-readme-action@v1
|
||||
with:
|
||||
template: '<a href="https://github.com/{{ login }}"><img src="https://github.com/{{ login }}.png" width="50px" alt="{{ login }}" /></a> '
|
||||
token: ${{ secrets.PAT }}
|
||||
file: 'README.md'
|
||||
|
||||
- name: Deploy to GitHub Pages 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
branch: main
|
||||
folder: '.'
|
||||
@@ -12,6 +12,9 @@ You use Ligolo-ng for your penetration tests? Did it help you pass a certificati
|
||||
|
||||
[:heart: Sponsor nicocha30](https://github.com/sponsors/nicocha30)
|
||||
|
||||
Thank you to the following people for supporting the development of Ligolo-ng.
|
||||
|
||||
<!-- sponsors --><!-- sponsors -->
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -20,26 +23,9 @@ You use Ligolo-ng for your penetration tests? Did it help you pass a certificati
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Features](#features)
|
||||
- [How is this different from Ligolo/Chisel/Meterpreter... ?](#how-is-this-different-from-ligolochiselmeterpreter-)
|
||||
- [Building & Usage](#building--usage)
|
||||
- [Precompiled binaries](#precompiled-binaries)
|
||||
- [Building Ligolo-ng](#building-ligolo-ng)
|
||||
- [Setup Ligolo-ng](#setup-ligolo-ng)
|
||||
- [Linux](#linux)
|
||||
- [Windows](#windows)
|
||||
- [Running Ligolo-ng proxy server](#running-ligolo-ng-proxy-server)
|
||||
- [TLS Options](#tls-options)
|
||||
- [Using Let's Encrypt Autocert](#using-lets-encrypt-autocert)
|
||||
- [Using your own TLS certificates](#using-your-own-tls-certificates)
|
||||
- [Automatic self-signed certificates](#automatic-self-signed-certificates)
|
||||
- [Using Ligolo-ng](#using-ligolo-ng)
|
||||
- [Start the agent](#start-the-agent)
|
||||
- [Start the tunneling](#start-the-tunneling)
|
||||
- [Setup routing](#setup-routing)
|
||||
- [Agent Binding/Listening](#agent-bindinglistening)
|
||||
- [Access to agent's local ports (127.0.0.1)](#access-to-agents-local-ports-127001)
|
||||
- [Agent as server (Bind)](#agent-as-server-bind)
|
||||
- [Demo](#demo)
|
||||
- [How is this different from Ligolo/Chisel/Meterpreter... ?](#how-is-this-different-from-ligolochiselmeterpreter-)
|
||||
- [How to use - documentation - tutorial](#how-to-use---documentation---tutorial)
|
||||
- [Does it require Administrator/root access ?](#does-it-require-administratorroot-access-)
|
||||
- [Supported protocols/packets](#supported-protocolspackets)
|
||||
- [Performance](#performance)
|
||||
@@ -56,16 +42,22 @@ tunnels from a reverse TCP/TLS connection using a **tun interface** (without the
|
||||
|
||||
## Features
|
||||
|
||||
- **Tun interface** (No more SOCKS!)
|
||||
- **Tun interface** (No more SOCKS/Proxychains!)
|
||||
- Simple UI with *agent* selection and *network information*
|
||||
- Easy to use and setup
|
||||
- Automatic certificate configuration with Let's Encrypt
|
||||
- Performant (Multiplexing)
|
||||
- Does not require high privileges
|
||||
- Does not require privileges on the *agent*
|
||||
- Socket listening/binding on the *agent*
|
||||
- Multiple platforms supported for the *agent*
|
||||
- Can handle multiple tunnels
|
||||
- Reverse/Bind Connection
|
||||
- Automatic tunnel/listeners recovery (in case of network issues)
|
||||
- Websocket support
|
||||
|
||||
## Demo
|
||||
|
||||
[Ligolo-ng-demo.webm](https://github.com/nicocha30/ligolo-ng/assets/31402213/3070bb7c-0b0d-4c77-9181-cff74fb2f0ba)
|
||||
|
||||
## How is this different from Ligolo/Chisel/Meterpreter... ?
|
||||
|
||||
@@ -83,292 +75,9 @@ As an example, for a TCP connection:
|
||||
|
||||
This allows running tools like *nmap* without the use of *proxychains* (simpler and faster).
|
||||
|
||||
## Building & Usage
|
||||
## How to use - documentation - tutorial
|
||||
|
||||
### Precompiled binaries
|
||||
|
||||
Precompiled binaries (Windows/Linux/macOS) are available on the [Release page](https://github.com/nicocha30/ligolo-ng/releases).
|
||||
|
||||
### Building Ligolo-ng
|
||||
Building *ligolo-ng* (Go >= 1.20 is required):
|
||||
|
||||
```shell
|
||||
$ go build -o agent cmd/agent/main.go
|
||||
$ go build -o proxy cmd/proxy/main.go
|
||||
# Build for Windows
|
||||
$ GOOS=windows go build -o agent.exe cmd/agent/main.go
|
||||
$ GOOS=windows go build -o proxy.exe cmd/proxy/main.go
|
||||
```
|
||||
|
||||
### Setup Ligolo-ng
|
||||
|
||||
#### Linux
|
||||
|
||||
When using Linux, you need to create a tun interface on the Proxy Server (C2):
|
||||
|
||||
```shell
|
||||
$ sudo ip tuntap add user [your_username] mode tun ligolo
|
||||
$ sudo ip link set ligolo up
|
||||
```
|
||||
|
||||
> On **Ligolo-ng >= v0.6**, you can now use the `interface_create` command to create a new interface! No need to use ip tuntap!
|
||||
|
||||
```
|
||||
ligolo-ng » interface_create --name "evil-cha"
|
||||
INFO[3185] Creating a new "evil-cha" interface...
|
||||
INFO[3185] Interface created!
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
You need to download the [Wintun](https://www.wintun.net/) driver (used by [WireGuard](https://www.wireguard.com/)) and place the `wintun.dll` in the same folder as Ligolo (make sure you use the right architecture).
|
||||
|
||||
#### Running Ligolo-ng proxy server
|
||||
|
||||
Start the *proxy* server on your Command and Control (C2) server (default port 11601):
|
||||
|
||||
```shell
|
||||
$ ./proxy -h # Help options
|
||||
$ ./proxy -autocert # Automatically request LetsEncrypt certificates
|
||||
$ ./proxy -selfcert # Use self-signed certificates
|
||||
```
|
||||
For using websocket protocol start the *proxy* server with `https://` prefix
|
||||
```shell
|
||||
$ ./proxy -selfcert https://0.0.0.0:8443 # Use self-signed certificates
|
||||
```
|
||||
|
||||
|
||||
### TLS Options
|
||||
|
||||
#### Using Let's Encrypt Autocert
|
||||
|
||||
When using the `-autocert` option, the proxy will automatically request a certificate (using Let's Encrypt) for *attacker_c2_server.com* when an agent connects.
|
||||
|
||||
> Port 80 needs to be accessible for Let's Encrypt certificate validation/retrieval
|
||||
|
||||
#### Using your own TLS certificates
|
||||
|
||||
If you want to use your own certificates for the proxy server, you can use the `-certfile` and `-keyfile` parameters.
|
||||
|
||||
#### Automatic self-signed certificates
|
||||
|
||||
The *proxy/relay* can automatically generate self-signed TLS certificates using the `-selfcert` option.
|
||||
|
||||
***Validating self-signed certificates fingerprints (recommended)***
|
||||
|
||||
When running selfcert, you can run the `certificate_fingerprint` command to print the currently used certificate fingerprint.
|
||||
|
||||
```
|
||||
ligolo-ng » certificate_fingerprint
|
||||
INFO[0203] TLS Certificate fingerprint for ligolo is: D005527D2683A8F2DB73022FBF23188E064493CFA17D6FCF257E14F4B692E0FC
|
||||
```
|
||||
|
||||
On the agent, you can then connect using the fingerprint provided by the Ligolo-ng proxy.
|
||||
|
||||
```
|
||||
ligolo-agent -connect 127.0.0.1:11601 -v -accept-fingerprint D005527D2683A8F2DB73022FBF23188E064493CFA17D6FCF257E14F4B692E0FC nchatelain@nworkstation
|
||||
INFO[0000] Connection established addr="127.0.0.1:11601"
|
||||
```
|
||||
|
||||
> By default, the "ligolo" domain name is used for TLS Certificate generation. You can change the domain by using the -selfcert-domain [domain] option at startup.
|
||||
|
||||
***Ignoring all certificate verification (for lab/debugging)***
|
||||
|
||||
To ignore all security mechanisms, the `-ignore-cert` option can be used with the *agent*.
|
||||
|
||||
> Beware of man-in-the-middle attacks! This option should only be used in a test environment or for debugging purposes.
|
||||
### Using Ligolo-ng
|
||||
|
||||
#### Start the agent
|
||||
|
||||
Start the *agent* on your target (victim) computer (no privileges are required!):
|
||||
|
||||
```shell
|
||||
$ ./agent -connect attacker_c2_server.com:11601
|
||||
```
|
||||
|
||||
You can use websocket connection to ligolo-ng proxy by adding https:// prefix (by default 443 port will be used):
|
||||
```shell
|
||||
$ ./agent -connect https://attacker_c2_server.com
|
||||
$ ./agent -connect https://attacker_c2_server.com:8443
|
||||
```
|
||||
|
||||
> If you want to tunnel the connection over a SOCKS5/HTTP proxy, you can use the `--proxy schema://username:password@ip:port` option.
|
||||
> Examples: `--proxy http://127.0.0.1:8080`, `--proxy http://admin:secret@127.0.0.1:8080`, `--proxy socks5://admin:secret@127.0.0.1:1080`
|
||||
>
|
||||
> HTTP proxy can be used only with websocket protocol.
|
||||
|
||||
A session should appear on the *proxy* server.
|
||||
|
||||
```
|
||||
INFO[0102] Agent joined. name=nchatelain@nworkstation remote="XX.XX.XX.XX:38000"
|
||||
```
|
||||
|
||||
Use the `session` command to select the *agent*.
|
||||
|
||||
```
|
||||
ligolo-ng » session
|
||||
? Specify a session : 1 - nchatelain@nworkstation - XX.XX.XX.XX:38000
|
||||
```
|
||||
|
||||
#### Start the tunneling
|
||||
|
||||
Start the tunnel on the proxy, using the `evil-cha` interface name.
|
||||
|
||||
```
|
||||
[Agent : nchatelain@nworkstation] » tunnel_start --tun evil-cha
|
||||
[Agent : nchatelain@nworkstation] » INFO[0690] Starting tunnel to nchatelain@nworkstation
|
||||
```
|
||||
> On macOS, you need to specify a utun[0-9] device, like utun4.
|
||||
|
||||
#### Setup routing
|
||||
|
||||
First, display the network configuration of the agent using the `ifconfig` command:
|
||||
|
||||
```
|
||||
[Agent : nchatelain@nworkstation] » ifconfig
|
||||
[...]
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Interface 3 │
|
||||
├──────────────┬──────────────────────────────┤
|
||||
│ Name │ wlp3s0 │
|
||||
│ Hardware MAC │ de:ad:be:ef:ca:fe │
|
||||
│ MTU │ 1500 │
|
||||
│ Flags │ up|broadcast|multicast │
|
||||
│ IPv4 Address │ 192.168.0.30/24 │
|
||||
└──────────────┴──────────────────────────────┘
|
||||
```
|
||||
|
||||
Then setup routes accordingly.
|
||||
|
||||
**Linux**:
|
||||
|
||||
*Using the terminal:*
|
||||
```shell
|
||||
$ sudo ip route add 192.168.0.0/24 dev ligolo
|
||||
```
|
||||
*Or using the Ligolo-ng (>= 0.6) cli:*
|
||||
```
|
||||
ligolo-ng » interface_add_route --name evil-cha --route 192.168.2.0/24
|
||||
INFO[3206] Route created.
|
||||
```
|
||||
|
||||
**Windows**:
|
||||
```
|
||||
> netsh int ipv4 show interfaces
|
||||
|
||||
Idx Mét MTU État Nom
|
||||
--- ---------- ---------- ------------ ---------------------------
|
||||
25 5 65535 connected ligolo
|
||||
|
||||
> route add 192.168.0.0 mask 255.255.255.0 0.0.0.0 if [THE INTERFACE IDX]
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
|
||||
```
|
||||
$ sudo ifconfig utun4 alias [random_ip] 255.255.255.0
|
||||
$ sudo route add -net 192.168.2.0/24 -interface utun4
|
||||
```
|
||||
|
||||
You can now access the *192.168.0.0/24* *agent* network from the *proxy* server.
|
||||
|
||||
```shell
|
||||
$ nmap 192.168.0.0/24 -v -sV -n
|
||||
[...]
|
||||
$ rdesktop 192.168.0.123
|
||||
[...]
|
||||
```
|
||||
|
||||
### Agent Binding/Listening
|
||||
|
||||
You can listen to ports on the *agent* and *redirect* connections to your control/proxy server.
|
||||
|
||||
In a ligolo session, use the `listener_add` command.
|
||||
|
||||
The following example will create a TCP listening socket on the agent (0.0.0.0:1234) and redirect connections to the 4321 port of the proxy server.
|
||||
```
|
||||
[Agent : nchatelain@nworkstation] » listener_add --addr 0.0.0.0:1234 --to 127.0.0.1:4321 --tcp
|
||||
INFO[1208] Listener created on remote agent!
|
||||
```
|
||||
|
||||
On the `proxy`:
|
||||
|
||||
```shell
|
||||
$ nc -lvp 4321
|
||||
```
|
||||
|
||||
When a connection is made on the TCP port `1234` of the agent, `nc` will receive the connection.
|
||||
|
||||
This is very useful when using reverse tcp/udp payloads.
|
||||
|
||||
You can view currently running listeners using the `listener_list` command and stop them using the `listener_stop [ID]` command:
|
||||
|
||||
```
|
||||
[Agent : nchatelain@nworkstation] » listener_list
|
||||
┌───────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Active listeners │
|
||||
├───┬─────────────────────────┬────────────────────────┬────────────────────────┤
|
||||
│ # │ AGENT │ AGENT LISTENER ADDRESS │ PROXY REDIRECT ADDRESS │
|
||||
├───┼─────────────────────────┼────────────────────────┼────────────────────────┤
|
||||
│ 0 │ nchatelain@nworkstation │ 0.0.0.0:1234 │ 127.0.0.1:4321 │
|
||||
└───┴─────────────────────────┴────────────────────────┴────────────────────────┘
|
||||
|
||||
[Agent : nchatelain@nworkstation] » listener_stop 0
|
||||
INFO[1505] Listener closed.
|
||||
```
|
||||
|
||||
### Access to agent's local ports (127.0.0.1)
|
||||
|
||||
If you need to access the local ports of the currently connected agent, there's a "magic" CIDR hardcoded in Ligolo-ng: *240.0.0.0/4* (This is an unused IPv4 subnet).
|
||||
If you query an IP address on this subnet, Ligolo-ng will automatically redirect traffic to the agent's local IP address (127.0.0.1).
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ sudo ip route add 240.0.0.1/32 dev ligolo
|
||||
$ nmap 240.0.0.1 -sV
|
||||
Starting Nmap 7.93 ( https://nmap.org ) at 2023-12-30 22:17 CET
|
||||
Nmap scan report for 240.0.0.1
|
||||
Host is up (0.023s latency).
|
||||
Not shown: 998 closed tcp ports (conn-refused)
|
||||
PORT STATE SERVICE VERSION
|
||||
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
|
||||
8000/tcp open http SimpleHTTPServer 0.6 (Python 3.9.2)
|
||||
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
|
||||
|
||||
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
|
||||
Nmap done: 1 IP address (1 host up) scanned in 7.16 seconds
|
||||
```
|
||||
|
||||
### Agent as server (Bind)
|
||||
|
||||
The Ligolo-ng agent can operate using a bind connection (i.e. acting as a server).
|
||||
|
||||
Instead of using the `--connect [ip:port]` argument, you can use `--bind [ip:port]` so the agent start listening to connections.
|
||||
|
||||
After that, the proxy can connect to the agent using the `connect_agent` command.
|
||||
|
||||
In a terminal:
|
||||
```
|
||||
» ligolo-agent -bind 127.0.0.1:4444
|
||||
WARN[0000] TLS Certificate fingerprint is: 05518ABE4F0D3B137A2365E0DE52A01FE052EE4C5A2FD12D8E2DD93AED1DD04B
|
||||
INFO[0000] Listening on 127.0.0.1:4444...
|
||||
INFO[0005] Got connection from: 127.0.0.1:53908
|
||||
INFO[0005] Connection established addr="127.0.0.1:53908"
|
||||
```
|
||||
|
||||
In ligolo-ng proxy:
|
||||
|
||||
```
|
||||
ligolo-ng » connect_agent --ip 127.0.0.1:4444
|
||||
? TLS Certificate Fingerprint is: 05518ABE4F0D3B137A2365E0DE52A01FE052EE4C5A2FD12D8E2DD93AED1DD04B, connect? Yes
|
||||
INFO[0021] Agent connected. name=nchatelain@nworkstation remote="127.0.0.1:4444"
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
[Ligolo-ng-demo.webm](https://github.com/nicocha30/ligolo-ng/assets/31402213/3070bb7c-0b0d-4c77-9181-cff74fb2f0ba)
|
||||
You will find the documentation for Ligolo-ng, as well as the steps to follow to get it up and running on the [Ligolo-ng Wiki](https://github.com/nicocha30/ligolo-ng/wiki)
|
||||
|
||||
## Does it require Administrator/root access ?
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
You think you've discovered a security problem on Ligolo-ng? Please contact me at nicolas - at - chatelain.me.
|
||||
|
||||
If the problem is serious, and you prefer to use PGP, don't hesitate to ask for my key beforehand.
|
||||
+86
-68
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/hashicorp/yamux"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/controller"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/proxy"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/proxy/netstack"
|
||||
@@ -18,13 +19,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AgentList map[int]*controller.LigoloAgent
|
||||
var AgentListMutex sync.Mutex
|
||||
var ListenerList map[int]controller.Listener
|
||||
var ListenerListMutex sync.Mutex
|
||||
var ProxyController *controller.Controller
|
||||
|
||||
// CurrentAgentID points to the selected agent in the UI (when running session)
|
||||
@@ -50,6 +48,32 @@ func RegisterAgent(agent *controller.LigoloAgent) error {
|
||||
if registeredAgents.Running {
|
||||
go StartTunnel(registeredAgents, registeredAgents.Interface)
|
||||
}
|
||||
|
||||
for lid, listener := range registeredAgents.Listeners {
|
||||
logrus.Info("Restarting listener: %s", listener.String())
|
||||
if err := listener.ResetMultiplexer(registeredAgents.Session); err != nil {
|
||||
logrus.Errorf("Failed to reset yamux: %v", err)
|
||||
}
|
||||
if err := listener.Stop(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
lis, err := proxy.NewListener(registeredAgents.Session, listener.ListenerAddr(), listener.Network(), listener.RedirectAddr())
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
registeredAgents.Listeners[lid] = &lis
|
||||
go func() {
|
||||
err := lis.StartRelay()
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{"listener": lis.String(), "agent": agent.Name}).Error("Listener relay failed with error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{"listener": lis.String(), "agent": agent.Name}).Warning("Listener ended without error.")
|
||||
return
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -58,13 +82,6 @@ func RegisterAgent(agent *controller.LigoloAgent) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnregisterAgent(agent *controller.LigoloAgent) error {
|
||||
/*AgentListMutex.Lock()
|
||||
delete(AgentList, agent.Id)
|
||||
AgentListMutex.Unlock()*/
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartTunnel(agent *controller.LigoloAgent, tunName string) {
|
||||
logrus.Infof("Starting tunnel to %s", agent.Name)
|
||||
ligoloStack, err := proxy.NewLigoloTunnel(netstack.StackSettings{
|
||||
@@ -113,8 +130,6 @@ func StartTunnel(agent *controller.LigoloAgent, tunName string) {
|
||||
func Run() {
|
||||
// AgentList contains all the connected agents
|
||||
AgentList = make(map[int]*controller.LigoloAgent)
|
||||
// ListenerList contains all listener relays
|
||||
ListenerList = make(map[int]controller.Listener)
|
||||
|
||||
App.AddCommand(&grumble.Command{
|
||||
Name: "session",
|
||||
@@ -375,13 +390,18 @@ func Run() {
|
||||
t := table.NewWriter()
|
||||
t.SetStyle(table.StyleLight)
|
||||
t.SetTitle("Active listeners")
|
||||
t.AppendHeader(table.Row{"#", "Agent", "Network", "Agent listener address", "Proxy redirect address"})
|
||||
t.AppendHeader(table.Row{"#", "Agent", "Network", "Agent listener address", "Proxy redirect address", "Status"})
|
||||
|
||||
ListenerListMutex.Lock()
|
||||
for id, listener := range ListenerList {
|
||||
t.AppendRow(table.Row{id, listener.Agent.String(), listener.Network, listener.ListenerAddr, listener.RedirectAddr})
|
||||
for _, agent := range AgentList {
|
||||
for _, listener := range agent.Listeners {
|
||||
status := text.Colors{text.FgGreen}.Sprintf("Online")
|
||||
if agent.Session == nil || agent.Session.IsClosed() {
|
||||
status = text.Colors{text.FgRed}.Sprintf("Offline")
|
||||
}
|
||||
t.AppendRow(table.Row{listener.ID, agent.String(), listener.Network(), listener.ListenerAddr(), listener.RedirectAddr(), status})
|
||||
}
|
||||
}
|
||||
ListenerListMutex.Unlock()
|
||||
|
||||
c.App.Println(t.Render())
|
||||
return nil
|
||||
},
|
||||
@@ -390,35 +410,56 @@ func Run() {
|
||||
App.AddCommand(&grumble.Command{
|
||||
Name: "listener_stop",
|
||||
Help: "Stop a listener",
|
||||
Usage: "listener_stop [id]",
|
||||
Usage: "listener_stop",
|
||||
HelpGroup: "Listeners",
|
||||
Args: func(a *grumble.Args) {
|
||||
a.Int("id", "listener id")
|
||||
},
|
||||
Run: func(c *grumble.Context) error {
|
||||
if _, ok := AgentList[CurrentAgentID]; !ok {
|
||||
return ErrInvalidAgent
|
||||
var session string
|
||||
type LigoloListenerAgent struct {
|
||||
listener *proxy.LigoloListener
|
||||
agent *controller.LigoloAgent
|
||||
}
|
||||
CurrentAgent := AgentList[CurrentAgentID]
|
||||
ListenerListMutex.Lock()
|
||||
if _, ok := ListenerList[c.Args.Int("id")]; !ok {
|
||||
ListenerListMutex.Unlock()
|
||||
return errors.New("invalid listener id")
|
||||
}
|
||||
listener := ListenerList[c.Args.Int("id")]
|
||||
ListenerListMutex.Unlock()
|
||||
listener.Session.Close()
|
||||
listenerMap := make(map[int]LigoloListenerAgent)
|
||||
listenerSelector := &survey.Select{
|
||||
Message: "Specify the listener to stop:",
|
||||
Options: func() (out []string) {
|
||||
AgentListMutex.Lock()
|
||||
i := 0
|
||||
for _, agent := range AgentList {
|
||||
for _, listener := range agent.Listeners {
|
||||
status := text.Colors{text.FgGreen}.Sprintf("Online")
|
||||
if agent.Session == nil || agent.Session.IsClosed() {
|
||||
status = text.Colors{text.FgRed}.Sprintf("Offline")
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%d - Agent: %s - Net: %s - Agent Listener: %s - Redirect: %s [%s]", i, agent.String(), listener.Network(), listener.ListenerAddr(), listener.RedirectAddr(), status))
|
||||
listenerMap[i] = LigoloListenerAgent{listener: listener, agent: agent}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if err := proxy.ListenerStop(CurrentAgent.Session, listener.ListenerID); err != nil {
|
||||
AgentListMutex.Unlock()
|
||||
return
|
||||
}(),
|
||||
}
|
||||
|
||||
err := survey.AskOne(listenerSelector, &session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("Listener closed.")
|
||||
s := strings.Split(session, " ")
|
||||
listenerId, err := strconv.Atoi(s[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete from the Listener List
|
||||
ListenerListMutex.Lock()
|
||||
delete(ListenerList, c.Args.Int("id"))
|
||||
ListenerListMutex.Unlock()
|
||||
if listener, ok := listenerMap[listenerId]; ok {
|
||||
if err := listener.listener.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
listener.agent.DeleteListener(int(listener.listener.ID))
|
||||
} else {
|
||||
return errors.New("invalid listener id")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -434,8 +475,6 @@ func Run() {
|
||||
f.BoolL("udp", false, "Use UDP listener")
|
||||
f.StringL("addr", "", "The agent listening address:port")
|
||||
f.StringL("to", "", "Where to redirect connections")
|
||||
f.BoolL("no-retry", false, "Do not restart relay on listener error")
|
||||
|
||||
},
|
||||
Run: func(c *grumble.Context) error {
|
||||
if _, ok := AgentList[CurrentAgentID]; !ok {
|
||||
@@ -472,43 +511,22 @@ func Run() {
|
||||
return err
|
||||
}
|
||||
|
||||
proxyListener, err := proxy.NewListener(CurrentAgent.Session, c.Flags.String("addr"), netProto, c.Flags.String("to"))
|
||||
proxyListener, err := CurrentAgent.AddListener(c.Flags.String("addr"), netProto, c.Flags.String("to"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("Listener %d created on remote agent!", proxyListener.ID)
|
||||
|
||||
// Register the listener in the UI
|
||||
listener := controller.Listener{
|
||||
Agent: *CurrentAgent,
|
||||
Network: netProto,
|
||||
ListenerAddr: c.Flags.String("addr"),
|
||||
RedirectAddr: c.Flags.String("to"),
|
||||
Session: proxyListener.Conn,
|
||||
ListenerID: proxyListener.ID,
|
||||
}
|
||||
ListenerListMutex.Lock()
|
||||
ListenerList[controller.ListenerCounter] = listener
|
||||
ListenerListMutex.Unlock()
|
||||
controller.ListenerCounter++
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := proxyListener.StartRelay()
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{"listener": listener.String()}).Error("Listener relay failed with error: ", err)
|
||||
if !c.Flags.Bool("no-retry") {
|
||||
logrus.Warning("Listener failed. Restarting in 5 seconds...")
|
||||
time.Sleep(time.Second * 5)
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{"listener": listener.String()}).Warning("Listener ended without error.")
|
||||
err := proxyListener.StartRelay()
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{"listener": proxyListener.String(), "agent": CurrentAgent.Name}).Error("Listener relay failed with error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{"listener": proxyListener.String(), "agent": CurrentAgent.Name}).Warning("Listener ended without error.")
|
||||
return
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,4 +8,5 @@ var App = grumble.New(&grumble.Config{
|
||||
Description: "Ligolo-ng - An advanced, yet simple tunneling tool",
|
||||
HelpHeadlineUnderline: true,
|
||||
HelpSubCommands: true,
|
||||
HistoryFile: "ligolo-ng.history",
|
||||
})
|
||||
|
||||
@@ -2,11 +2,14 @@ package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/proxy/netstack/tun"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/utils/codenames"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -24,7 +27,6 @@ func init() {
|
||||
t.SetTitle("Available tuntaps")
|
||||
t.AppendHeader(table.Row{"#", "Tap Name", "Dst routes"})
|
||||
|
||||
AgentListMutex.Lock()
|
||||
tuntaps, err := tun.GetTunTaps()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -36,7 +38,6 @@ func init() {
|
||||
}
|
||||
t.AppendRow(table.Row{i, tuntap.Name(), strings.Join(prettyRoute, ",")})
|
||||
}
|
||||
AgentListMutex.Unlock()
|
||||
App.Println(t.Render())
|
||||
return nil
|
||||
},
|
||||
@@ -61,7 +62,7 @@ func init() {
|
||||
return err
|
||||
}
|
||||
|
||||
ifName = codenames.Generate(rng, 0)
|
||||
ifName = codenames.Generate(rng)
|
||||
}
|
||||
logrus.Infof("Creating a new \"%s\" interface...", ifName)
|
||||
if err := tun.CreateTUN(ifName); err != nil {
|
||||
@@ -146,7 +147,39 @@ func init() {
|
||||
|
||||
routeCidr := c.Flags.String("route")
|
||||
if routeCidr == "" {
|
||||
return errors.New("please specify a route")
|
||||
tuntaps, err := tun.GetTunTaps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var possibleRoutes []string
|
||||
for _, tuntap := range tuntaps {
|
||||
for _, route := range tuntap.Routes() {
|
||||
possibleRoutes = append(possibleRoutes, fmt.Sprintf("%s (%s)", route.Dst.String(), tuntap.Name()))
|
||||
}
|
||||
}
|
||||
if len(possibleRoutes) == 0 {
|
||||
return errors.New("no routes available")
|
||||
}
|
||||
|
||||
routePrompt := &survey.MultiSelect{
|
||||
Message: "Select routes to delete:",
|
||||
Options: possibleRoutes,
|
||||
}
|
||||
var selectedRoutes []string
|
||||
if err := survey.AskOne(routePrompt, &selectedRoutes); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, selectedRoute := range selectedRoutes {
|
||||
route := strings.Split(selectedRoute, " ")[0]
|
||||
ifByRoute, err := tun.GetTunByRoute(route)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to get tuntap by route \"%s\": %v", route, err)
|
||||
}
|
||||
if err := ifByRoute.DelRoute(route); err != nil {
|
||||
logrus.Errorf("Failed to delete route \"%s\": %v", route, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
ifName := c.Flags.String("name")
|
||||
if ifName == "" {
|
||||
@@ -170,4 +203,109 @@ func init() {
|
||||
},
|
||||
})
|
||||
|
||||
App.AddCommand(&grumble.Command{
|
||||
Name: "autoroute",
|
||||
Help: "Setup everything for you (interfaces, routes & tunnel)",
|
||||
HelpGroup: "Tunneling",
|
||||
Usage: "autoroute",
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.BoolL("with-ipv6", false, "Include IPv6 addresses")
|
||||
},
|
||||
Run: func(c *grumble.Context) error {
|
||||
|
||||
if _, ok := AgentList[CurrentAgentID]; !ok {
|
||||
return ErrInvalidAgent
|
||||
}
|
||||
CurrentAgent := AgentList[CurrentAgentID]
|
||||
// Note: Network information is not refreshed when calling this command
|
||||
if CurrentAgent.Session == nil {
|
||||
return ErrInvalidAgent
|
||||
}
|
||||
var possibleRoutes []string
|
||||
for _, ifaceInfo := range CurrentAgent.Network {
|
||||
for _, address := range ifaceInfo.Addresses {
|
||||
ip, _, err := net.ParseCIDR(address)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !ip.IsLoopback() {
|
||||
if ip.To4() != nil || c.Flags.Bool("with-ipv6") {
|
||||
possibleRoutes = append(possibleRoutes, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
routePrompt := &survey.MultiSelect{
|
||||
Message: "Select routes to add:",
|
||||
Options: possibleRoutes,
|
||||
}
|
||||
var selectedRoutes []string
|
||||
if err := survey.AskOne(routePrompt, &selectedRoutes); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(selectedRoutes) == 0 {
|
||||
return errors.New("no route selected")
|
||||
}
|
||||
var ifaceSelectionPrompt string
|
||||
if err := survey.AskOne(&survey.Select{Message: "Create a new interface or use an existing one?", Options: []string{"Create a new interface", "Use an existing one"}}, &ifaceSelectionPrompt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var selectedIface string
|
||||
if ifaceSelectionPrompt == "Create a new interface" {
|
||||
logrus.Info("Generating a random interface name...")
|
||||
rng, err := codenames.DefaultRNG()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ifName := codenames.Generate(rng)
|
||||
|
||||
logrus.Infof("Creating a new \"%s\" interface...", ifName)
|
||||
if err := tun.CreateTUN(ifName); err != nil {
|
||||
return err
|
||||
}
|
||||
selectedIface = ifName
|
||||
} else {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ifaceNames []string
|
||||
for _, iface := range ifaces {
|
||||
ifaceNames = append(ifaceNames, iface.Name)
|
||||
}
|
||||
if err := survey.AskOne(&survey.Select{Message: "Select the interface to use", Options: ifaceNames}, &selectedIface); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logrus.Infof("Using interface %s, creating routes...", selectedIface)
|
||||
stun, err := tun.GetTunByName(selectedIface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, route := range selectedRoutes {
|
||||
if err := stun.AddRoute(route); err != nil {
|
||||
logrus.Errorf("Could not add route %s: %v", route, err)
|
||||
continue
|
||||
}
|
||||
logrus.Infof("Route %s created.", route)
|
||||
}
|
||||
|
||||
startTunnel := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: "Start the tunnel?",
|
||||
}
|
||||
survey.AskOne(prompt, &startTunnel)
|
||||
|
||||
if startTunnel {
|
||||
go StartTunnel(CurrentAgent, selectedIface)
|
||||
} else {
|
||||
logrus.Infof("You can start the tunnel with: start --tun %s", selectedIface)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
+25
-14
@@ -4,11 +4,9 @@ import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/yamux"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/protocol"
|
||||
"net"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/proxy"
|
||||
)
|
||||
|
||||
var ListenerCounter = 0
|
||||
|
||||
type LigoloAgent struct {
|
||||
Name string
|
||||
Network []protocol.NetInterface
|
||||
@@ -17,24 +15,37 @@ type LigoloAgent struct {
|
||||
CloseChan chan bool
|
||||
Interface string
|
||||
Running bool
|
||||
Listeners []*proxy.LigoloListener
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
Agent LigoloAgent
|
||||
Network string
|
||||
ListenerAddr string
|
||||
RedirectAddr string
|
||||
|
||||
Session net.Conn
|
||||
ListenerID int32
|
||||
func (la *LigoloAgent) AddListener(addr string, network string, to string) (*proxy.LigoloListener, error) {
|
||||
proxyListener, err := proxy.NewListener(la.Session, addr, network, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
la.Listeners = append(la.Listeners, &proxyListener)
|
||||
return &proxyListener, nil
|
||||
}
|
||||
|
||||
func (l Listener) String() string {
|
||||
return fmt.Sprintf("[%s] (%s) [Agent] %s => [Proxy] %s", l.Agent.Name, l.Network, l.ListenerAddr, l.RedirectAddr)
|
||||
func (la *LigoloAgent) GetListener(id int) *proxy.LigoloListener {
|
||||
for _, listener := range la.Listeners {
|
||||
if listener.ID == int32(id) {
|
||||
return listener
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (la *LigoloAgent) DeleteListener(id int) {
|
||||
for i, listener := range la.Listeners {
|
||||
if listener.ID == int32(id) {
|
||||
la.Listeners = append(la.Listeners[:i], la.Listeners[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (la *LigoloAgent) String() string {
|
||||
raddr := "Disconnected"
|
||||
raddr := "[Offline]"
|
||||
if la.Session != nil {
|
||||
raddr = la.Session.RemoteAddr().String()
|
||||
}
|
||||
|
||||
+60
-31
@@ -3,6 +3,7 @@ package proxy
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/yamux"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/protocol"
|
||||
"github.com/nicocha30/ligolo-ng/pkg/relay"
|
||||
@@ -11,37 +12,6 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func ListenerStop(sess *yamux.Session, listenerId int32) error {
|
||||
// Open Yamux connection
|
||||
yamuxConnectionSession, err := sess.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ligoloProtocol := protocol.NewEncoderDecoder(yamuxConnectionSession)
|
||||
|
||||
// Send close request
|
||||
closeRequest := protocol.ListenerCloseRequestPacket{ListenerID: listenerId}
|
||||
if err := ligoloProtocol.Encode(protocol.Envelope{
|
||||
Type: protocol.MessageListenerCloseRequest,
|
||||
Payload: closeRequest,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Process close response
|
||||
if err := ligoloProtocol.Decode(); err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
response := ligoloProtocol.Envelope.Payload
|
||||
|
||||
if err := response.(protocol.ListenerCloseResponsePacket).Err; err != false {
|
||||
return errors.New(response.(protocol.ListenerCloseResponsePacket).ErrString)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LigoloListener struct {
|
||||
ID int32
|
||||
ctx context.Context
|
||||
@@ -81,6 +51,64 @@ func NewListener(sess *yamux.Session, addr string, network string, to string) (L
|
||||
return LigoloListener{ID: response.ListenerID, sess: sess, Conn: conn, addr: addr, network: network, to: to}, nil
|
||||
}
|
||||
|
||||
func (l *LigoloListener) ResetMultiplexer(sess *yamux.Session) error {
|
||||
// Change the listener session, used in session recovery mechanism
|
||||
l.sess = sess
|
||||
conn, err := sess.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Conn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LigoloListener) RedirectAddr() string {
|
||||
return l.to
|
||||
}
|
||||
|
||||
func (l *LigoloListener) ListenerAddr() string {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
func (l *LigoloListener) Network() string {
|
||||
return l.network
|
||||
}
|
||||
|
||||
func (l *LigoloListener) String() string {
|
||||
return fmt.Sprintf("[#%d] (%s) [Agent] %s => [Proxy] %s", l.ID, l.network, l.addr, l.to)
|
||||
}
|
||||
|
||||
func (l *LigoloListener) Stop() error {
|
||||
// Open Yamux connection
|
||||
yamuxConnectionSession, err := l.sess.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ligoloProtocol := protocol.NewEncoderDecoder(yamuxConnectionSession)
|
||||
|
||||
// Send close request
|
||||
closeRequest := protocol.ListenerCloseRequestPacket{ListenerID: l.ID}
|
||||
if err := ligoloProtocol.Encode(protocol.Envelope{
|
||||
Type: protocol.MessageListenerCloseRequest,
|
||||
Payload: closeRequest,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Process close response
|
||||
if err := ligoloProtocol.Decode(); err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
response := ligoloProtocol.Envelope.Payload
|
||||
|
||||
if err := response.(protocol.ListenerCloseResponsePacket).Err; err != false {
|
||||
return errors.New(response.(protocol.ListenerCloseResponsePacket).ErrString)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LigoloListener) StartRelay() error {
|
||||
if l.network == "tcp" {
|
||||
return l.relayTCP()
|
||||
@@ -97,6 +125,7 @@ func (l *LigoloListener) relayTCP() error {
|
||||
if err := ligoloProtocol.Decode(); err != nil {
|
||||
if err == io.EOF {
|
||||
// Listener closed.
|
||||
logrus.Debug("Listener closed connection (EOF)")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -37,13 +37,10 @@ func DefaultRNG() (*rand.Rand, error) {
|
||||
}
|
||||
|
||||
// Generate generates and returns a random hero name.
|
||||
// Eventually you can specify a `tokenLength` greater
|
||||
// then zero to generate and additional token and create
|
||||
// even more entropy.
|
||||
func Generate(rng *rand.Rand, tokenLength int) string {
|
||||
func Generate(rng *rand.Rand) string {
|
||||
res := fmt.Sprintf("%s%s", randomAdjective(rng), randomNoun(rng))
|
||||
if tokenLength > 0 {
|
||||
res = fmt.Sprintf("%s-%s", res, randomToken(rng, tokenLength))
|
||||
if len(res) > 15 {
|
||||
return res[:15]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user