Files

111 lines
4.4 KiB
C#

using System.Security.Cryptography;
using System.Text;
using WxAgent.Core;
using Xunit;
namespace WxAgent.Core.Tests;
public sealed class MemoryScanningTests
{
private static byte[] EncodedLiteral(string literal)
{
var bytes = Encoding.ASCII.GetBytes(literal);
var mask = Convert.FromHexString("d2c7442458020000004889442450488b450048844c2448488944254048584c24");
for (var i = 0; i < bytes.Length; i++) bytes[i] ^= mask[i % mask.Length];
return bytes;
}
[Fact]
public void ConfigCipherDecodeFindsKeyAndEmbeddedSalt()
{
var key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
var salt = "101112131415161718191a1b1c1d1e1f";
var decoded = WcdbConfigCipher.Decode(EncodedLiteral($"x'{key}{salt}'"));
Assert.Equal(new WcdbKeyCandidate(key, salt), Assert.Single(decoded));
}
[Fact]
public void ConfigCipherDecodeWindowsOnLongRuns()
{
var key = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
var salt = "101112131415161718191a1b1c1d1e1f";
var run = key + salt + key + salt;
var decoded = WcdbConfigCipher.Decode(EncodedLiteral($"x'{run}'"));
Assert.Contains(decoded, item => item.EncKey == key && item.SaltHint == salt);
Assert.Equal(4, decoded.Count);
}
[Fact]
public void ConfigCipherDecodeRejectsBoundsAndUniformKeys()
{
Assert.Empty(WcdbConfigCipher.Decode([]));
Assert.Empty(WcdbConfigCipher.Decode(EncodedLiteral("x'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'" + "00000000000000000000000000000000")));
Assert.Empty(WcdbConfigCipher.Decode(EncodedLiteral("plain")));
Assert.Empty(WcdbConfigCipher.Decode(EncodedLiteral("x''")));
var tooLong = EncodedLiteral($"x'{new string('e', 200)}'");
Assert.Empty(WcdbConfigCipher.Decode(tooLong));
}
[Fact]
public void ConfigCipherDecodeRejectsBlobsOverLimit()
{
var literal = $"x'{new string('f', 64)}{new string('1', 32)}'";
var blob = new List<byte>(EncodedLiteral(literal));
while (blob.Count <= WcdbConfigCipher.MaximumBlobLength) blob.Insert(0, 0x00);
Assert.Empty(WcdbConfigCipher.Decode(blob.ToArray()));
}
[Fact]
public void PatternScannerMatchesAcrossChunkBoundaries()
{
var needle = Encoding.ASCII.GetBytes("com.Tencent.WCDB.Config.Cipher");
var content = Encoding.ASCII.GetBytes("prefix " + Encoding.ASCII.GetString(needle) + " suffix");
var split = 10;
var scanner = new MemoryPatternScanner(needle);
var matches = scanner.Feed(content.AsSpan(0, split), 0x1000).ToList();
matches.AddRange(scanner.Feed(content.AsSpan(split), 0x1000 + (ulong)split));
Assert.Equal([0x1000 + 7UL], matches);
}
[Fact]
public void PatternScannerReportsMatchesInSecondChunk()
{
var needle = Encoding.ASCII.GetBytes("abc");
var content = Encoding.ASCII.GetBytes("xxabc");
var scanner = new MemoryPatternScanner(needle);
var matches = scanner.Feed(content.AsSpan(0, 3), 0x2000).ToList();
matches.AddRange(scanner.Feed(content.AsSpan(3), 0x2003));
Assert.Equal([0x2002UL], matches);
}
[Fact]
public void PatternScannerDoesNotJoinAcrossGaps()
{
var needle = Encoding.ASCII.GetBytes("abc");
var scanner = new MemoryPatternScanner(needle);
var matches = scanner.Feed("a"u8[..1], 0x3000).ToList();
matches.AddRange(scanner.Feed("b"u8[..1], 0x4000)); // Gap: end was 0x3001, next feed at 0x4000.
matches.AddRange(scanner.Feed("c"u8[..1], 0x4001));
Assert.Empty(matches);
}
[Fact]
public void PatternScannerFindsMatchSpanningThreeChunks()
{
var needle = Encoding.ASCII.GetBytes("abcde");
var content = Encoding.ASCII.GetBytes("xxabcde");
var scanner = new MemoryPatternScanner(needle);
var matches = scanner.Feed(content.AsSpan(0, 3), 0x5000).ToList();
matches.AddRange(scanner.Feed(content.AsSpan(3, 3), 0x5003));
matches.AddRange(scanner.Feed(content.AsSpan(6), 0x5006));
Assert.Equal([0x5002UL], matches);
}
[Fact]
public void DecodeProducesNoMatchAfterPatternError()
{
// Simulates the reference's own fail-open behavior after a failed read.
var decoded = WcdbConfigCipher.Decode(Encoding.ASCII.GetBytes("x'abc'"));
Assert.Empty(decoded);
}
}