ELF: Use ARM instruction set for XOR decryption heuristics

This commit is contained in:
Katy Coe
2020-12-09 21:55:56 +01:00
parent 34819a114d
commit 40d01e9afa

View File

@@ -295,13 +295,15 @@ namespace Il2CppInspector
var (armNibbleB, armNibbleT, armValueB, armValueT) = testValues[Bits]; var (armNibbleB, armNibbleT, armValueB, armValueT) = testValues[Bits];
// We examine the bottom nibble of the 2nd byte and top nibble of 4th byte var instructionsToTest = 256;
// of the first 64 words (256 bytes) of .text. These values are expected to be primarily 0x0 and 0xE (ARM only)
var textFirstDWords = ReadArray<uint>(conv.Long(sectionByName[".text"].sh_offset), 256); // This gives us an idea of whether the code might be encrypted
var textFirstDWords = ReadArray<uint>(conv.Long(sectionByName[".text"].sh_offset), instructionsToTest);
var bottom = textFirstDWords.Select(w => (w >> armNibbleB) & 0xF).GroupBy(n => n).OrderByDescending(f => f.Count()).First().Key; var bottom = textFirstDWords.Select(w => (w >> armNibbleB) & 0xF).GroupBy(n => n).OrderByDescending(f => f.Count()).First().Key;
var top = textFirstDWords.Select(w => w >> armNibbleT).GroupBy(n => n).OrderByDescending(f => f.Count()).First().Key; var top = textFirstDWords.Select(w => w >> armNibbleT).GroupBy(n => n).OrderByDescending(f => f.Count()).First().Key;
var xorKeyCandidateFromCode = (byte) (((top ^ armValueT) << 4) | (bottom ^ armValueB)); var xorKeyCandidateFromCode = (byte) (((top ^ armValueT) << 4) | (bottom ^ armValueB));
// If the first part of the data section is encrypted, proceed
if (xorKeyCandidateStriped != 0x00) { if (xorKeyCandidateStriped != 0x00) {
// Some files may use a striped encryption whereby alternate blocks are encrypted and un-encrypted // Some files may use a striped encryption whereby alternate blocks are encrypted and un-encrypted
@@ -317,8 +319,8 @@ namespace Il2CppInspector
var firstUnencrypted = 0xffffffff; var firstUnencrypted = 0xffffffff;
var stripeSize = 0xffffffff; var stripeSize = 0xffffffff;
// At least this many instructions must pass the threshold // At least this many instructions per block must pass the threshold to be considered unencrypted
var threshold = (blockSize / 4) / 5; var threshold = (blockSize / 4) * 0.8;
// A stripe of encryption or non-encryption is considered to have ended when this many blocks in the opposite state are found // A stripe of encryption or non-encryption is considered to have ended when this many blocks in the opposite state are found
var maxBlocksInARow = 4; var maxBlocksInARow = 4;
@@ -333,9 +335,9 @@ namespace Il2CppInspector
for (var pos = start; pos < start + maxSearchLength && stripeSize == 0xffffffff; pos += blockSize) { for (var pos = start; pos < start + maxSearchLength && stripeSize == 0xffffffff; pos += blockSize) {
var size = Math.Min(blockSize, start + length - pos); var size = Math.Min(blockSize, start + length - pos);
var dwords = ReadArray<uint>(pos, size / 4); var dwords = ReadArray<uint>(pos, size / 4);
var countB = dwords.Count(w => ((w >> armNibbleB) & 0xF) == armValueB);
var countT = dwords.Count(w => (w >> armNibbleT) == armValueT); var commonInstructions = dwords.Count(w => Bits == 32? isCommonARMv7(w) : isCommonARMv8A(w));
var probablyEncrypted = countT < threshold && countB < threshold; var probablyEncrypted = commonInstructions < threshold;
// Increment one or the other; reset the other one to zero // Increment one or the other; reset the other one to zero
probablyEncryptedCount = probablyEncrypted? probablyEncryptedCount + 1 : 0; probablyEncryptedCount = probablyEncrypted? probablyEncryptedCount + 1 : 0;
@@ -356,8 +358,10 @@ namespace Il2CppInspector
var xorKey = bestKey.Key; var xorKey = bestKey.Key;
// Otherwise choose according to striped/full encryption // Otherwise choose according to striped/full encryption
if (bestKey.Count() == 1) if (bestKey.Count() == 1) {
xorKey = stripeSize != 0xffffffff ? xorKeyCandidateStriped : xorKeyCandidateFull; xorKey = keys.OrderByDescending(k => textFirstDWords.Select(w => w ^ (k << 24) ^ (k << 16) ^ (k << 8) ^ k)
.Count(w => Bits == 32? isCommonARMv7((uint) w) : isCommonARMv8A((uint) w))).First();
}
StatusUpdate("Decrypting"); StatusUpdate("Decrypting");
Console.WriteLine($"Performing XOR decryption (key: 0x{xorKey:X2}, stripe size: 0x{stripeSize:X4})"); Console.WriteLine($"Performing XOR decryption (key: 0x{xorKey:X2}, stripe size: 0x{stripeSize:X4})");
@@ -384,6 +388,72 @@ namespace Il2CppInspector
return true; return true;
} }
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/ARM-Instruction-Set-Encoding/ARM-instruction-set-encoding
private bool isCommonARMv7(uint inst) {
var cond = inst >> 28; // We'll allow 0x1111 (for BL/BLX), AL, EQ, NE, GE, LT, GT, LE only
if (cond != 0b1111 && cond != 0b1110 && cond != 0b0000 && cond != 0b0001 && cond != 0b1010 && cond != 0b1011 && cond != 0b1100 && cond != 0b1101)
return false;
var op1 = (inst >> 25) & 7;
// Disallow media instructions
var op = (inst >> 4) & 1;
if (op1 == 0b011 && op == 1)
return false;
// Disallow co-processor instructions
if (op1 == 0b110 || op1 == 0b111)
return false;
// Disallow 0b1111 cond except for BL and BLX
if (cond == 0b1111) {
var op1_1 = (inst >> 20) & 0b11111111;
if ((op1_1 >> 5) != 0b101)
return false;
}
// Disallow MSR and other miscellaneous
if (op == 1) {
var op1_1 = (inst >> 20) & 0b11111;
var op2 = (inst >> 4) & 0b1111;
if (op1_1 == 0b10010 || op1_1 == 0b10110 || op1_1 == 0b10000 || op1_1 == 0b10100)
return false;
// Disallow synchronization primitives
if ((op1_1 >> 4) == 1)
return false;
}
// Probably a common instruction
return true;
}
// https://montcs.bloomu.edu/Information/ARMv8/ARMv8-A_Architecture_Reference_Manual_(Issue_A.a).pdf
private bool isCommonARMv8A(uint inst) {
var op = (inst >> 24) & 0b11111;
// Disallow unexpected, SIMD and FP
if ((op >> 3) == 0 || (op >> 1) == 0b0111 || (op >> 1) == 0b1111)
return false;
// Disallow exception generation and system instructions
if ((inst >> 24) == 0b11010100 || (inst >> 22) == 0b1101010100)
return false;
// Disallow bitfield and extract
if (op == 0b10011)
return false;
// Disallow conditional compare and data processing
if ((op >> 1) == 0b1101)
return false;
return true;
}
private void xorRange(int offset, int length, byte xorValue) { private void xorRange(int offset, int length, byte xorValue) {
using var writer = new BinaryWriter(BaseStream, Encoding.Default, true); using var writer = new BinaryWriter(BaseStream, Encoding.Default, true);