rebase with ws

This commit is contained in:
Nicolas Chatelain
2024-08-04 15:01:16 +02:00
parent 30606f4bae
commit d000d62d65
9 changed files with 363 additions and 429 deletions
+24
View File
@@ -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>&nbsp;&nbsp;'
token: ${{ secrets.PAT }}
file: 'README.md'
- name: Deploy to GitHub Pages 🚀
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: main
folder: '.'
+15 -306
View File
@@ -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 ?
+7
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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",
})
+142 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
+3 -6
View File
@@ -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
}