NSO支持

This commit is contained in:
Perfare 2019-03-19 12:14:09 +08:00
parent 182580c90c
commit 5d26d0f480
7 changed files with 880 additions and 11 deletions

View file

@ -47,6 +47,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Lz4DecoderStream.cs" />
<Compile Include="NSO.cs" />
<Compile Include="NSOClass.cs" />
<Compile Include="VersionAttribute.cs" />
<Compile Include="Config.cs" />
<Compile Include="DummyAssemblyCreator.cs" />

View file

@ -0,0 +1,540 @@
#define CHECK_ARGS
#define CHECK_EOF
//#define LOCAL_SHADOW
using System;
using System.IO;
namespace Lz4
{
public class Lz4DecoderStream : Stream
{
public Lz4DecoderStream(Stream input, long inputLength = long.MaxValue)
{
Reset(input, inputLength);
}
private void Reset(Stream input, long inputLength = long.MaxValue)
{
this.inputLength = inputLength;
this.input = input;
phase = DecodePhase.ReadToken;
decodeBufferPos = 0;
litLen = 0;
matLen = 0;
matDst = 0;
inBufPos = DecBufLen;
inBufEnd = DecBufLen;
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing && input != null)
{
input.Close();
}
input = null;
decodeBuffer = null;
}
finally
{
base.Dispose(disposing);
}
}
private long inputLength;
private Stream input;
//because we might not be able to match back across invocations,
//we have to keep the last window's worth of bytes around for reuse
//we use a circular buffer for this - every time we write into this
//buffer, we also write the same into our output buffer
private const int DecBufLen = 0x10000;
private const int DecBufMask = 0xFFFF;
private const int InBufLen = 128;
private byte[] decodeBuffer = new byte[DecBufLen + InBufLen];
private int decodeBufferPos, inBufPos, inBufEnd;
//we keep track of which phase we're in so that we can jump right back
//into the correct part of decoding
private DecodePhase phase;
private enum DecodePhase
{
ReadToken,
ReadExLiteralLength,
CopyLiteral,
ReadOffset,
ReadExMatchLength,
CopyMatch,
}
//state within interruptable phases and across phase boundaries is
//kept here - again, so that we can punt out and restart freely
private int litLen, matLen, matDst;
public override int Read(byte[] buffer, int offset, int count)
{
#if CHECK_ARGS
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset < 0 || count < 0 || buffer.Length - count < offset)
throw new ArgumentOutOfRangeException();
if (input == null)
throw new InvalidOperationException();
#endif
int nRead, nToRead = count;
var decBuf = decodeBuffer;
//the stringy gotos are obnoxious, but their purpose is to
//make it *blindingly* obvious how the state machine transitions
//back and forth as it reads - remember, we can yield out of
//this routine in several places, and we must be able to re-enter
//and pick up where we left off!
#if LOCAL_SHADOW
var phase = this.phase;
var inBufPos = this.inBufPos;
var inBufEnd = this.inBufEnd;
#endif
switch (phase)
{
case DecodePhase.ReadToken:
goto readToken;
case DecodePhase.ReadExLiteralLength:
goto readExLiteralLength;
case DecodePhase.CopyLiteral:
goto copyLiteral;
case DecodePhase.ReadOffset:
goto readOffset;
case DecodePhase.ReadExMatchLength:
goto readExMatchLength;
case DecodePhase.CopyMatch:
goto copyMatch;
}
readToken:
int tok;
if (inBufPos < inBufEnd)
{
tok = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
tok = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (tok == -1)
goto finish;
#endif
}
litLen = tok >> 4;
matLen = (tok & 0xF) + 4;
switch (litLen)
{
case 0:
phase = DecodePhase.ReadOffset;
goto readOffset;
case 0xF:
phase = DecodePhase.ReadExLiteralLength;
goto readExLiteralLength;
default:
phase = DecodePhase.CopyLiteral;
goto copyLiteral;
}
readExLiteralLength:
int exLitLen;
if (inBufPos < inBufEnd)
{
exLitLen = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
exLitLen = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (exLitLen == -1)
goto finish;
#endif
}
litLen += exLitLen;
if (exLitLen == 255)
goto readExLiteralLength;
phase = DecodePhase.CopyLiteral;
goto copyLiteral;
copyLiteral:
int nReadLit = litLen < nToRead ? litLen : nToRead;
if (nReadLit != 0)
{
if (inBufPos + nReadLit <= inBufEnd)
{
int ofs = offset;
for (int c = nReadLit; c-- != 0;)
buffer[ofs++] = decBuf[inBufPos++];
nRead = nReadLit;
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
nRead = ReadCore(buffer, offset, nReadLit);
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (nRead == 0)
goto finish;
#endif
}
offset += nRead;
nToRead -= nRead;
litLen -= nRead;
if (litLen != 0)
goto copyLiteral;
}
if (nToRead == 0)
goto finish;
phase = DecodePhase.ReadOffset;
goto readOffset;
readOffset:
if (inBufPos + 1 < inBufEnd)
{
matDst = (decBuf[inBufPos + 1] << 8) | decBuf[inBufPos];
inBufPos += 2;
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
matDst = ReadOffsetCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (matDst == -1)
goto finish;
#endif
}
if (matLen == 15 + 4)
{
phase = DecodePhase.ReadExMatchLength;
goto readExMatchLength;
}
else
{
phase = DecodePhase.CopyMatch;
goto copyMatch;
}
readExMatchLength:
int exMatLen;
if (inBufPos < inBufEnd)
{
exMatLen = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
exMatLen = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (exMatLen == -1)
goto finish;
#endif
}
matLen += exMatLen;
if (exMatLen == 255)
goto readExMatchLength;
phase = DecodePhase.CopyMatch;
goto copyMatch;
copyMatch:
int nCpyMat = matLen < nToRead ? matLen : nToRead;
if (nCpyMat != 0)
{
nRead = count - nToRead;
int bufDst = matDst - nRead;
if (bufDst > 0)
{
//offset is fairly far back, we need to pull from the buffer
int bufSrc = decodeBufferPos - bufDst;
if (bufSrc < 0)
bufSrc += DecBufLen;
int bufCnt = bufDst < nCpyMat ? bufDst : nCpyMat;
for (int c = bufCnt; c-- != 0;)
buffer[offset++] = decBuf[bufSrc++ & DecBufMask];
}
else
{
bufDst = 0;
}
int sOfs = offset - matDst;
for (int i = bufDst; i < nCpyMat; i++)
buffer[offset++] = buffer[sOfs++];
nToRead -= nCpyMat;
matLen -= nCpyMat;
}
if (nToRead == 0)
goto finish;
phase = DecodePhase.ReadToken;
goto readToken;
finish:
nRead = count - nToRead;
int nToBuf = nRead < DecBufLen ? nRead : DecBufLen;
int repPos = offset - nToBuf;
if (nToBuf == DecBufLen)
{
Buffer.BlockCopy(buffer, repPos, decBuf, 0, DecBufLen);
decodeBufferPos = 0;
}
else
{
int decPos = decodeBufferPos;
while (nToBuf-- != 0)
decBuf[decPos++ & DecBufMask] = buffer[repPos++];
decodeBufferPos = decPos & DecBufMask;
}
#if LOCAL_SHADOW
this.phase = phase;
this.inBufPos = inBufPos;
#endif
return nRead;
}
private int ReadByteCore()
{
var buf = decodeBuffer;
if (inBufPos == inBufEnd)
{
int nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
return -1;
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
}
return buf[inBufPos++];
}
private int ReadOffsetCore()
{
var buf = decodeBuffer;
if (inBufPos == inBufEnd)
{
int nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
return -1;
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
}
if (inBufEnd - inBufPos == 1)
{
buf[DecBufLen] = buf[inBufPos];
int nRead = input.Read(buf, DecBufLen + 1,
InBufLen - 1 < inputLength ? InBufLen - 1 : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
{
inBufPos = DecBufLen;
inBufEnd = DecBufLen + 1;
return -1;
}
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead + 1;
}
int ret = (buf[inBufPos + 1] << 8) | buf[inBufPos];
inBufPos += 2;
return ret;
}
private int ReadCore(byte[] buffer, int offset, int count)
{
int nToRead = count;
var buf = decodeBuffer;
int inBufLen = inBufEnd - inBufPos;
int fromBuf = nToRead < inBufLen ? nToRead : inBufLen;
if (fromBuf != 0)
{
var bufPos = inBufPos;
for (int c = fromBuf; c-- != 0;)
buffer[offset++] = buf[bufPos++];
inBufPos = bufPos;
nToRead -= fromBuf;
}
if (nToRead != 0)
{
int nRead;
if (nToRead >= InBufLen)
{
nRead = input.Read(buffer, offset,
nToRead < inputLength ? nToRead : (int)inputLength);
nToRead -= nRead;
}
else
{
nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
fromBuf = nToRead < nRead ? nToRead : nRead;
var bufPos = inBufPos;
for (int c = fromBuf; c-- != 0;)
buffer[offset++] = buf[bufPos++];
inBufPos = bufPos;
nToRead -= fromBuf;
}
inputLength -= nRead;
}
return count - nToRead;
}
#region Stream internals
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override void Flush()
{
}
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
#endregion
}
}

