Skip to content

Commit 1d8f1b1

Browse files
committed
[WIP] Update assembly decompressor to read blobs
1 parent 39778be commit 1d8f1b1

File tree

4 files changed

+162
-14
lines changed

4 files changed

+162
-14
lines changed

tools/assembly-blob-reader/BlobAssembly.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class BlobAssembly
1818
public uint RuntimeIndex { get; set; }
1919

2020
public BlobReader Blob { get; }
21+
public string DllName => MakeFileName ("dll");
22+
public string PdbName => MakeFileName ("pdb");
23+
public string ConfigName => MakeFileName ("dll.config");
2124

2225
internal BlobAssembly (BinaryReader reader, BlobReader blob)
2326
{
@@ -30,5 +33,55 @@ internal BlobAssembly (BinaryReader reader, BlobReader blob)
3033
ConfigDataOffset = reader.ReadUInt32 ();
3134
ConfigDataSize = reader.ReadUInt32 ();
3235
}
36+
37+
public void ExtractImage (string outputDirPath, string? fileName = null)
38+
{
39+
Blob.ExtractAssemblyImage (this, MakeOutputFilePath (outputDirPath, "dll", fileName));
40+
}
41+
42+
public void ExtractImage (Stream output)
43+
{
44+
Blob.ExtractAssemblyImage (this, output);
45+
}
46+
47+
public void ExtractDebugData (string outputDirPath, string? fileName = null)
48+
{
49+
Blob.ExtractAssemblyDebugData (this, MakeOutputFilePath (outputDirPath, "pdb", fileName));
50+
}
51+
52+
public void ExtractDebugData (Stream output)
53+
{
54+
Blob.ExtractAssemblyDebugData (this, output);
55+
}
56+
57+
public void ExtractConfig (string outputDirPath, string? fileName = null)
58+
{
59+
Blob.ExtractAssemblyConfig (this, MakeOutputFilePath (outputDirPath, "dll.config", fileName));
60+
}
61+
62+
public void ExtractConfig (Stream output)
63+
{
64+
Blob.ExtractAssemblyConfig (this, output);
65+
}
66+
67+
string MakeOutputFilePath (string outputDirPath, string extension, string? fileName)
68+
{
69+
return Path.Combine (outputDirPath, MakeFileName (extension, fileName));
70+
}
71+
72+
string MakeFileName (string extension, string? fileName = null)
73+
{
74+
if (String.IsNullOrEmpty (fileName)) {
75+
fileName = Name;
76+
77+
if (String.IsNullOrEmpty (fileName)) {
78+
fileName = $"{Hash32:x}_{Hash64:x}";
79+
}
80+
81+
fileName = $"{fileName}.{extension}";
82+
}
83+
84+
return fileName!;
85+
}
3386
}
3487
}

tools/assembly-blob-reader/BlobExplorer.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class BlobExplorer
1212
BlobManifestReader? manifest;
1313
int numberOfBlobs = 0;
1414
Action<BlobExplorerLogLevel, string>? logger;
15+
bool keepBlobInMemory;
1516

