108 lines
2.9 KiB
C#
108 lines
2.9 KiB
C#
using System.Text;
|
|
|
|
namespace WxAgent.Core;
|
|
|
|
public sealed record WcdbKeyCandidate(string EncKey, string? SaltHint);
|
|
|
|
public sealed class WcdbKeyCandidateScanner
|
|
{
|
|
private const int KeyHexLength = 64;
|
|
private const int SaltHexLength = 32;
|
|
|
|
private readonly StringBuilder _keyPrefix = new(KeyHexLength);
|
|
private readonly char[] _saltSuffix = new char[SaltHexLength];
|
|
private readonly HashSet<string> _seen = new(StringComparer.Ordinal);
|
|
private long _runLength;
|
|
private readonly bool _utf16;
|
|
private bool _hasLowByte;
|
|
private byte _lowByte;
|
|
|
|
public WcdbKeyCandidateScanner(bool utf16 = false) => _utf16 = utf16;
|
|
|
|
public IReadOnlyList<WcdbKeyCandidate> Feed(ReadOnlySpan<byte> bytes, bool finalBlock = false)
|
|
{
|
|
var found = new List<WcdbKeyCandidate>();
|
|
foreach (var rawValue in bytes)
|
|
{
|
|
var value = rawValue;
|
|
if (_utf16)
|
|
{
|
|
if (!_hasLowByte)
|
|
{
|
|
_lowByte = value;
|
|
_hasLowByte = true;
|
|
continue;
|
|
}
|
|
_hasLowByte = false;
|
|
if (value != 0)
|
|
{
|
|
CompleteRun(found);
|
|
continue;
|
|
}
|
|
value = _lowByte;
|
|
}
|
|
if (IsAsciiHex(value))
|
|
{
|
|
AppendHex(value);
|
|
}
|
|
else
|
|
{
|
|
CompleteRun(found);
|
|
}
|
|
}
|
|
|
|
if (finalBlock)
|
|
{
|
|
CompleteRun(found);
|
|
_hasLowByte = false;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
private void AppendHex(byte value)
|
|
{
|
|
var character = char.ToLowerInvariant((char)value);
|
|
if (_runLength < KeyHexLength)
|
|
{
|
|
_keyPrefix.Append(character);
|
|
}
|
|
|
|
_saltSuffix[_runLength % SaltHexLength] = character;
|
|
_runLength++;
|
|
}
|
|
|
|
private void CompleteRun(List<WcdbKeyCandidate> found)
|
|
{
|
|
if (_runLength >= KeyHexLength)
|
|
{
|
|
var key = _keyPrefix.ToString();
|
|
var salt = _runLength >= KeyHexLength + SaltHexLength ? GetSaltSuffix() : null;
|
|
if (_seen.Add(key + ":" + salt))
|
|
{
|
|
found.Add(new WcdbKeyCandidate(key, salt));
|
|
}
|
|
}
|
|
|
|
_keyPrefix.Clear();
|
|
_runLength = 0;
|
|
}
|
|
|
|
private string GetSaltSuffix()
|
|
{
|
|
var salt = new char[SaltHexLength];
|
|
var start = (int)(_runLength % SaltHexLength);
|
|
for (var index = 0; index < salt.Length; index++)
|
|
{
|
|
salt[index] = _saltSuffix[(start + index) % SaltHexLength];
|
|
}
|
|
|
|
return new string(salt);
|
|
}
|
|
|
|
private static bool IsAsciiHex(byte value) =>
|
|
value is >= (byte)'0' and <= (byte)'9' or
|
|
>= (byte)'a' and <= (byte)'f' or
|
|
>= (byte)'A' and <= (byte)'F';
|
|
}
|