View file

@ -131,7 +131,7 @@ namespace Il2CppDumper
}
}
}
if (version == 24)
if (version >= 24)
{
/* ADRP X0, unk
* ADD X0, X0, unk

222
Il2CppDumper/NSO.cs Normal file
View file

@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Lz4;
namespace Il2CppDumper
{
public sealed class NSO : Il2Cpp
{
private NSOHeader header;
private bool isTextCompressed;
private bool isRoDataCompressed;
private bool isDataCompressed;
private List<NSOSegmentHeader> segments = new List<NSOSegmentHeader>();
private bool isCompressed => isTextCompressed || isRoDataCompressed || isDataCompressed;
public NSO(Stream stream, float version, long maxMetadataUsages) : base(stream, version, maxMetadataUsages)
{
header = new NSOHeader();
header.Magic = ReadUInt32();
header.Version = ReadUInt32();
header.Reserved = ReadUInt32();
header.Flags = ReadUInt32();
isTextCompressed = (header.Flags & 1) != 0;
isRoDataCompressed = (header.Flags & 2) != 0;
isDataCompressed = (header.Flags & 4) != 0;
header.TextSegment = new NSOSegmentHeader
{
FileOffset = ReadUInt32(),
MemoryOffset = ReadUInt32(),
DecompressedSize = ReadUInt32()
};
segments.Add(header.TextSegment);
header.ModuleOffset = ReadUInt32();
header.RoDataSegment = new NSOSegmentHeader
{
FileOffset = ReadUInt32(),
MemoryOffset = ReadUInt32(),
DecompressedSize = ReadUInt32()
};
segments.Add(header.RoDataSegment);
header.ModuleFileSize = ReadUInt32();
header.DataSegment = new NSOSegmentHeader
{
FileOffset = ReadUInt32(),
MemoryOffset = ReadUInt32(),
DecompressedSize = ReadUInt32()
};
segments.Add(header.DataSegment);
header.BssSize = ReadUInt32();
header.DigestBuildID = ReadBytes(0x20);
header.TextCompressedSize = ReadUInt32();
header.RoDataCompressedSize = ReadUInt32();
header.DataCompressedSize = ReadUInt32();
header.Padding = ReadBytes(0x1C);
header.APIInfo = new NSORelativeExtent
{
RegionRoDataOffset = ReadUInt32(),
RegionSize = ReadUInt32()
};
header.DynStr = new NSORelativeExtent
{
RegionRoDataOffset = ReadUInt32(),
RegionSize = ReadUInt32()
};
header.DynSym = new NSORelativeExtent
{
RegionRoDataOffset = ReadUInt32(),
RegionSize = ReadUInt32()
};
header.TextHash = ReadBytes(0x20);
header.RoDataHash = ReadBytes(0x20);
header.DataHash = ReadBytes(0x20);
if (!isCompressed)
{
Position = header.TextSegment.FileOffset + 4;
var modOffset = ReadInt32();
Position = header.TextSegment.FileOffset + modOffset + 8;
var bssStart = ReadUInt32();
var bssEnd = ReadUInt32();
header.BssSegment = new NSOSegmentHeader
{
FileOffset = bssStart,
MemoryOffset = bssStart,
DecompressedSize = bssEnd - bssStart
};
}
}
public override dynamic MapVATR(dynamic uiAddr)
{
var segment = segments.First(x => uiAddr >= x.MemoryOffset && uiAddr <= x.MemoryOffset + x.DecompressedSize);
return uiAddr - segment.MemoryOffset + segment.FileOffset;
}
public override bool Search()
{
return false;
}
public override bool AdvancedSearch(int methodCount)
{
return false;
}
public override bool PlusSearch(int methodCount, int typeDefinitionsCount)
{
var plusSearch = new PlusSearch(this, methodCount, typeDefinitionsCount, maxMetadataUsages);
plusSearch.SetSearch(header.DataSegment);
plusSearch.SetPointerRangeFirst(header.DataSegment);
plusSearch.SetPointerRangeSecond(header.TextSegment);
var codeRegistration = plusSearch.FindCodeRegistration64Bit();
plusSearch.SetPointerRangeSecond(header.BssSegment);
var metadataRegistration = plusSearch.FindMetadataRegistration64Bit();
if (codeRegistration != 0 && metadataRegistration != 0)
{
Console.WriteLine("CodeRegistration : {0:x}", codeRegistration);
Console.WriteLine("MetadataRegistration : {0:x}", metadataRegistration);
Init(codeRegistration, metadataRegistration);
return true;
}
return false;
}
public override bool SymbolSearch()
{
return false;
}
public NSO UnCompress()
{
if (isTextCompressed || isRoDataCompressed || isDataCompressed)
{
var unCompressedStream = new MemoryStream();
var writer = new BinaryWriter(unCompressedStream);
writer.Write(header.Magic);
writer.Write(header.Version);
writer.Write(header.Reserved);
writer.Write(0); //Flags
writer.Write(header.TextSegment.FileOffset);
writer.Write(header.TextSegment.MemoryOffset);
writer.Write(header.TextSegment.DecompressedSize);
writer.Write(header.ModuleOffset);
var roOffset = header.TextSegment.FileOffset + header.TextSegment.DecompressedSize;
writer.Write(roOffset); //header.RoDataSegment.FileOffset
writer.Write(header.RoDataSegment.MemoryOffset);
writer.Write(header.RoDataSegment.DecompressedSize);
writer.Write(header.ModuleFileSize);
writer.Write(roOffset + header.RoDataSegment.DecompressedSize); //header.DataSegment.FileOffset
writer.Write(header.DataSegment.MemoryOffset);
writer.Write(header.DataSegment.DecompressedSize);
writer.Write(header.BssSize);
writer.Write(header.DigestBuildID);
writer.Write(header.TextCompressedSize);
writer.Write(header.RoDataCompressedSize);
writer.Write(header.DataCompressedSize);
writer.Write(header.Padding);
writer.Write(header.APIInfo.RegionRoDataOffset);
writer.Write(header.APIInfo.RegionSize);
writer.Write(header.DynStr.RegionRoDataOffset);
writer.Write(header.DynStr.RegionSize);
writer.Write(header.DynSym.RegionRoDataOffset);
writer.Write(header.DynSym.RegionSize);
writer.Write(header.TextHash);
writer.Write(header.RoDataHash);
writer.Write(header.DataHash);
writer.BaseStream.Position = header.TextSegment.FileOffset;
Position = header.TextSegment.FileOffset;
var textBytes = ReadBytes((int)header.TextCompressedSize);
if (isTextCompressed)
{
var unCompressedData = new byte[header.TextSegment.DecompressedSize];
using (var decoder = new Lz4DecoderStream(new MemoryStream(textBytes)))
{
decoder.Read(unCompressedData, 0, unCompressedData.Length);
}
writer.Write(unCompressedData);
}
else
{
writer.Write(textBytes);
}
var roDataBytes = ReadBytes((int)header.RoDataCompressedSize);
if (isRoDataCompressed)
{
var unCompressedData = new byte[header.RoDataSegment.DecompressedSize];
using (var decoder = new Lz4DecoderStream(new MemoryStream(roDataBytes)))
{
decoder.Read(unCompressedData, 0, unCompressedData.Length);
}
writer.Write(unCompressedData);
}
else
{
writer.Write(roDataBytes);
}
var dataBytes = ReadBytes((int)header.DataCompressedSize);
if (isDataCompressed)
{
var unCompressedData = new byte[header.DataSegment.DecompressedSize];
using (var decoder = new Lz4DecoderStream(new MemoryStream(dataBytes)))
{
decoder.Read(unCompressedData, 0, unCompressedData.Length);
}
writer.Write(unCompressedData);
}
else
{
writer.Write(dataBytes);
}
writer.Flush();
unCompressedStream.Position = 0;
return new NSO(unCompressedStream, version, maxMetadataUsages);
}
return this;
}
}
}

47
Il2CppDumper/NSOClass.cs Normal file
View file

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Il2CppDumper
{
public class NSOHeader
{
public uint Magic;
public uint Version;
public uint Reserved;
public uint Flags;
public NSOSegmentHeader TextSegment;
public uint ModuleOffset;
public NSOSegmentHeader RoDataSegment;
public uint ModuleFileSize;
public NSOSegmentHeader DataSegment;
public uint BssSize;
public byte[] DigestBuildID;
public uint TextCompressedSize;
public uint RoDataCompressedSize;
public uint DataCompressedSize;
public byte[] Padding;
public NSORelativeExtent APIInfo;
public NSORelativeExtent DynStr;
public NSORelativeExtent DynSym;
public byte[] TextHash;
public byte[] RoDataHash;
public byte[] DataHash;
public NSOSegmentHeader BssSegment;
}
public class NSOSegmentHeader
{
public uint FileOffset;
public uint MemoryOffset;
public uint DecompressedSize;
}
public class NSORelativeExtent
{
public uint RegionRoDataOffset;
public uint RegionSize;
}
}

View file

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Il2CppDumper
{
@ -142,6 +140,22 @@ namespace Il2CppDumper
}
}
public void SetSearch(params NSOSegmentHeader[] sections)
{
foreach (var section in sections)
{
if (section != null)
{
search.Add(new Section
{
start = section.FileOffset,
end = section.FileOffset + section.DecompressedSize,
address = section.MemoryOffset
});
}
}
}
public void SetPointerRangeFirst(params MachoSection64Bit[] sections)
{
foreach (var section in sections)
@ -254,6 +268,22 @@ namespace Il2CppDumper
}
}
public void SetPointerRangeFirst(params NSOSegmentHeader[] sections)
{
foreach (var section in sections)
{
if (section != null)
{
pointerRange1.Add(new Section
{
start = section.FileOffset,
end = section.FileOffset + section.DecompressedSize,
address = section.MemoryOffset
});
}
}
}
public void SetPointerRangeSecond(params MachoSection64Bit[] sections)
{
pointerRange2.Clear();
@ -390,6 +420,23 @@ namespace Il2CppDumper
}
}
public void SetPointerRangeSecond(params NSOSegmentHeader[] sections)
{
pointerRange2.Clear();
foreach (var section in sections)
{
if (section != null)
{
pointerRange2.Add(new Section
{
start = section.MemoryOffset,
end = section.MemoryOffset + section.DecompressedSize,
address = section.MemoryOffset
});
}
}
}
public ulong FindCodeRegistration()
{
foreach (var section in search)

View file

@ -119,21 +119,26 @@ namespace Il2CppDumper
var isElf = false;
var isPE = false;
var is64bit = false;
var isNSO = false;
switch (il2cppMagic)
{
default:
throw new Exception("ERROR: il2cpp file not supported.");
case 0x304F534E:
isNSO = true;
is64bit = true;
break;
case 0x905A4D: //PE
isPE = true;
break;
case 0x464c457f: //ELF
isElf = true;
if (il2cppBytes[4] == 2)
if (il2cppBytes[4] == 2) //ELF64
{
goto case 0xFEEDFACF; //ELF64
is64bit = true;
}
break;
case 0xCAFEBABE: //FAT header
case 0xCAFEBABE: //FAT Mach-O
case 0xBEBAFECA:
var machofat = new MachoFat(new MemoryStream(il2cppBytes));
Console.Write("Select Platform: ");
@ -147,14 +152,14 @@ namespace Il2CppDumper
var index = int.Parse(key.KeyChar.ToString()) - 1;
var magic = machofat.fats[index % 2].magic;
il2cppBytes = machofat.GetMacho(index % 2);
if (magic == 0xFEEDFACF) // 64-bit mach object file
if (magic == 0xFEEDFACF)
goto case 0xFEEDFACF;
else
goto case 0xFEEDFACE;
case 0xFEEDFACF: // 64-bit mach object file
case 0xFEEDFACF: // 64bit Mach-O
is64bit = true;
break;
case 0xFEEDFACE: // 32-bit mach object file
case 0xFEEDFACE: // 32bit Mach-O
break;
}
@ -162,7 +167,12 @@ namespace Il2CppDumper
var modeKey = Console.ReadKey(true);
var version = config.ForceIl2CppVersion ? config.ForceVersion : metadata.version;
Console.WriteLine("Initializing il2cpp file...");
if (isPE)
if (isNSO)
{
var nso = new NSO(new MemoryStream(il2cppBytes), version, metadata.maxMetadataUsages);
il2cpp = nso.UnCompress();
}
else if (isPE)
{
il2cpp = new PE(new MemoryStream(il2cppBytes), version, metadata.maxMetadataUsages);
}