从 libmonodroid_bundle.app.so 解压 xamarin mono dll

逆向工程 安卓 手臂 dll 包装工
2021-06-12 15:53:40

我有一个用 xamarin 编写的 apk。元数据表明该应用程序使用了大量 dll 文件。我发现了一个名为 libmonodroid_bundle_app.so 的文件,当在 IDA 中反汇编时,它似乎是一个打包程序/解包程序,具有诸如 inflate、my_inflate、install_dll_config_files 等功能。

我想解压缩其中包含的任何 dll 文件,有人可以提供任何建议吗?有一个 x86 编译的 .so 文件,所以也许我可以以某种方式在 Windows 中执行它。

编辑:它似乎与 zlib 打包在一起。文件中有很好的命名标题,所以我将尝试导出原始数据,然后使用 zlib 库进行解压缩。

4个回答

感谢发现它的原始海报(以及最重要的其他建议)。我不得不审计我们自己的一些 xamarin 应用程序,并想解压缩代码,以便我拥有攻击者能够获得的信息。反编译 .so 并检查头文件后,我确定可以从文件中导出魔术偏移量(使用您拥有的 dll 数量)。不过,在花费了几个小时之后,结果证明只提取文件中的所有 gzip 流会更快:) 所以,谢谢大家,这是我最终完成工作的代码:

using System;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using System.Text;

using Kaitai; //http://formats.kaitai.io/elf/csharp.html

public class unpack {
static void Main(string[] args)
    {
        var unpacked = "unpacked";
        Directory.CreateDirectory(unpacked);

        var path = @"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;
        }

        int dllCount = 0;

        //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 dlls
            if(!name.EndsWith(".dll"))
                break;

            packedFiles.Add(name);
            addr += (uint)name.Length + 1u;
            ++dllCount;
        }


        var data = elf.Header.SectionHeaders.FirstOrDefault(x => x.Name.Equals(".data"));
        if(data == null)
        {
            Console.WriteLine(".data not found");
            Console.ReadKey();
            return;
        }

        int ixGzip = 0;

        //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; )
        {
            ixGzip = findNextGZIPIndex(bytes, ixGzip);
            if(ixGzip > 0)
            {
                var ptr = ixGzip;
                var length = GetBigEndianUInt32(bytes, addr + 8);

                var compressedbytes = new byte[length];
                if(ptr + length <= bytes.LongLength)
                {
                    Array.Copy(bytes, ptr, compressedbytes, 0, length);
                    try
                    {
                        var decompbytes = Decompress(compressedbytes);
                        File.WriteAllBytes($@"{unpacked}\{packedFiles[i]}", decompbytes);
                        addr += 0x10; //sizeof assembly_bundle struct
                        i++;
                    }
                    catch(Exception e)
                    {
                        Console.WriteLine($"Failed to decompress file: {packedFiles[i]} {e}");
                    }
                }
            }
        }
    }

    private static int findNextGZIPIndex(byte[] bytes, int ixGzip)
    {
        for(int j = ixGzip + 2; j < bytes.Length; j++)
        {
            if(bytes[j - 1] == 0x1f && bytes[j] == 0x8b)
            {
                ixGzip = j - 1;
                return ixGzip;
            }
        }

        return 0;
    }

    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);
    }
}

这在跨平台上非常有效。确保安装这些 python 包:

sudo pip install pyelftools
sudo pip install yara-python

这将从以下位置导出所有 dll libmonodroid_bundle_app.so

from elftools.elf.elffile import ELFFile
from zipfile import ZipFile
from cStringIO import StringIO
import gzip, string

data = open('libmonodroid_bundle_app.so').read()
f = StringIO(data)
elffile = ELFFile(f)
section = elffile.get_section_by_name('.dynsym')

for symbol in section.iter_symbols():
  if symbol['st_shndx'] != 'SHN_UNDEF' and symbol.name.startswith('assembly_data_'):
    print symbol.name
    dll_data = data[symbol['st_value']:symbol['st_value']+symbol['st_size']]
    dll_data = gzip.GzipFile(fileobj=StringIO(dll_data)).read()
    outfile = open(symbol.name[14:].replace('_dll', '.dll'), 'w')
    outfile.write(dll_data)
    outfile.close()

改编自https://github.com/maldroid/maldrolyzer/blob/master/plugins/z3core.py

这是我在 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);
}

如果您想提取这些打包文件(假设它们确实已打包),我建议使用 File Carver 取证工具 - 我使用基于 Unix 的程序,最重要的是.

File Carvers 可以在其他文件中搜索并尝试查找表示文件开头的文件头或签名。

如果您成功地在搜索的任何文件中找到文件,您可能会发现它们尚不可访问,因为您说它们是用 zlib 压缩的 - 很简单!只需将该文件重命名为 somename.zip 并解压缩它,或者如果您真的想要,自己编写一些代码来解压缩它们。

祝你好运!