Support Confuser 1.9 r77172

This commit is contained in:
de4dot 2013-01-20 15:59:30 +01:00
parent 40083ad33a
commit 3eb7e5be41
7 changed files with 214 additions and 14 deletions

View File

@ -19,6 +19,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using SevenZip.Compression.LZMA;
using dnlib.IO; using dnlib.IO;
using dnlib.DotNet; using dnlib.DotNet;
using dnlib.DotNet.Emit; using dnlib.DotNet.Emit;
@ -171,5 +173,84 @@ namespace de4dot.code.deobfuscators.Confuser {
} }
return count; return count;
} }
public static byte[] SevenZipDecompress(byte[] data) {
var reader = new BinaryReader(new MemoryStream(data));
var props = reader.ReadBytes(5);
var decoder = new Decoder();
decoder.SetDecoderProperties(props);
long totalSize = reader.ReadInt64();
long compressedSize = data.Length - props.Length - 8;
var decompressed = new byte[totalSize];
decoder.Code(reader.BaseStream, new MemoryStream(decompressed, true), compressedSize, totalSize, null);
return decompressed;
}
// Finds the Lzma type by finding an instruction that allocates a new Lzma.Decoder
public static TypeDef FindLzmaType(MethodDef method) {
if (method == null || method.Body == null)
return null;
foreach (var instr in method.Body.Instructions) {
if (instr.OpCode.Code != Code.Newobj)
continue;
var ctor = instr.Operand as MethodDef;
if (ctor == null)
continue;
var ctorType = ctor.DeclaringType;
if (ctorType == null)
continue;
if (!IsLzmaType(ctorType.DeclaringType))
continue;
return ctorType.DeclaringType;
}
return null;
}
static bool IsLzmaType(TypeDef type) {
if (type == null)
return false;
if (type.NestedTypes.Count != 6)
return false;
if (!CheckLzmaMethods(type))
return false;
if (FindLzmaOutWindowType(type.NestedTypes) == null)
return false;
return true;
}
static bool CheckLzmaMethods(TypeDef type) {
int methods = 0;
foreach (var m in type.Methods) {
if (m.IsStaticConstructor)
continue;
if (m.IsInstanceConstructor) {
if (m.MethodSig.GetParamCount() != 0)
return false;
continue;
}
if (!DotNetUtils.IsMethod(m, "System.UInt32", "(System.UInt32)"))
return false;
methods++;
}
return methods == 1;
}
static readonly string[] outWindowFields = new string[] {
"System.Byte[]",
"System.UInt32",
"System.IO.Stream",
};
static TypeDef FindLzmaOutWindowType(IEnumerable<TypeDef> types) {
foreach (var type in types) {
if (new FieldTypes(type).Exactly(outWindowFields))
return type;
}
return null;
}
} }
} }

View File

