MAC版E信心跳包加密KEY的逆向

来源:互联网 发布:hse风险矩阵标准 编辑:程序博客网 时间:2024/05/16 14:54

    在开始正题前先扯点别的吧,这篇博文本来应该在几个月前我还在校的时候就写的,但由于我当时懒得去写博客,加之在我逆向出MAC版E信心跳包加密KEY之后的下一周SimpleNetkeeper作者就在GitHub上公布了加密KEY,我就觉得写不写这篇博文也没太大要紧了,但现在回顾了下当时的逆向过程,发现我好多东西都已经不太记得了,趁还没有完全忘光之前,写下这篇博文作为记录,并且我也已经脱坑了,这篇博文还可以给各位还在坑里的人参考。

    正文之前先放一个讲Netkeeper拨号流程的分析博文的链接:https://sunflyer.cn/archives/239,此博文是SimpleNetkeeper作者写的,我就是读了这个才了解到心跳包这个东西的,以前写E信自由版时都不知道有心跳包这个东西,不过这也和当时湖北地区并未开启心跳服务器有关,湖北地区是后来才开启了心跳服务器。

    好了,正文开始,有人可能会问,为什么要去逆向MAC版E信?如果会逆向的话,直接去逆向Windows版本的E信岂不是更容易,但事实是如果使用壳检测工具会发现Windows版本的E信使用了VMP壳来保护程序,壳信息如下图


    并且windows版本E信有具有反调试、反RAM DUMP的能力,我这初入逆向大坑的小白怎么可能去脱掉VMP壳,还有我就会点静态分析,动态分析根本就没玩过。加上我听SimpleNetkeeper作者说过MAC版E信并未混淆或加壳,那我当然只有去逆向MAC版E信这一种选择了,逆向MAC版E信最大的难点就在于需要了解一点80x86汇编语言以及Objective-C语言的知识,如果你学过任何一种架构的CPU的汇编语言,那么你对其他架构的CPU的汇编语言其实也可以触类旁通,基本的指令每个架构的CPU指令集都会有,只不过是叫法以及使用方法不一样,比如我学过ARM汇编语言,对于80x86汇编语言我当时也只是看了两篇介绍80x86汇编语言以及80x86的CPU寄存器的博文后就开始去逆向MAC版E信了;另外就是Objective-C语言的问题了,学过任何一门面向对象的语言后对于其他面向对象的语言其实也是可以很快就能了解其大致内容的,计算机编程语言就是这么的微妙,它们基本上都是使用了同一个思想(比如面向对象的语言,其主要思想是面向对象,这句话好像是废话),只是具体实现不一样而已。

    MAC版E信的安装包是一个后缀为.pkg的文件,使用windows下的压缩软件可以打开,但打开后发现其只有一个名为Payload~的文件


    到网上搜到了如何在MAC下解压PKG文件,其先使用xar命令解压.pkg文件,然后使用cat命令和cpio命令,具体如下

xar -xf you_file.pkgcat unpacked_file.pkg/Payload | cpio -i
    在MAC OS X下解压过程如下图

xar命令会把原始.pkg文件解压成app.pkg文件夹、Resources文件夹以及Distribution文件,然后cat命令加cpio命令会将app.pkg文件夹里的Payload文件处理成“E信.app”文件夹,在MAC下其将app.pkg文件夹当成文件,E信.app文件夹当成应用,在windows下查看就一目了然了,如下图


解压完成后MAC版E信的主程序在"E信.app\Contents\MacOS"文件夹下,文件名为“E信”,当时在搜索解压.pkg文件时貌似到网上看到过说能在liunx下解压MAC OS的.pkg文件,于是用linux试了下发现根本就没有xar命令,就放弃了用linux解压.pkg文件,现在我试了下在linux下使用tar命令解压.pkg文件,但还是解压不了,如下图



后来在windows下使用360压缩把Payload~文件解压出来再放到linux下直接使用cat和cpio命令发现可以成功解压



    好了,既然MAC版E信的主程序文件已经得到,那么下一步就是使用IDA静态分析主程序文件了,将E信主程序文件拖入IDA,等待IDA自动分析结束,在将MAC版E信的主程序拖入IDA后,IDA提示找到了Objective-C 2.0的结构体信息,是否需要解析结构体信息并根据这些信息重命名方法,那肯定是点Yes了,逆向过程中能得到的有用信息越多对于逆向越有利


    接着IDA接开始自动分析了,自动分析完成后的结果如下:


    左边是IDA分析出来的函数列表的窗口,果然MAC版E信并没有混淆,有些函数的函数名都直接分析出来了,右边面积比较大的窗口是IDA的反汇编窗口,里面就是各种汇编函数了,IDA分析完就直接停在了_main函数这里,先简单看下函数列表里有没有涉及到心跳的函数,发现果然有一些"HeartBeat"类的实例函数,


    注意这里的HeartBeat并不是函数的返回类型,而是类名,init是方法名(在面向对象的语言里函数一般被称为方法),可以看到,反汇编窗口中IDA给出的注释为HeartBeat - (id)init,id才是这个方法的真正返回类型,Objective-C里面的id类型类似于C#里面的Object类型,



