这是我在 C# 中解压缩文件的解决方案。
我无法弄清楚 64 字节对齐是如何工作的,因此我为不在对齐边界上的单词硬编码了 0xD60 的偏移量。我相信这是下一个空闲页面的偏移量。
如果其他人想使用这个解决方案,他们要么需要使用 IDA 等手动查找页面偏移量,要么弄清楚如何动态获取它。
我使用 katai struct 库来解析 elf 文件。
using Kaitai; //http://formats.kaitai.io/elf/csharp.html
static void Main(string[] args)
{
var path = @"C:\path\libmonodroid_bundle_app.so";
var bytes = File.ReadAllBytes(path);
var elf = Elf.FromFile(path);
var rodata = elf.Header.SectionHeaders.FirstOrDefault(x => x.Name.Equals(".rodata"));
if (rodata == null)
{
Console.WriteLine(".rodata not found");
Console.ReadKey();
return;
}
//read in all the packed file names
var packedFiles = new List<string>();
uint addr = (uint)rodata.Addr;
while (true)
{
//up to 16 bytes of alignment
uint i;
for (i = 0; i < 16; i++) if (bytes[addr + i] != 0) break;
if (i == 16) break; //We found all the files
else addr += i;
var name = GetString(bytes, addr);
if (string.IsNullOrWhiteSpace(name)) break;
//We only care about mono dlls
if (!name.EndsWith(".dll")) break;
packedFiles.Add(name);
addr += (uint)name.Length + 1u;
}
var data = elf.Header.SectionHeaders.FirstOrDefault(x => x.Name.Equals(".data"));
if (data == null)
{
Console.WriteLine(".data not found");
Console.ReadKey();
return;
}
//Read in all the packed file pointers and sizes and then decompress and dump them
addr = (uint) (data.Offset);
for (int i = 0; i < packedFiles.Count; i++)
{
//0xD60 is a magic offset due to 64 byte alignment. I am not sure how to dynamically generate this
//offset hence this solution will need you to manually find this offset to work with other SO files.
var ptr = GetBigEndianUInt32(bytes, addr+0xD60);
var length = GetBigEndianUInt32(bytes, addr+8);
var compressedbytes = new byte[length];
Array.Copy(bytes, ptr, compressedbytes, 0, length);
try
{
var decompbytes = Decompress(compressedbytes);
File.WriteAllBytes($@"{packedFiles[i]}", decompbytes);
}
catch (Exception e)
{
Console.WriteLine($"Failed to decompress file: {packedFiles[i]} {e}");
}
addr += 0x10; //sizeof assembly_bundle struct
}
}
static byte[] Decompress(byte[] data)
{
using (var compressedStream = new MemoryStream(data))
using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var resultStream = new MemoryStream())
{
zipStream.CopyTo(resultStream);
return resultStream.ToArray();
}
}
public static uint GetBigEndianUInt32(byte[] bytes, uint address)
{
uint byte1 = (uint)bytes[(int)address + 3] << 24;
uint byte2 = (uint)bytes[(int)address + 2] << 16;
uint byte3 = (uint)bytes[(int)address + 1] << 8;
uint byte4 = (uint)bytes[(int)address];
return (byte1 + byte2 + byte3 + byte4);
}
public static string GetString(byte[] bytes, uint address)
{
int maxLength = 255;
//Search for a null char up to the limit
for (int i = (int)address; i < address + maxLength; i++)
{
if (bytes[i] == 0)
{
maxLength = i - (int)address;
break;
}
}
var buffer = new byte[maxLength];
Array.Copy(bytes, address, buffer, 0, maxLength);
return Encoding.ASCII.GetString(buffer);
}