我还看得起你 发表于 2021-9-2 13:03:49

U3D逆向-Mono另类解密

如题所示,U3D的Mono解密,这个玩意我昨晚在我的技术群里貌似看到说U3D是要弃用了Mono这个机制,也不知道是真是假,趁着还没弃用,我也就来说说这个机制的一个另类解密。
       关于Mono这东西我就不啰嗦了,我就直接进入正题。众所周知,Mono的加密主要是针对 Assembly-CSharp.dll,此DLL包含了游戏的所有功能性函数,并且可以通过工具dnSpy.exe加载后进行查看。

 此DLL公开等于说源码公开,可以通过C#工程引入该DLL自写一个GameObject注入到游戏里调用游戏自带函数实现作弊。

 

 大多的加密手段就是对这DLL进行二进制处理,也就是把文件字节给进行处理。对于这种加密的处理方式很简单,Mono.dll是U3D用来初始化并加载dll的一个模块,他里面有一个函数mono_image_open_from_data_with_name,这里放一下他的函数代码
MonoImage *
mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)
{
    return mono_image_open_from_data_internal (data, data_len, need_copy, status, refonly, FALSE, name);
}
MonoImage *
mono_image_open_from_data_internal (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, gboolean metadata_only,
                const char *name)
{
    MonoCLIImageInfo *iinfo;
    MonoImage *image;
    char *datac;

    if (!data || !data_len) {
      if (status)
            *status = MONO_IMAGE_IMAGE_INVALID;
      return NULL;
    }
    datac = data;
    if (need_copy) {
      datac = (char *)g_try_malloc (data_len);
      if (!datac) {
            if (status)
                *status = MONO_IMAGE_ERROR_ERRNO;
            return NULL;
      }
      memcpy (datac, data, data_len);
    }

    image = g_new0 (MonoImage, 1);
    image->raw_data = datac;
    image->raw_data_len = data_len;
    image->raw_data_allocated = need_copy;
    image->name = (name == NULL) ? g_strdup_printf ("data-%p", datac) : g_strdup(name);
    iinfo = g_new0 (MonoCLIImageInfo, 1);
    image->image_info = iinfo;
    image->ref_only = refonly;
    image->metadata_only = metadata_only;
    image->ref_count = 1;

    image = do_mono_image_load (image, status, TRUE, TRUE);
    if (image == NULL)
      return NULL;

    return register_image (image);
}  看不懂没关系,我们只需要关注他的data,data_len,name这三个参数,这三个分别表示当前被加载模块的二进制内容,二进制长度,模块名。大部分游戏厂商都会在这里判定模块名是否为Assembly-CSharp,然后进行二进制内容解密。那么只需要用调试工具在这个函数下段,然后在这里分析竣事的位置,然后直接dump即可。这里放一下大概代码


 直接在解密完毕的位置下断,然后把rdi给dump出来就行,得到的就是解密后的dll。也可以自写脚本。



____________________________________________________________________________________________________________________________________________________________

上面先容了Assembly-CSharp的一种加密和解密方式,虽说是加密,但是非常下饭,基本有手就行。今天就来说说另一种加密方式。小白鼠是国内某款游戏,现在应该应该不开放了,但是游戏依旧躺在我硬盘里。该游戏同样是加密Assembly-CSharp,但是有个不同点就是该文件只有1kb,这个就很可疑,因为这等于说这文件是空的,用十六进制打开文件也能看出里面无内容,那么游戏很大概是联网获取新的,大概是内存开释。那么除了去分析后解密,就没有办法拿到解密后的文件吗,答案是否定的。这个时候就需要上点硬技术了。
static MonoImage *
register_image (MonoImage *image)
{
    MonoImage *image2;
    GHashTable *loaded_images = get_loaded_images_hash (image->ref_only);         //       重点关注对象

    mono_images_lock ();
    image2 = (MonoImage *)g_hash_table_lookup (loaded_images, image->name);

    if (image2) {
      /* Somebody else beat us to it */
      mono_image_addref (image2);
      mono_images_unlock ();
      mono_image_close (image);
      return image2;
    }

    GHashTable *loaded_images_by_name = get_loaded_images_by_name_hash (image->ref_only);
    g_hash_table_insert (loaded_images, image->name, image);                     //       重点关注对象
    if (image->assembly_name && (g_hash_table_lookup (loaded_images_by_name, image->assembly_name) == NULL))
      g_hash_table_insert (loaded_images_by_name, (char *) image->assembly_name, image);
    mono_images_unlock ();

    return image;
}  这里可以清晰的看到,U3D直接把需要的加载的模块插入了一个HashTable,然后完成模块的一个装载。这里我们先不管这个模块进行了如何加密和解密,既然你要扔给U3D托管,那么你不大概扔了一个加密模块的给U3D托管,除非你的U3D引擎进行了一个大规模的魔改。所以说,我们只需要找到这个HashTable,然后去遍历一下里面的模块,我们是不是就能拿到解密后的Assembly-CSharp,答案是肯定的。那么现在重点对象从 如何解密Assembly-CSharp变成了 如何使用loaded_images。
 我们应该如何去找到这个loaded_images?没办法,上分析。调试器打开游戏跳到mono_image_open_from_data_with_name这个函数然后对照源码进行分析。


 从源码得知,register_image是最后一个调用的函数,我们直接找到最后一个函数,进去后分析得知。




拿到loaded_images后,我们下一步就是要去遍历这个HashTabel,两个办法,一个是自己去搭建GHashTable库,然后自己去跑一遍。第二个办法,自己去分析g_hash_table_insert函数的流程。我选择后者 =。=
        Ida打开mono.dll后搜索g_hash_table_insert,有一个

        进去F5后无脑暴力分析。

 分析了一个大概流程后,打开CE,用数据布局工具分析验证看看

 很显着,这里应该就是模块列表,我们再任意进去一个去看看。通过刚刚IDA的分析,0x0是模块名,0x8是image。




 和我们刚刚分析的一致,现在我们还差一个东西,那就是image的布局,我们在源码查找一下。
struct _MonoImage {
         ………
         guint32 raw_data_len;
         char *raw_data;                   //模块二进制
         char *name;            //模块名
         …………
}  这里就贴出重要的。问题来了,我们怎么知道他的偏移。别忘了,这里有
MonoImage *mono_image_open_from_data_internal (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, gboolean metadata_only,               const char *name){    MonoCLIImageInfo *iinfo;    MonoImage *image;    char *datac;   if (!data || !data_len) {      if (status)            *status = MONO_IMAGE_IMAGE_INVALID;      return NULL;    }    datac = data;    if (need_copy) {      datac = (char *)g_try_malloc (data_len);      if (!datac) {            if (status)                *status = MONO_IMAGE_ERROR_ERRNO;            return NULL;      }      memcpy (datac, data, data_len);    }   image = g_new0 (MonoImage, 1);    image->raw_data = datac;
页: [1]
查看完整版本: U3D逆向-Mono另类解密