这个函数的真实函数名为id __cdecl -[HeartBeat init](struct HeartBeat *self, SEL),id为返回类型,__cdecl说明这个函数的调用方式为C函数默认的调用方式(可以自己百度下cdecl),HeartBeat init为类名和方法名,方法的第一个参数为指向struct HeartBeat类型的指针,第二个参数是Objective-C中特有的节(selector)字符串,Objective-C中默认第二个参数都是SEL类型,这个参数在IDA的反汇编窗口可能没有定义形参名,但是在IDA生成的伪C代码中会有形参名,如下图


这里就体现出来了Objective-C实现面向对象思想的本质其实是使用了C里面的结构体,当然具体实现起来肯定没这么容易。看过了这些带有HeartBeat的函数的伪代码之后发现并没有什么帮助,都没有涉及到心跳加密算法的KEY,于是只好去看看程序里包含的字符串,打开IDA的Strings窗口,以HeartBeat为关键字搜索发现有许多个包含HeartBeat关键字的结果,其中一个结果为"TYPE=HEARTBEAT&USER_NAME=%@&PASSWORD=%@

&IP=%@&MAC=%@&VERSION_NUMBER=%@&PIN=MAC_TEST&DRIVER=1"这个结果貌似挺有用的,和SimpleNetkeeper作者博客里提到的心跳包内容一致,但转到IDA反汇编窗口查看数据的交叉引用并没有发现啥很有帮助的内容,然后又找了好几处交叉引用,始终没能找到很有帮助的东西。

    后来通过E信所使用的心跳加密算法——AES和3DES找到了对应的加密函数,如下图


其中XinliAES的key方法的伪C代码如下

// XinliAES - (id)keyid __cdecl -[XinliAES key](struct XinliAES *self, SEL a2){  return (id)self->key;}

其只是返回了XinliAES类的实例的成员key,这里IDA貌似直接把属性给识别成了方法了,不过这也恰好印证了属性是带方法的字段这一说法,看了几个XinliAES类的方法的伪C代码后发现其init方法里有给成员key赋值的语句,init方法的伪代码如下

// XinliAES - (id)initid __cdecl -[XinliAES init](struct XinliAES *self, SEL a2){  struct objc_object *v2; // rax@1  struct objc_object *v3; // rbx@1  __int64 v5; // [sp+8h] [bp-18h]@1  void *v6; // [sp+10h] [bp-10h]@1  v5 = (__int64)self;  v6 = classRef_XinliAES_0;  LODWORD(v2) = msgRef_init__objc_msgSendSuper2_fixup(&v5, &msgRef_init__objc_msgSendSuper2_fixup);  v3 = v2;  if ( v2 )  {    msgRef_setKey___objc_msgSend_fixup(v2, &msgRef_setKey___objc_msgSend_fixup, &cfstr_Xinli_zhejiang);    v3[3].isa = 0LL;    v3[2].isa = 0LL;  }  return v3;}

其伪C代码的第15行调用了一个静态方法,并且这个静态方法的函数名中含有setKey字样,如果了解Objective-C的话就应该知道这句代码是在给XinliAES类的实例方法setKey发送消息,相当于就是调用setKey方法,这个静态方法有三个参数,最后一个为指向CFString的指针,CFString是Core Foundation 字符串,CoreFundation框架是苹果公司专有的框架,不管它是否加了CF前缀,反正只要知道这是个字符串就可以了,通过IDA的交叉引用,很快就知道了这个字符串的内容为“xinli_zhejiang12”。并且第16行和第17行给某个字段或是属性赋了值0,还能和AES扯上关系的只有分组加密算法的初始化向量(IV)了。




那么在XinliDES的init方法和Xinli3DES类的init方法中肯定也能找到DES加密算法的KEY和3DES加密算法的KEY,果不其然,另外两个KEY也找到了



