查看原文
其他

内存dump 获得基于Unity3d的游戏相关代码

lhfzhh 看雪学院 2021-03-08


本文为看雪论坛优秀文章

看雪论坛作者ID:lhfzhh



1、准备



解压apk,在lib文件夹看到有libmono.so,libunity.so 便是Unity3d的程序。又看到有libDexHelper.so,便是梆梆的加固,把 libmono.so拖到IDA是解析不出来的。然后去看 assets\bin\Data\Managed 下,便是 Unity3d的.NET程序,把对应dll拖到ILSpy 也是不识别的。

这种的加密手段一般都是通过修改 mono代码 (现在都是这个函数mono_image_open_from_data_with_name) 在加载dll的时候进行解密,所以需要先还原 libmono.so。


2、dump内存



用什么dump呢,自然是需要找一个好点的工具,我是用了GameGuardian来导出内存。

需要注意的是,需要配置在激活  GameGuardian 的时候,暂停程序,因为程序一般都会监听 /proc/pid/mem或/proc/pid/pagemap,遇到读内存的时候杀掉自身进程。用这个工具有时候也是不能导出内存,也不知道为什么,有时候就可以。



选择对应的libmono.so地址,主要怕导出的少了,就导出的范围广一些, 14c00000-15000000。导出到文件后就开始分析了。


3、修正dump的so



选用010 Editor来编辑二进制文件,这个工具上有模板插件帮助分析elf文件格式。



修正内存dump出来的so文件还是比较简单的,主要是修正program_header_table。


重要的是Loadable Segment和Dynamic Segment,前一个是代码段,后一个是符号表。其他段完全可以设置为0。基本原理就是系统加载so的过程,主要就是段的地址,大小及对齐方式在文件和内存中可能不一样。


对于IDA来讲是解析的文件形式的。那么只要把对应文件里面的段地址及大小和对齐改成和内存中的一致就可以了。如下图修改前和修改后的对比。


修改前的段的地址:


修改后的段的地址:


主要就是修改 p_offset_FROM_FILE_BEGIN 和 p_filesz_SEGMENT_FILE_LENGTH 为对应的内存地址。

至于section就忽略了。

然后用IDA打开就能看到对应的代码了:


搜索一下字符串Assembly-CSharp,就能看到对应的代码引用:




4、查找dll



简单分析一下应该是用了xxtea的一个变种加密算发,因为从网上找了源码解码对应的dll不成功。怎么办呢?难道要分析解码过程? 这有点麻烦。

想想还是从内存中找吧,毕竟mono也是按照windows的PE格式加载dll的。但是dll的内存地址信息不会像so一样可以通过/proc/pid/maps等看到,那就只有把内存全部dump出来再找了。

还是用 GameGuardian导出所有地址的内容,他会按照 /proc/pid/maps指明的段保存内存。文件太多怎么找呢?只有写程序了,毕竟PE的格式也是固定的。

查找过程如下代码:

char * findDosHeader(char * buffer, int length, int *out_length) {
    char * pe = NULL;
    for (int i = 0; i < length - 0x200; i++) {
        WORD * p = (WORD *)(buffer + i);
        if (*p != IMAGE_DOS_SIGNATURE) {
            continue;
        }
        IMAGE_NT_HEADERS * ntheader = (IMAGE_NT_HEADERS *)(buffer + 0x80 + i);
        if (ntheader->Signature != IMAGE_NT_SIGNATURE){
            continue;
        }
        IMAGE_FILE_HEADER * fileheader = &ntheader->FileHeader;
        if (!(fileheader->Characteristics & IMAGE_FILE_DLL)){
            continue;
        }
        IMAGE_SECTION_HEADER * section = IMAGE_FIRST_SECTION(ntheader);
        int size = 0;
        for (int j = 0; j < fileheader->NumberOfSections; j++) {
            if ((section + j)->SizeOfRawData + (section + j)->PointerToRawData > size) {
                size = (section + j)->SizeOfRawData + (section + j)->PointerToRawData;
            }
        }
        *out_length = size;
        return (buffer + i);
    }
    return NULL;
}
 
void find(const char * fileName) {
    FILE *fp = NULL;
    fopen_s(&fp, fileName, "rb");
    if (fp == NULL) {
        return;
    }
    fseek(fp, 0L, SEEK_END);
    int length = ftell(fp);
    fseek(fp, 0L, SEEK_SET);
 
    char * pFileBuffer = new char[length];
    fread_s(pFileBuffer, length, 1, length, fp);
    char* ppe = pFileBuffer;
    int image_length = 0;
    int buffer_length = length;
    while (true)
    {
        char f[512];
        buffer_length -= image_length;
        ppe = findDosHeader(ppe + image_length, length - (ppe - pFileBuffer) - image_length, &image_length);
        if (ppe != NULL) {
            sprintf_s(f, "%s.%x-%x.dll", fileName, ppe, image_length);
            FILE* outfp = 0;
            fopen_s(&outfp, f, "wb+");
            fwrite(ppe, image_length, 1, outfp);
            fclose(outfp);
            printf("%x %x\n", ppe, image_length);
        }
        else{
            break;
        }
    }
     
    delete[] pFileBuffer;
    fclose(fp);
}


查完后会把找到的dll存储到对应的文件夹下。

然后用ILSpy打开查看dll,呵呵,代码都正常显示。

当然,如果想要改代码,要么是查解码dll的算法,要么就是直接修改没有加密的dll,植入相关的代码也可以。如果要是把dll也加密混淆一下,就伤脑筋了。





- End -







看雪ID:lhfzhh

https://bbs.pediy.com/user-559996.htm 


*本文由看雪论坛  lhfzhh  原创,转载请注明来自看雪社区







推荐文章++++

Android逆向入门练习——滑雪大冒险支付分析

AS 3.0.1 编写 Xposed 插件入门记录

ECC椭圆曲线加密学习笔记

Dalvik解释器源码到VMP分析

CVE-2017-13258 Android 蓝牙BNEP漏洞分析







进阶安全圈,不得不读的一本书










“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存