@ -46,7 +46,7 @@ namespace de4dot.code.deobfuscators.Confuser {
return null; return null;
} }
public static FieldDef FindDataField(MethodDef method, TypeDef declaringType) { public static FieldDef FindDataField_v18_r75367(MethodDef method, TypeDef declaringType) {
var instrs = method.Body.Instructions; var instrs = method.Body.Instructions;
for (int i = 0; i < instrs.Count - 1; i++) { for (int i = 0; i < instrs.Count - 1; i++) {
var callvirt = instrs[i]; var callvirt = instrs[i];
@ -70,6 +70,31 @@ namespace de4dot.code.deobfuscators.Confuser {
return null; return null;
} }
// Normal ("safe") mode only (not dynamic or native)
public static FieldDef FindDataField_v19_r77172(MethodDef method, TypeDef declaringType) {
var instrs = method.Body.Instructions;
for (int i = 0; i < instrs.Count - 1; i++) {
var ldloc = instrs[i];
if (!ldloc.IsLdloc())
continue;
var local = ldloc.GetLocal(method.Body.Variables);
if (local == null || local.Type.GetFullName() != "System.Byte[]")
continue;
var stsfld = instrs[i + 1];
if (stsfld.OpCode.Code != Code.Stsfld)
continue;
var field = stsfld.Operand as FieldDef;
if (field == null || field.DeclaringType != declaringType)
continue;
if (field.FieldType.FullName != "System.Byte[]")
continue;
return field;
}
return null;
}
public static FieldDef FindStreamField(MethodDef method, TypeDef declaringType) { public static FieldDef FindStreamField(MethodDef method, TypeDef declaringType) {
return FindStreamField(method, declaringType, "System.IO.Stream"); return FindStreamField(method, declaringType, "System.IO.Stream");
} }

View File

@ -38,6 +38,7 @@ namespace de4dot.code.deobfuscators.Confuser {
MethodDefAndDeclaringTypeDict<DecrypterInfo> decrypters = new MethodDefAndDeclaringTypeDict<DecrypterInfo>(); MethodDefAndDeclaringTypeDict<DecrypterInfo> decrypters = new MethodDefAndDeclaringTypeDict<DecrypterInfo>();
uint key0, key0d; uint key0, key0d;
MethodDef nativeMethod; MethodDef nativeMethod;
TypeDef lzmaType;
EmbeddedResource resource; EmbeddedResource resource;
byte[] constants; byte[] constants;
ConfuserVersion version = ConfuserVersion.Unknown; ConfuserVersion version = ConfuserVersion.Unknown;
@ -50,6 +51,9 @@ namespace de4dot.code.deobfuscators.Confuser {
v18_r75369_normal, v18_r75369_normal,
v18_r75369_dynamic, v18_r75369_dynamic,
v18_r75369_native, v18_r75369_native,
v19_r77172_normal,
v19_r77172_dynamic,
v19_r77172_native,
} }
public class DecrypterInfo { public class DecrypterInfo {
@ -105,6 +109,9 @@ namespace de4dot.code.deobfuscators.Confuser {
case ConfuserVersion.v18_r75369_normal: case ConfuserVersion.v18_r75369_normal:
case ConfuserVersion.v18_r75369_dynamic: case ConfuserVersion.v18_r75369_dynamic:
case ConfuserVersion.v18_r75369_native: case ConfuserVersion.v18_r75369_native:
case ConfuserVersion.v19_r77172_normal:
case ConfuserVersion.v19_r77172_dynamic:
case ConfuserVersion.v19_r77172_native:
return Hash1(key0l * magic); return Hash1(key0l * magic);
default: default:
throw new ApplicationException("Invalid version"); throw new ApplicationException("Invalid version");
@ -151,6 +158,10 @@ namespace de4dot.code.deobfuscators.Confuser {
get { return nativeMethod; } get { return nativeMethod; }
} }
public TypeDef LzmaType {
get { return lzmaType; }
}
public EmbeddedResource Resource { public EmbeddedResource Resource {
get { return resource; } get { return resource; }
} }
@ -177,7 +188,9 @@ namespace de4dot.code.deobfuscators.Confuser {
if ((dictField = ConstantsDecrypterUtils.FindDictField(cctor, cctor.DeclaringType)) == null) if ((dictField = ConstantsDecrypterUtils.FindDictField(cctor, cctor.DeclaringType)) == null)
return; return;
if ((dataField = ConstantsDecrypterUtils.FindDataField(cctor, cctor.DeclaringType)) == null)
if ((dataField = ConstantsDecrypterUtils.FindDataField_v18_r75367(cctor, cctor.DeclaringType)) == null &&
(dataField = ConstantsDecrypterUtils.FindDataField_v19_r77172(cctor, cctor.DeclaringType)) == null)
return; return;
nativeMethod = FindNativeMethod(cctor, cctor.DeclaringType); nativeMethod = FindNativeMethod(cctor, cctor.DeclaringType);
@ -189,8 +202,13 @@ namespace de4dot.code.deobfuscators.Confuser {
var info = new DecrypterInfo(this, method, ConfuserVersion.Unknown); var info = new DecrypterInfo(this, method, ConfuserVersion.Unknown);
if (FindKeys_v18_r75367(info)) if (FindKeys_v18_r75367(info))
InitVersion(cctor, ConfuserVersion.v18_r75367_normal, ConfuserVersion.v18_r75367_dynamic, ConfuserVersion.v18_r75367_native); InitVersion(cctor, ConfuserVersion.v18_r75367_normal, ConfuserVersion.v18_r75367_dynamic, ConfuserVersion.v18_r75367_native);
else if (FindKeys_v18_r75369(info)) else if (FindKeys_v18_r75369(info)) {
lzmaType = ConfuserUtils.FindLzmaType(cctor);
if (lzmaType == null)
InitVersion(cctor, ConfuserVersion.v18_r75369_normal, ConfuserVersion.v18_r75369_dynamic, ConfuserVersion.v18_r75369_native); InitVersion(cctor, ConfuserVersion.v18_r75369_normal, ConfuserVersion.v18_r75369_dynamic, ConfuserVersion.v18_r75369_native);
else
InitVersion(cctor, ConfuserVersion.v19_r77172_normal, ConfuserVersion.v19_r77172_dynamic, ConfuserVersion.v19_r77172_native);
}
else else
return; return;
@ -380,6 +398,9 @@ namespace de4dot.code.deobfuscators.Confuser {
case ConfuserVersion.v18_r75369_normal: case ConfuserVersion.v18_r75369_normal:
case ConfuserVersion.v18_r75369_dynamic: case ConfuserVersion.v18_r75369_dynamic:
case ConfuserVersion.v18_r75369_native: case ConfuserVersion.v18_r75369_native:
case ConfuserVersion.v19_r77172_normal:
case ConfuserVersion.v19_r77172_dynamic:
case ConfuserVersion.v19_r77172_native:
return FindKeys_v18_r75369(info); return FindKeys_v18_r75369(info);
default: default:
throw new ApplicationException("Invalid version"); throw new ApplicationException("Invalid version");
@ -541,18 +562,27 @@ namespace de4dot.code.deobfuscators.Confuser {
return false; return false;
} }
byte[] Decompress(byte[] compressed) {
if (lzmaType != null)
return ConfuserUtils.SevenZipDecompress(compressed);
return DeobUtils.Inflate(compressed, true);
}
byte[] DecryptResource(byte[] encrypted) { byte[] DecryptResource(byte[] encrypted) {
switch (version) { switch (version) {
case ConfuserVersion.v18_r75367_normal: case ConfuserVersion.v18_r75367_normal:
case ConfuserVersion.v18_r75369_normal: case ConfuserVersion.v18_r75369_normal:
case ConfuserVersion.v19_r77172_normal:
return DecryptResource_v18_r75367_normal(encrypted); return DecryptResource_v18_r75367_normal(encrypted);
case ConfuserVersion.v18_r75367_dynamic: case ConfuserVersion.v18_r75367_dynamic:
case ConfuserVersion.v18_r75369_dynamic: case ConfuserVersion.v18_r75369_dynamic:
case ConfuserVersion.v19_r77172_dynamic:
return DecryptResource_v18_r75367_dynamic(encrypted); return DecryptResource_v18_r75367_dynamic(encrypted);
case ConfuserVersion.v18_r75367_native: case ConfuserVersion.v18_r75367_native:
case ConfuserVersion.v18_r75369_native: case ConfuserVersion.v18_r75369_native:
case ConfuserVersion.v19_r77172_native:
return DecryptResource_v18_r75367_native(encrypted); return DecryptResource_v18_r75367_native(encrypted);
default: default:
@ -567,7 +597,7 @@ namespace de4dot.code.deobfuscators.Confuser {
byte[] DecryptResource_v18_r75367_normal(byte[] encrypted) { byte[] DecryptResource_v18_r75367_normal(byte[] encrypted) {
var key = GetSigKey(); var key = GetSigKey();
var decrypted = ConfuserUtils.Decrypt(BitConverter.ToUInt32(key, 12) * (uint)key0, encrypted); var decrypted = ConfuserUtils.Decrypt(BitConverter.ToUInt32(key, 12) * (uint)key0, encrypted);
return DeobUtils.Inflate(DeobUtils.AesDecrypt(decrypted, key, DeobUtils.Md5Sum(key)), true); return Decompress(DeobUtils.AesDecrypt(decrypted, key, DeobUtils.Md5Sum(key)));
} }
static int GetDynamicStartIndex(IList<Instruction> instrs, int ldlocIndex) { static int GetDynamicStartIndex(IList<Instruction> instrs, int ldlocIndex) {
@ -645,9 +675,7 @@ namespace de4dot.code.deobfuscators.Confuser {
byte[] DecryptResource(byte[] encrypted, Func<uint, byte> decryptFunc) { byte[] DecryptResource(byte[] encrypted, Func<uint, byte> decryptFunc) {
var key = GetSigKey(); var key = GetSigKey();
var decrypted = DeobUtils.AesDecrypt(encrypted, key, DeobUtils.Md5Sum(key)); byte[] decrypted = DecryptAndDecompress(encrypted, key);
decrypted = DeobUtils.Inflate(decrypted, true);
var reader = MemoryImageStream.Create(decrypted); var reader = MemoryImageStream.Create(decrypted);
var result = new MemoryStream(); var result = new MemoryStream();
var writer = new BinaryWriter(result); var writer = new BinaryWriter(result);
@ -659,6 +687,16 @@ namespace de4dot.code.deobfuscators.Confuser {
return result.ToArray(); return result.ToArray();
} }
byte[] DecryptAndDecompress(byte[] encrypted, byte[] key) {
byte[] iv = DeobUtils.Md5Sum(key);
try {
return Decompress(DeobUtils.AesDecrypt(encrypted, key, iv));
}
catch {
return DeobUtils.AesDecrypt(Decompress(encrypted), key, iv);
}
}
static bool VerifyGenericArg(MethodSpec gim, ElementType etype) { static bool VerifyGenericArg(MethodSpec gim, ElementType etype) {
if (gim == null) if (gim == null)
return false; return false;
@ -729,6 +767,13 @@ namespace de4dot.code.deobfuscators.Confuser {
case ConfuserVersion.v18_r75369_dynamic: case ConfuserVersion.v18_r75369_dynamic:
case ConfuserVersion.v18_r75369_native: case ConfuserVersion.v18_r75369_native:
minRev = 75369; minRev = 75369;
maxRev = 77124;
return true;
case ConfuserVersion.v19_r77172_normal:
case ConfuserVersion.v19_r77172_dynamic:
case ConfuserVersion.v19_r77172_native:
minRev = 77172;
maxRev = int.MaxValue; maxRev = int.MaxValue;
return true; return true;

View File

@ -461,6 +461,7 @@ namespace de4dot.code.deobfuscators.Confuser {
AddTypesToBeRemoved(constantsDecrypterV18.Types, "Constants decrypter type"); AddTypesToBeRemoved(constantsDecrypterV18.Types, "Constants decrypter type");
AddFieldsToBeRemoved(constantsDecrypterV18.Fields, "Constants decrypter field"); AddFieldsToBeRemoved(constantsDecrypterV18.Fields, "Constants decrypter field");
AddMethodToBeRemoved(constantsDecrypterV18.NativeMethod, "Constants decrypter native method"); AddMethodToBeRemoved(constantsDecrypterV18.NativeMethod, "Constants decrypter native method");
AddTypeToBeRemoved(constantsDecrypterV18.LzmaType, "LZMA type");
AddResourceToBeRemoved(constantsDecrypterV18.Resource, "Encrypted constants"); AddResourceToBeRemoved(constantsDecrypterV18.Resource, "Encrypted constants");
} }
@ -516,6 +517,7 @@ namespace de4dot.code.deobfuscators.Confuser {
AddResourceToBeRemoved(rsrc, "Encrypted resources"); AddResourceToBeRemoved(rsrc, "Encrypted resources");
AddMethodToBeRemoved(resourceDecrypter.Handler, "Resource decrypter handler"); AddMethodToBeRemoved(resourceDecrypter.Handler, "Resource decrypter handler");
AddFieldsToBeRemoved(resourceDecrypter.Fields, "Resource decrypter field"); AddFieldsToBeRemoved(resourceDecrypter.Fields, "Resource decrypter field");
AddTypeToBeRemoved(resourceDecrypter.LzmaType, "LZMA type");
} }
void RemoveObfuscatorAttribute() { void RemoveObfuscatorAttribute() {

View File

@ -30,6 +30,7 @@ namespace de4dot.code.deobfuscators.Confuser {
ISimpleDeobfuscator simpleDeobfuscator; ISimpleDeobfuscator simpleDeobfuscator;
MethodDef handler; MethodDef handler;
MethodDef installMethod; MethodDef installMethod;
TypeDef lzmaType;
EmbeddedResource resource; EmbeddedResource resource;
Dictionary<FieldDef, bool> fields = new Dictionary<FieldDef, bool>(); Dictionary<FieldDef, bool> fields = new Dictionary<FieldDef, bool>();
byte key0, key1; byte key0, key1;
@ -42,6 +43,7 @@ namespace de4dot.code.deobfuscators.Confuser {
v17_r73822, v17_r73822,
v18_r75367, v18_r75367,
v18_r75369, v18_r75369,
v19_r77172,
} }
public IEnumerable<FieldDef> Fields { public IEnumerable<FieldDef> Fields {
@ -52,6 +54,10 @@ namespace de4dot.code.deobfuscators.Confuser {
get { return handler; } get { return handler; }
} }
public TypeDef LzmaType {
get { return lzmaType; }
}
public bool Detected { public bool Detected {
get { return handler != null; } get { return handler != null; }
} }
@ -103,8 +109,13 @@ namespace de4dot.code.deobfuscators.Confuser {
tmpVersion = ConfuserVersion.v17_r73822; tmpVersion = ConfuserVersion.v17_r73822;
else if (FindKey0_v18_r75367(tmpHandler, out key0) && FindKey1_v17_r73404(tmpHandler, out key1)) else if (FindKey0_v18_r75367(tmpHandler, out key0) && FindKey1_v17_r73404(tmpHandler, out key1))
tmpVersion = ConfuserVersion.v18_r75367; tmpVersion = ConfuserVersion.v18_r75367;
else if (FindKey0_v18_r75369(tmpHandler, out key0) && FindKey1_v18_r75369(tmpHandler, out key1)) else if (FindKey0_v18_r75369(tmpHandler, out key0) && FindKey1_v18_r75369(tmpHandler, out key1)) {
lzmaType = ConfuserUtils.FindLzmaType(tmpHandler);
if (lzmaType == null)
tmpVersion = ConfuserVersion.v18_r75369; tmpVersion = ConfuserVersion.v18_r75369;
else
tmpVersion = ConfuserVersion.v19_r77172;
}
else else
return false; return false;
} }
@ -343,6 +354,7 @@ namespace de4dot.code.deobfuscators.Confuser {
case ConfuserVersion.v17_r73822: return Decrypt_v17_r73404(); case ConfuserVersion.v17_r73822: return Decrypt_v17_r73404();
case ConfuserVersion.v18_r75367: return Decrypt_v18_r75367(); case ConfuserVersion.v18_r75367: return Decrypt_v18_r75367();
case ConfuserVersion.v18_r75369: return Decrypt_v18_r75367(); case ConfuserVersion.v18_r75369: return Decrypt_v18_r75367();
case ConfuserVersion.v19_r77172: return Decrypt_v19_r77172();
default: throw new ApplicationException("Unknown version"); default: throw new ApplicationException("Unknown version");
} }
} }
@ -381,6 +393,17 @@ namespace de4dot.code.deobfuscators.Confuser {
return reader.ReadBytes(reader.ReadInt32()); return reader.ReadBytes(reader.ReadInt32());
} }
byte[] Decrypt_v19_r77172() {
var encrypted = resource.GetResourceData();
byte k = key0;
for (int i = 0; i < encrypted.Length; i++) {
encrypted[i] ^= k;
k *= key1;
}
var reader = new BinaryReader(new MemoryStream(ConfuserUtils.SevenZipDecompress(encrypted)));
return reader.ReadBytes(reader.ReadInt32());
}
public void Deobfuscate(Blocks blocks) { public void Deobfuscate(Blocks blocks) {
if (blocks.Method != installMethod) if (blocks.Method != installMethod)
return; return;
@ -415,6 +438,11 @@ namespace de4dot.code.deobfuscators.Confuser {
case ConfuserVersion.v18_r75369: case ConfuserVersion.v18_r75369:
minRev = 75369; minRev = 75369;
maxRev = 77124;
return true;
case ConfuserVersion.v19_r77172:
minRev = 77172;
maxRev = int.MaxValue; maxRev = int.MaxValue;
return true; return true;

View File

@ -86,6 +86,7 @@ namespace de4dot.code.deobfuscators.Confuser {
v17_r75076, v17_r75076,
v18_r75184, v18_r75184,
v18_r75367, v18_r75367,
v19_r77172,
} }
public bool Detected { public bool Detected {
@ -170,8 +171,10 @@ namespace de4dot.code.deobfuscators.Confuser {
theVersion = ConfuserVersion.v17_r75076; theVersion = ConfuserVersion.v17_r75076;
else if (module.Name == "Stub.exe") else if (module.Name == "Stub.exe")
theVersion = ConfuserVersion.v18_r75184; theVersion = ConfuserVersion.v18_r75184;
else else if (!IsGetLenToPosStateMethodPrivate(type))
theVersion = ConfuserVersion.v18_r75367; theVersion = ConfuserVersion.v18_r75367;
else
theVersion = ConfuserVersion.v19_r77172;
} }
else if (IsDecryptMethod_v17_r73404(decyptMethod)) else if (IsDecryptMethod_v17_r73404(decyptMethod))
theVersion = ConfuserVersion.v17_r73404; theVersion = ConfuserVersion.v17_r73404;
@ -201,6 +204,15 @@ namespace de4dot.code.deobfuscators.Confuser {
version = theVersion; version = theVersion;
} }
static bool IsGetLenToPosStateMethodPrivate(TypeDef type) {
foreach (var m in type.Methods) {
if (!DotNetUtils.IsMethod(m, "System.UInt32", "(System.UInt32)"))
continue;
return m.IsPrivate;
}
return false;
}
bool FindEntryPointToken(ISimpleDeobfuscator simpleDeobfuscator, MethodDef cctor, MethodDef entryPoint, out uint token) { bool FindEntryPointToken(ISimpleDeobfuscator simpleDeobfuscator, MethodDef cctor, MethodDef entryPoint, out uint token) {
token = 0; token = 0;
ulong @base; ulong @base;
@ -474,6 +486,7 @@ namespace de4dot.code.deobfuscators.Confuser {
case ConfuserVersion.v17_r75076: return Decrypt_v17_r75076(data); case ConfuserVersion.v17_r75076: return Decrypt_v17_r75076(data);
case ConfuserVersion.v18_r75184: return Decrypt_v17_r75076(data); case ConfuserVersion.v18_r75184: return Decrypt_v17_r75076(data);
case ConfuserVersion.v18_r75367: return Decrypt_v17_r75076(data); case ConfuserVersion.v18_r75367: return Decrypt_v17_r75076(data);
case ConfuserVersion.v19_r77172: return Decrypt_v17_r75076(data);
default: throw new ApplicationException("Unknown version"); default: throw new ApplicationException("Unknown version");
} }
} }
@ -535,10 +548,10 @@ namespace de4dot.code.deobfuscators.Confuser {
var reader = new BinaryReader(new MemoryStream(data)); var reader = new BinaryReader(new MemoryStream(data));
byte[] key, iv; byte[] key, iv;
data = Decrypt_v15_r60785(reader, out key, out iv); data = Decrypt_v15_r60785(reader, out key, out iv);
return SevenzipDecompress(DeobUtils.AesDecrypt(data, key, iv)); return SevenZipDecompress(DeobUtils.AesDecrypt(data, key, iv));
} }
static byte[] SevenzipDecompress(byte[] data) { static byte[] SevenZipDecompress(byte[] data) {
var reader = new BinaryReader(new MemoryStream(data)); var reader = new BinaryReader(new MemoryStream(data));
int totalSize = reader.ReadInt32(); int totalSize = reader.ReadInt32();
var props = reader.ReadBytes(5); var props = reader.ReadBytes(5);
@ -628,6 +641,11 @@ namespace de4dot.code.deobfuscators.Confuser {
case ConfuserVersion.v18_r75367: case ConfuserVersion.v18_r75367:
minRev = 75367; minRev = 75367;
maxRev = 77124;
return true;
case ConfuserVersion.v19_r77172:
minRev = 77172;
maxRev = int.MaxValue; maxRev = int.MaxValue;
return true; return true;

View File

@ -43,7 +43,8 @@ namespace de4dot.code.deobfuscators.Confuser {
75306, 75318, 75349, 75367, 75369, 75402, 75459, 75461, 75306, 75318, 75349, 75367, 75369, 75402, 75459, 75461,
75573, 75719, 75720, 75725, 75806, 75807, 75926, 76101, 75573, 75719, 75720, 75725, 75806, 75807, 75926, 76101,
76119, 76163, 76186, 76271, 76360, 76509, 76542, 76548, 76119, 76163, 76186, 76271, 76360, 76509, 76542, 76548,
76558, 76580, 76656, 76558, 76580, 76656, 76871, 76923, 76924, 76933, 76934,
76972, 76974, 77124, 77172,
}; };
static Dictionary<int, Version> revToVersion = new Dictionary<int, Version> { static Dictionary<int, Version> revToVersion = new Dictionary<int, Version> {