于是乎MAC版E信的心跳包加密KEY就这样找到了,还需要确定的就是AES和3DES加密算法是使用的什么加密模式以及数据填充方式,因为AES和3DES都是属于分组加密算法,分组加密算法有几种不同的加密模式,一般常用的有ECB,CBC,CFB,OFB这四种,那么就来看看Xinli3DES和XinliAES的encode方法吧,先看Xinli3DES的encode方法的伪C代码

// Xinli3DES - (id)encode:(id) id __cdecl -[Xinli3DES encode:](struct Xinli3DES *self, SEL a2, id a3){  id v3; // rbx@1  id result; // rax@1  __int64 v5; // rax@2  __int64 v6; // rcx@2  __int64 v7; // rax@3  __int64 v8; // rax@3  __int64 v9; // r12@3  size_t v10; // rax@3  size_t v11; // rbx@3  unsigned __int64 v12; // r13@3  void *v13; // r15@3  const void *v14; // rax@3  __int64 v15; // rax@3  __int64 v16; // rax@5  __int64 v17; // rax@5  __int64 v18; // [sp+30h] [bp-30h]@3  v3 = a3;  result = 0LL;  if ( a3 )  {    LODWORD(v5) = msgRef_length__objc_msgSend_fixup(a3, &msgRef_length__objc_msgSend_fixup);    v6 = v5;    result = 0LL;    if ( v6 )    {      LODWORD(v7) = msgRef_nsStringEncodingByName___objc_msgSend_fixup(                      classRef_nkUtils,                      &msgRef_nsStringEncodingByName___objc_msgSend_fixup,                      &cfstr_Gbk_1);      LODWORD(v8) = msgRef_dataUsingEncoding___objc_msgSend_fixup(                      v3,                      &msgRef_dataUsingEncoding___objc_msgSend_fixup,                      v7);      v9 = v8;      v18 = 0LL;      LODWORD(v10) = msgRef_length__objc_msgSend_fixup(v8, &msgRef_length__objc_msgSend_fixup);      v11 = v10;      v12 = (v10 + 8) & 0xFFFFFFFFFFFFFFF8LL;      v13 = malloc(v12);      LODWORD(v14) = msgRef_bytes__objc_msgSend_fixup(v9, &msgRef_bytes__objc_msgSend_fixup);      memcpy(v13, v14, v11);      memset((char *)v13 + v11, v12 - v11, v12 - v11);      LODWORD(v15) = msgRef_UTF8String__objc_msgSend_fixup(self->key, &msgRef_UTF8String__objc_msgSend_fixup);      if ( CCCrypt(0LL, 2LL, 0LL, v15, 24LL, self->iv) )      {        free(v13);        result = 0LL;      }      else      {        LODWORD(v16) = msgRef_alloc__objc_msgSend_fixup(classRef_NSData, &msgRef_alloc__objc_msgSend_fixup);        LODWORD(v17) = msgRef_initWithBytesNoCopy_length___objc_msgSend_fixup(                         v16,                         &msgRef_initWithBytesNoCopy_length___objc_msgSend_fixup,                         v13,                         v18);        LODWORD(result) = msgRef_autorelease__objc_msgSend_fixup(v17, &msgRef_autorelease__objc_msgSend_fixup);      }    }  }  return result;}


代码比较长,并且由于是IDA反编译出来的伪C代码,所以变量名啥的都是用的比较晦涩的名称,具体这段代码我就不分析了(其实是懒得再去详细看了),当时分析过,并且修改了变量的名称,也加了注释,修改后的伪C代码如下

