手脱定制版的android SO UPX壳

来源:互联网 发布:淘宝信誉度 编辑:程序博客网 时间:2024/05/21 11:03

转自:https://bbs.pediy.com/thread-221997.htm

一 前言


       加固厂商SO的加壳,一般都是采用UPX壳实现的。如果用原生的进行加壳,可以使用原生UPX进行脱壳。但是对于定制的UPX壳使用原生UPX是无法脱壳的。定制版的UPX一般有以下2种形式:

   

       1  将里面的魔术字符串改掉。下图是UPX源码中定义的魔术字以及修改后的例子:


                                  源码中的魔术字                                                                                           修改后的例子

                   

                 2 加密算法改掉或数据结构进行更改。  

                 这里讨论是如何手脱一个定制的UPX壳,尤其是更改加密算法或者数据结构的定制的UPX壳(以AJM为例)。先看一下脱壳后的样子:
          
                                                             脱壳前                                                                                                               脱壳后
                       
                 

二  UPX壳loader

        加壳一般需要加密和解密2个过程,UPX壳也不例外。UPX壳的解密是由 upx loader实现的。在UPX加壳过程中,将loader放在init段,确保loader先获得程序执行的控制权。

        Loader程序是由汇编代码实现的,下面是upx源码中针对arm平台的汇编代码:

                                                          
        在 dynamic段中,init标志值为:0x0C,见下图:
                                                            

        下面是loader在dynamic中的位置:

                                                                      

        当loader开始运行时的步骤如下:

                                                             

        从loader执行流程可以看到如下:

        1、loader是由汇编代码内嵌实现的,因此要解决重定位的问题,所以其中的函数调用都是使用系统调用实现的,比如mmap 以及mprotect等函数。        

        2、loader 程序要将加密数据解密,解密后的数据要覆盖加密数据。

        3、由于upx使用的是压缩算法,因此解密后的数据会比加密的数据大,此时loader程序也会被解密后的数据覆盖,所以loader在解密前要拷贝到内存中执行。


三  linker加载so

       Android系统的linker加载是通过dlopen函数实现的。以4.4源码为例如下:

                                                               

四  手脱UPX壳的原理

        前面说加壳一般需要加密和解密2个过程,UPX壳也一样。我们手脱指的是不用研究加密算法,动态脱壳。当upx的loader执行完后,代码也解密完成了,并且加密的数据被解密后的数据覆盖,是否可以直接在这个时间点,将SO从内存中dump出来就可以了呢?

       

        确实可以在这个时间点将so从内存中dump出来,但是只有这样是不行的,需要解决以下4个问题:

             ●  由于此时代码已经重定位完成,dump后plt段等信息已经包含了重定位后的信息,也就是绝对地址,此时静态用IDA打开,很多函数都无法识别。

             ●  Segment段中的Load段需要恢复,UPX加密后将原始的load段的大小由原来的加密前的大小改成了加密后的大小,因此需要修复。

             ●  Segment段中的 dynamic段在文件中的偏移需要修复。UPX将dynamic在文件中的偏移改变了。

             ●  section 段修复。对于section段的修复论坛里面有很多,这里就不讲了。

        因此解决了前面三个问题就可以实现手脱UPX壳了。

       

1 解决重定位的问题

        我们的目标是手脱UPX壳,因此我们可以将linker中执行重定位代码nop掉,就是不让linker执行重定位的操作,自然PLT等段中就不会带有重定位的信息。

        通过前面分析linker加载so的过程,soinfo_link_image调用soinfo_relocate实现重定位,下图是源码中相关调用:

                                                                      

       下面是在linker中的对应的汇编代码( 对于如何找到对应汇编代码,只要在IDA中搜索字符串“[ relocating %s plt ]”就可以找到):

                                                                            

        直接将上面的对sub_1464函数改成对应的”c0 46 c0 46”(thumb nop指令) 就可以。我们知道如果SO本身又调用了其他的SO,此时会优先加载其他的SO,因此注意nop的时机,避免将不是UPX壳的其它SO重定位也执行不了。

 

2 如何恢复segment段的load的大小

        我们知道upx壳的loader会对加密的数据进行解密,解密后的数据大小,就是对应load段的大小。

下面是UPX壳部分数据排列顺序:

                                                    
        下面是对应SO文件init入口数据:
                                                     

       l_info p_info b_info结构大小都为12个字节,如下:

                                                           

        根据上面的loader地址我们知道第一个l_info结构的地址为0x6508。见下图:

                                                               


       我们先不关心l_info和p_info结构,我们只关心b_info结构。对于b_info结构,第一个成员是压缩前的大小,第二个成员是压缩后的大小。因此我们可以通过b_info获取到被压缩数据的压缩前的大小:

       从上图可知,第一个b_info结构对应的压缩块:

               ● 压缩的前大小为:0x154

               ● 压缩后大小为0xA2

      这个块是对应ehdr和phdr的,我们跳过这个块,到达第一个load段对应的b_info结构。其地址为 :0x652C+0xA2 = 0x65CE,见下图:

                                                              

       从上图中对应的b_info结构,我们知道第一个load段压缩前大小为:0XDDC58


3 内存dump的时机

        我们必须在UPX解密后,并执行原始SO的init前进行dump。因此我们就在linker执行完第一个init函数,也就是upx的loader函数后开始dump。因此断点的位置下图中的0x274A位置:

                                                                   

4 修复load段

                                                                  

       将上图中第一个load段的0x8B290改为我们之前获得0Xddc58。同时将第二个段的文件偏移和虚拟地址偏移改成一样,都为E5C60。

 

5 修复dynamic段

       对于UPX壳,原始的dymic段的文件位置被修改了,但是对应加载后的内存的位置并没有修改,因此我们直接将其在文件中的偏移改成与内存偏移一致就OK。如下图: 

                                                                   

      将0x8E888改成0xE6888。


四 总结

        1 将linker中的 soinfo_link_image函数中对soinfo_relocate调用 NOP(避免重定位造成内存dump后IDA分析问题)

        2 在upx init(loader)入口处找到第一个l_info地址,从而获取到第一个b_info结构地址;

        3 跳过第一个b_info对应的压缩数据块,到下一个压缩段的b_info;

        4 从b_info结构中获得压缩前的大小;

        5 执行完upx的解压后,从内存中dump UPX壳;

        6 将第一个load段的文件大小 虚拟地址大小,物理地址大小都改成压缩前的大小;

        7 将第二个段的文件大小,虚拟地址大小,物理地址大小都改成相同;

        8 将dysmic修正(将文件偏移改成与虚拟地址偏移一致);

        9 将sht清0(后者在文件结尾找个位置,同时将sht_size清0)。(也可以根据dynamic修复)。


原创粉丝点击