shadow1ng/fscan · error

Invalid AV_PAIR list

Error message

Invalid AV_PAIR list

What it means

After reading each AV_PAIR in the NTLM CHALLENGE TargetInfo list, recvChallenge advances currIdx and expects another AV_PAIR header to still fit inside the TargetInfo block. This error means the AV_PAIR walk ran past the declared TargetInfo end — the list is not terminated/aligned as the NTLM spec requires, so the library refuses to continue parsing rather than reading garbage.

Source

Thrown at libs/grdp/protocol/tpkt/tpkt.go:257

			return err
		}
		currIdx := startIdx
		for avPair.AvID != 0 {
			if field, exists := AvIDMap[avPair.AvID]; exists {
				var value string
				r := response[currIdx+avPairLen : currIdx+avPairLen+int(avPair.AvLen)]
				if avPair.AvID == 7 {
					unixStamp := binary.LittleEndian.Uint64(r)/10000000 - 11644473600
					tm := time.Unix(int64(unixStamp), 0)
					value = tm.Format("2006-01-02 15:04:05")
				} else {
					value = strings.ReplaceAll(string(r), "\x00", "")
				}
				info[field] = value
			}
			currIdx += avPairLen + int(avPair.AvLen)
			if currIdx+avPairLen > startIdx+targetInfoLen {
				return fmt.Errorf("Invalid AV_PAIR list")
			}
			avPairBuf = bytes.NewBuffer(response[currIdx : currIdx+avPairLen])
			err = binary.Read(avPairBuf, binary.LittleEndian, &avPair)
			if err != nil {
				return err
			}
		}
	}
	glog.Info("get os info by NLA done !")
	glog.Info("=======================================")
	for key, value := range info {
		glog.Info(key, ":", value)
	}
	glog.Info("=======================================")

	//判断是否存在windows域
	if netBiosDomainName, exists := info["NetBIOSDomainName"]; exists {
		if netBiosComputerName, exists := info["NetBIOSComputerName"]; exists {

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Ensure the full NTLM CHALLENGE packet is read before parsing (buffer/reassemble instead of a single 1024-byte Read)
  2. Check that avPair.AvLen is parsed from the correct offset so currIdx advances by exactly avPairLen+AvLen
  3. Test against a known-good Windows RDP host to isolate server-side nonconformance
  4. Log startIdx, targetInfoLen, and currIdx at failure to confirm whether the server data or the parser is at fault

Example fix

// before
currIdx += avPairLen + int(avPair.AvLen)
if currIdx+avPairLen > startIdx+targetInfoLen {
    return fmt.Errorf("Invalid AV_PAIR list")
}
// after
currIdx += avPairLen + int(avPair.AvLen)
if currIdx == startIdx+targetInfoLen {
    break // reached end of TargetInfo normally
}
if currIdx+avPairLen > startIdx+targetInfoLen {
    return fmt.Errorf("Invalid AV_PAIR list")
}
Defensive patterns

Strategy: validation

Validate before calling

func avPairsWellFormed(targetInfo []byte) bool {
    i := 0
    for i+4 <= len(targetInfo) {
        avLen := int(binary.LittleEndian.Uint16(targetInfo[i+2 : i+4]))
        i += 4 + avLen
        if i == len(targetInfo) { return true }
        if i+4 > len(targetInfo) { return false }
    }
    return false
}

Try / catch

if err := client.Login(host, user, pass); err != nil {
    if strings.Contains(err.Error(), "Invalid AV_PAIR") {
        return fmt.Errorf("server sent malformed NTLM TargetInfo; try TLS-only security level: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: StartNLA → recvChallenge against a server whose TargetInfo AV_PAIR chain has an AvLen that pushes iteration beyond startIdx+targetInfoLen, or whose TargetInfo bytes were truncated/misparsed so the terminator AV_PAIR (MsvAvEOL) is never reached in bounds.

Common situations: Servers with unusual AV_PAIR sets (e.g. Linux rdesktop-style servers, tight gateways); a prior parsing bug shifting currIdx by a few bytes; truncated network reads delivering partial TargetInfo.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/30372e21f7a2c88d. Report an issue: GitHub.