// Xinli3DES - (id)encode:(id) id __cdecl -[Xinli3DES encode:](struct Xinli3DES *self, SEL a2, id data){  id orginal_data; // rbx@1  id result; // rax@1  __int64 data_length; // rax@2  __int64 v6; // rcx@2  __int64 Encoding_Type; // rax@3  __int64 GBK_Encoding_data; // rax@3  __int64 GBK_Encoding_data2; // r12@3  size_t GBK_Length; // rax@3  size_t copy_count; // rbx@3  unsigned __int64 after_padding_length; // r13@3  void *after_padding_data; // r15@3  const void *data_byte_array; // rax@3  __int64 _3des_key; // rax@3  __int64 v16; // rax@5  __int64 v17; // rax@5  __int64 v18; // [sp+30h] [bp-30h]@3  orginal_data = data;  result = 0LL;  if ( data )                                   // 判断传入的指针是否为NULL  {    LODWORD(data_length) = msgRef_length__objc_msgSend_fixup(data, &msgRef_length__objc_msgSend_fixup);// 获取传入数据的长度    v6 = data_length;    result = 0LL;    if ( v6 )                                   // 判断传入的数据的长度是否为0    {      LODWORD(Encoding_Type) = msgRef_nsStringEncodingByName___objc_msgSend_fixup(                                 classRef_nkUtils,                                 &msgRef_nsStringEncodingByName___objc_msgSend_fixup,                                 &cfstr_Gbk_1); // 获取编码的类型      LODWORD(GBK_Encoding_data) = msgRef_dataUsingEncoding___objc_msgSend_fixup(                                     orginal_data,                                     &msgRef_dataUsingEncoding___objc_msgSend_fixup,                                     Encoding_Type);// 使用GBK编码传入的数据      GBK_Encoding_data2 = GBK_Encoding_data;      v18 = 0LL;      LODWORD(GBK_Length) = msgRef_length__objc_msgSend_fixup(GBK_Encoding_data, &msgRef_length__objc_msgSend_fixup);// 获取使用了GBK编码的数据的长度      copy_count = GBK_Length;      after_padding_length = (GBK_Length + 8) & 0xFFFFFFFFFFFFFFF8LL;// 可能是为了对其到8字节长度,也即加密时的Padding      after_padding_data = malloc(after_padding_length);// 申请内存空间      LODWORD(data_byte_array) = msgRef_bytes__objc_msgSend_fixup(GBK_Encoding_data2, &msgRef_bytes__objc_msgSend_fixup);// 获取字节数组      memcpy(after_padding_data, data_byte_array, copy_count);      memset(        (char *)after_padding_data + copy_count,        after_padding_length - copy_count,        after_padding_length - copy_count);     // 将padding的每个字节设置为长度,即PKCS5的padding模式      LODWORD(_3des_key) = msgRef_UTF8String__objc_msgSend_fixup(                             *(_QWORD *)&self->NSObject_opaque[OBJC_IVAR___Xinli3DES_key],                             &msgRef_UTF8String__objc_msgSend_fixup);// 获取由UTF8编码的AES key      if ( CCCrypt(0LL, 2LL, 0LL, _3des_key, 24LL, (__int64)((char *)self + OBJC_IVAR___Xinli3DES_iv)) )      {        free(after_padding_data);               // 如果加密失败则释放内存空间        result = 0LL;                           // 返回空指针      }      else      {        LODWORD(v16) = msgRef_alloc__objc_msgSend_fixup(classRef_NSData, &msgRef_alloc__objc_msgSend_fixup);        LODWORD(v17) = msgRef_initWithBytesNoCopy_length___objc_msgSend_fixup(                         v16,                         &msgRef_initWithBytesNoCopy_length___objc_msgSend_fixup,                         after_padding_data,                         v18);                  // 可能是拷贝        LODWORD(result) = msgRef_autorelease__objc_msgSend_fixup(v17, &msgRef_autorelease__objc_msgSend_fixup);      }    }  }  return result;}

上面的伪C代码好像只能看出3DES的填充模式是PKCS7,无法看出是什么加密模式,但在Xinli3DES的init方法里找到了初始化向量为87654321,猜测其最有可能为CBC模式,另外XinliAES的encode方法的伪C代码也只能看出AES的填充算法是PKCS7。

    由于SimpleNetkeepr作者说过心跳加密算法只用了AES和3DES算法中的一种,后面使用了AES/ECB/PKCS7,AES/CBC/PKCS7,3DES/ECB/PKCS7,3DES/CBC/PKC7四种具体模式分别尝试解密心跳包内容,发现只有AES/ECB/PKCS7这种模式能成功解密心跳包,发送的心跳包内容和上面找到的字符串"TYPE=HEARTBEAT&USER_NAME=%@&PASSWORD=%@&IP=%@&MAC=%@&VERSION_NUMBER=%@&PIN=MAC_TEST

&DRIVER=1"一致,将其中的“%@”换为相应的内容即可。我在测试的过程中发现即使IP地址写得不对服务器端一样会返回正确心跳应答包,可能是服务器端没有检测IP地址是否正确的逻辑吧,并且湖北地区好像只有周一到周五开启了心跳服务器,周六周日好像就关了,我当时写了一个另外的程序专门负责发送心跳包,但这个程序也不能100%的保证不掉线,有时可能才发了4次心跳包就掉线了,或者发了心跳包服务器根本就不返回心跳应答包,可能和我没有去写获取心跳配置的逻辑有关吧,程序比较简单,我就不放出来了。

END



原创粉丝点击