1617
public IDictionary<string, BlobAssembly> AssembliesByName { get; } = new SortedDictionary<string, BlobAssembly> (StringComparer.OrdinalIgnoreCase);
1718
public IDictionary<uint, BlobAssembly> AssembliesByHash32 { get; } = new Dictionary<uint, BlobAssembly> ();
@@ -43,7 +44,7 @@ class BlobExplorer
4344
// Whichever file is referenced in `blobPath`, the BASE_NAME component is extracted and all the found files are read.
4445
// If `blobPath` points to an aab or an apk, BASE_NAME will always be `assemblies`
4546
//
46-
public BlobExplorer (string blobPath, Action<BlobExplorerLogLevel, string>? customLogger = null)
47+
public BlobExplorer (string blobPath, Action<BlobExplorerLogLevel, string>? customLogger = null, bool keepBlobInMemory = false)
4748
{
4849
if (String.IsNullOrEmpty (blobPath)) {
4950
throw new ArgumentException ("must not be null or empty", nameof (blobPath));
@@ -54,6 +55,7 @@ public BlobExplorer (string blobPath, Action<BlobExplorerLogLevel, string>? cust
5455
}
5556

5657
logger = customLogger;
58+
this.keepBlobInMemory = keepBlobInMemory;
5759
BlobPath = blobPath;
5860
string? extension = Path.GetExtension (blobPath);
5961
string? baseName = null;
@@ -216,7 +218,7 @@ void ReadBlobSetFromArchive (ZipArchive archive, string basePathInArchive)
216218
entry.Extract (stream);
217219

218220
if (entry.FullName.EndsWith (".blob", StringComparison.Ordinal)) {
219-
AddBlob (new BlobReader (stream, GetBlobArch (entry.FullName)));
221+
AddBlob (new BlobReader (stream, GetBlobArch (entry.FullName), keepBlobInMemory));
220222
} else if (entry.FullName.EndsWith (".manifest", StringComparison.Ordinal)) {
221223
manifest = new BlobManifestReader (stream);
222224
}
@@ -287,7 +289,7 @@ BlobManifestReader ReadManifest (string filePath)
287289
BlobReader CreateBlobReader (Stream input, string? arch)
288290
{
289291
numberOfBlobs++;
290-
return new BlobReader (input, arch);
292+
return new BlobReader (input, arch, keepBlobInMemory);
291293
}
292294

293295
bool IsAndroidArchive (string extension)

tools/assembly-blob-reader/BlobReader.cs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Text;
@@ -11,6 +12,8 @@ class BlobReader
1112
const uint BUNDLED_ASSEMBLIES_BLOB_MAGIC = 0x41424158; // 'XABA', little-endian
1213
const uint BUNDLED_ASSEMBLIES_BLOB_VERSION = 1; // The highest format version this reader understands
1314

15+
MemoryStream? blobData;
16+
1417
public uint Version { get; private set; }
1518
public uint LocalEntryCount { get; private set; }
1619
public uint GlobalEntryCount { get; private set; }
@@ -22,11 +25,18 @@ class BlobReader
2225

2326
public bool HasGlobalIndex => BlobID == 0;
2427

25-
public BlobReader (Stream blob, string? arch = null)
28+
public BlobReader (Stream blob, string? arch = null, bool keepBlobInMemory = false)
2629
{
2730
Arch = arch ?? String.Empty;
2831

2932
blob.Seek (0, SeekOrigin.Begin);
33+
if (keepBlobInMemory) {
34+
blobData = new MemoryStream ();
35+
blob.CopyTo (blobData);
36+
blobData.Flush ();
37+
blob.Seek (0, SeekOrigin.Begin);
38+
}
39+
3040
using (var reader = new BinaryReader (blob, Encoding.UTF8, leaveOpen: true)) {
3141
ReadHeader (reader);
3242

@@ -38,6 +48,87 @@ public BlobReader (Stream blob, string? arch = null)
3848
}
3949
}
4050

51+
internal void ExtractAssemblyImage (BlobAssembly assembly, string outputFilePath)
52+
{
53+
SaveDataToFile (outputFilePath, assembly.DataOffset, assembly.DataSize);
54+
}
55+
56+
internal void ExtractAssemblyImage (BlobAssembly assembly, Stream output)
57+
{
58+
SaveDataToStream (output, assembly.DataOffset, assembly.DataSize);
59+
}
60+
61+
internal void ExtractAssemblyDebugData (BlobAssembly assembly, string outputFilePath)
62+
{
63+
if (assembly.DebugDataOffset == 0 || assembly.DebugDataSize == 0) {
64+
return;
65+
}
66+
SaveDataToFile (outputFilePath, assembly.DebugDataOffset, assembly.DebugDataSize);
67+
}
68+
69+
internal void ExtractAssemblyDebugData (BlobAssembly assembly, Stream output)
70+
{
71+
if (assembly.DebugDataOffset == 0 || assembly.DebugDataSize == 0) {
72+
return;
73+
}
74+
SaveDataToStream (output, assembly.DebugDataOffset, assembly.DebugDataSize);
75+
}
76+
77+
internal void ExtractAssemblyConfig (BlobAssembly assembly, string outputFilePath)
78+
{
79+
if (assembly.ConfigDataOffset == 0 || assembly.ConfigDataSize == 0) {
80+
return;
81+
}
82+
83+
SaveDataToFile (outputFilePath, assembly.ConfigDataOffset, assembly.ConfigDataSize);
84+
}
85+
86+
internal void ExtractAssemblyConfig (BlobAssembly assembly, Stream output)
87+
{
88+
if (assembly.ConfigDataOffset == 0 || assembly.ConfigDataSize == 0) {
89+
return;
90+
}
91+
SaveDataToStream (output, assembly.ConfigDataOffset, assembly.ConfigDataSize);
92+
}
93+
94+
void SaveDataToFile (string outputFilePath, uint offset, uint size)
95+
{
96+
EnsureBlobDataAvailable ();
97+
using (var fs = File.Open (outputFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) {
98+
SaveDataToStream (fs, offset, size);
99+
}
100+
}
101+
102+
void SaveDataToStream (Stream output, uint offset, uint size)
103+
{
104+
EnsureBlobDataAvailable ();
105+
ArrayPool<byte> pool = ArrayPool<byte>.Shared;
106+
107+
blobData!.Seek (offset, SeekOrigin.Begin);
108+
byte[] buf = pool.Rent (16384);
109+
int nread;
110+
long toRead = size;
111+
while (toRead > 0 && (nread = blobData.Read (buf, 0, buf.Length)) > 0) {
112+
if (nread > toRead) {
113+
nread = (int)toRead;
114+
}
115+
116+
output.Write (buf, 0, nread);
117+
toRead -= nread;
118+
}
119+
output.Flush ();
120+
pool.Return (buf);
121+
}
122+
123+
void EnsureBlobDataAvailable ()
124+
{
125+
if (blobData != null) {
126+
return;
127+
}
128+
129+
throw new InvalidOperationException ("Blob data not available. BlobReader/BlobExplorer must be instantiated with the `keepBlobInMemory` argument set to `true`");
130+
}
131+
41132
public bool HasIdenticalContent (BlobReader other)
42133
{
43134
return

tools/decompress-assemblies/main.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ static int Usage ()
2525

2626
static bool UncompressDLL (Stream inputStream, string fileName, string filePath, string prefix)
2727
{
28-
string outputFile = $"{prefix}{Path.GetFileName (filePath)}";
28+
string outputFile = $"{prefix}{filePath}";
2929
bool retVal = true;
3030

3131
Console.WriteLine ($"Processing {fileName}");
@@ -75,7 +75,7 @@ static bool UncompressDLL (Stream inputStream, string fileName, string filePath,
7575
static bool UncompressDLL (string filePath, string prefix)
7676
{
7777
using (var fs = File.Open (filePath, FileMode.Open, FileAccess.Read)) {
78-
return UncompressDLL (fs, filePath, filePath, prefix);
78+
return UncompressDLL (fs, filePath, Path.GetFileName (filePath), prefix);
7979
}
8080
}
8181

@@ -93,7 +93,8 @@ static bool UncompressFromAPK_IndividualEntries (ZipArchive apk, string filePath
9393
using (var stream = new MemoryStream ()) {
9494
entry.Extract (stream);
9595
stream.Seek (0, SeekOrigin.Begin);
96-
UncompressDLL (stream, $"{filePath}!{entry.FullName}", entry.FullName, prefix);
96+
string fileName = entry.FullName.Substring (assembliesPath.Length);
97+
UncompressDLL (stream, $"{filePath}!{entry.FullName}", fileName, prefix);
9798
}
9899
}
99100

@@ -102,18 +103,19 @@ static bool UncompressFromAPK_IndividualEntries (ZipArchive apk, string filePath
102103

103104
static bool UncompressFromAPK_Blobs (string filePath, string prefix)
104105
{
105-
var explorer = new BlobExplorer (filePath);
106+
var explorer = new BlobExplorer (filePath, keepBlobInMemory: true);
106107
foreach (BlobAssembly assembly in explorer.Assemblies) {
107-
string assemblyName = assembly.Name;
108-
if (String.IsNullOrEmpty (assemblyName)) {
109-
assemblyName = $"{assembly.Hash32:x}_{assembly.Hash64:x}.dll";
110-
}
108+
string assemblyName = assembly.DllName;
111109

112110
if (!String.IsNullOrEmpty (assembly.Blob.Arch)) {
113111
assemblyName = $"{assembly.Blob.Arch}/{assemblyName}";
114112
}
115113

116-
throw new NotImplementedException ();
114+
using (var stream = new MemoryStream ()) {
115+
assembly.ExtractImage (stream);
116+
stream.Seek (0, SeekOrigin.Begin);
117+
UncompressDLL (stream, $"{filePath}!{assemblyName}", assemblyName, prefix);
118+
}
117119
}
118120

119121
return true;
@@ -123,7 +125,7 @@ static bool UncompressFromAPK (string filePath, string assembliesPath)
123125
{
124126
string prefix = $"uncompressed-{Path.GetFileNameWithoutExtension (filePath)}{Path.DirectorySeparatorChar}";
125127
using (ZipArchive apk = ZipArchive.Open (filePath, FileMode.Open)) {
126-
if (!apk.ContainsEntry ($"{assembliesPath}/assemblies.blob")) {
128+
if (!apk.ContainsEntry ($"{assembliesPath}assemblies.blob")) {
127129
return UncompressFromAPK_IndividualEntries (apk, filePath, assembliesPath, prefix);
128130
}
129131
}

0 commit comments

Comments
 (0)