• 手工脱壳之 UPX 【随机基址】【模拟UPX部分算法】【手工C++重建重定位表】


    一、工具及壳介绍

     使用工具:Ollydbg,PEID,ImportREC,LoadPE,010Editor

    UPX壳 3.94:

       

    有了上篇ASPack壳的经验,先查看数据目录表:

    可知是upx壳通过PE加载器修复自我重定位信息。

       

    二、脱壳

    1、ESP定律

    入口:

       

       

    通过ESP定律,ESP下硬件断点。

    断下的第二次:

       

       

    跳过循环,单步几次。

    OEP:

       

       

       

    2、Dump内存

    查看基址

       

       

    OPE - 加载基址 = OEP RVA

    4BA9E1- 390000= 12A9E1

       

    双击运行。

       

       

    查看出错位置。

    加载基址 + 出错RVA = 出错地址

    900000 + 131d90 = A31D90

       

       

    可见,重定位信息并没有修复。

       

    三、寻找重定位表数据

    目标:找到原本重定位表数据。

       

    1、解压数据、解密重定位点、修复重定位信息 代码块

    预判:程序先解压后 再修复重定位。

    找到解压后的OEP入口处,还有附近的exe重定位点

    程序重新加载,现加载基址 + OEP_RVA(12A9ED) == 现OEP。

       

       

       

       

    断在了壳的解压代码块

    解密还未完成。

    但是找到了似加密过的重定位点,找此点0xC0411A00再下硬件断点。

       

       

    断下来的地方,应该是修复exe重定位信息 代码块了。

    似做了混淆。

       

       

    锁定疑似修改重定位点的代码块,

    再次在原重定位点0xC0411A00下硬件断点

       

       

       

    断下的地方进行分析,发现:

    原重定位点的数据的确被加密

       

       

    详细分析:

    EBX取出到EAX(0x88FC1A00地址),解密,得到RVA,加上text段地址,再放回去,完成修复exe重定位。

       

       

       

       

    2、回溯分析 寻重定位数据

    它是怎么锁定当前重定位点的呢,往上分析,追踪EBX,找出重定位表。

    从【EDI】中取出1字节的偏移值给AL,后续在将EBX和EAX相加。

       

       

    它的算法是:

    EBX为当前重定位点的地址,【EDI】 --> EAX为下一重定位点的偏移

    EBX+EAX方可定位到下一重定位点地址。

       

       

    查看EDI内存,内存储存着不同的偏移值。

       

       

    查看取出偏移值代码块:

    可以看出代码有两处机制,可以推出偏移值有1字节和2字节之分。

       

       

    锁定分析EDI地址数据来源,也就是偏移值来源

    最终分析,发现UPX其算法非常特别。

    对偏移值区域数据的操作:

       

       

    也就是说EDX在加4之前,是0x15690F3,查看地址,发现真的是数据覆盖。

       

       

    3、总结及实施方案

    总结upx的几点:

    1. 对原需要 修复重定位点 进行加密。
    2. 采用重定位点地址 加 偏移值 修复下一重定位点。
    3. 算出偏移值的算法 奇特。

       

    而且通过原demo的重定位表数据,搜索字节码,发现无论在脱壳前还是脱壳后,都搜寻不到.reloc的数据。

    可推断,upx在加壳时就已经将.reloc Section加密了,在壳中再通过其算法解析出偏移值,再通过偏移值修复重定位点

       

      4.加壳时已对重定位表数据加密。

       

    加壳 --> .reloc加密,重定位点加密 --> 壳运行 --> .reloc解密 --> 算出偏移值 -->

    对加密的重定位点解密 --> 采用偏移值修复重定位点信息。

       

    现在有两种方案:

    1. UPX是开源的,研究其源码算法,对偏移值数据逆向处理,得到原本重定位表。
    2. 分析算法代码块,抠出偏移值数据,模拟算法,将偏移值转换为重定位表数据。

    最后重建重定位表,为程序添加.reloc Section

       

    这里时间紧迫,选择方案2。

       

    四、模拟算法转换数据

    1、分析算法代码块:

    可见是从EDI所指向的地址,取出偏移值给EAX,再进行JA判断,最终加上EBX。

    编写C++代码 进行模拟算法计算,转换,并输出。

       

       

    2、取偏移值区块数据:

    数据首:

       

       

    数据末:

       

       

    抠出数据:

       

    3、写C++代码转换数据

       

    //模拟重定位表结构,转换重定位表数据。
    //参考,重定位表结构体:
    /*typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
    //  WORD    TypeOffset[1];
    } IMAGE_BASE_RELOCATION;    */
    
    typedef vector<WORD> _TypeOffset;
    
    //自实现重定位结构体
    typedef struct _MyRELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
    _TypeOffset TypeOffset;
    } MyRELOCATION,*PMyRELOCATION;
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        ///////////////////////////////////////////////////////////////////////////////////////
        //////前期工作
    
        //打开文件
        FILE* DataFile = NULL;
        DWORD error = fopen_s(&DataFile, "Section_Data", "rb");
    
        if (error != NULL)
        {
            printf("失败1");
            getchar();
            return 0;
        }
    
        //重建重定位表
        PMyRELOCATION relocData = new MyRELOCATION();
        //新建重定位表数据文件
        HANDLE HNewFile = CreateFile(L"reloc_Section", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (HNewFile == INVALID_HANDLE_VALUE)
        {
            printf("失败2");
            getchar();
            return 0;
        }
    
        //读取数据
        fseek(DataFile, 0, SEEK_END);
        DWORD FileSize = ftell(DataFile);
        fseek(DataFile, 0, SEEK_SET);
    
        //存放偏移值缓冲区
        BYTE* FileBuff = new BYTE[FileSize]();
        fread_s(FileBuff, FileSize, FileSize, 1, DataFile);
    
        ///////////////////////////////////////////////////////////////////////////////////////
        /////////模拟算法
    
        BYTE* BuffPos = FileBuff;        //Buff指针
        DWORD Curshifting = 0;        //当前偏移值 用于运算
        DWORD AddShifting = 0;        //本区段累计偏移值 用于判断跨区段
        DWORD Count = 1;            //跨区段个数  //由重复逆向分析可可知,TEXE段的RVA恒定0x1000
        DWORD RelocOfNumber = 0;    //重定位数量
    
        for (DWORD i = 0; BuffPos[i] != 0;)
        {
            Curshifting = BuffPos[i];
    
            if (Curshifting > 0xEF)        //取出两字节
            {
                Curshifting &= 0xF;
                Curshifting << 0x10;
                Curshifting = *(WORD*)&BuffPos[i + 1];
    
                if (i == 0)
                    Curshifting -= 4;            //首字节减去4字节
    
                i += 1 + 2;
            }
            else
            {                            //取出1字节
    
                if (i == 0)
                    Curshifting -= 4;            //首字节减去4字节
    
                ++i;
            }
    
            ///////////////////////////////////////////////////////////////////////////////////////
            //////////转换成重定位表数据
    
            //判断是否跨区段
            ////当本区段累计值 与 当前偏移值 之和 超过0x1000时,表示跨区段
            if (AddShifting + Curshifting > 0x1000)
            {
                //跨区段
                Count += 1;
                relocData->VirtualAddress = 0x1000 * Count;
                relocData->SizeOfBlock = ((relocData->TypeOffset.size()*sizeof(WORD)) + sizeof(_IMAGE_BASE_RELOCATION));
    
                //输出到重定位表数据文件
                DWORD Relity = 0;
                WriteFile(HNewFile, &(relocData->VirtualAddress), 4, (LPDWORD)&Relity, NULL);
                WriteFile(HNewFile, &(relocData->SizeOfBlock), 4, (LPDWORD)&Relity, NULL);
                RelocOfNumber += relocData->TypeOffset.size();
                for (DWORD j = 0; j < relocData->TypeOffset.size(); j++)
                {
                    WriteFile(HNewFile, &(relocData->TypeOffset[j]), 2, (LPDWORD)&Relity, NULL);
                }
    
                //清空结构体
    
    
                //计算新区段累加值
                AddShifting = (AddShifting + Curshifting) % 0x1000;
            }
    
            AddShifting += Curshifting;        //添加进本区段累计偏移值
    
            //记录每个重定位区域 重定位点 的偏移值,高4位重定位属性3,低12位偏移值。
            WORD y = (0x3000 | *(WORD*)&AddShifting);                    //位运算符是把数据加载,运算后,再小端存储
            relocData->TypeOffset.push_back((0x3000 | *(WORD*)&AddShifting));
        }
    
        //最后重定位表结构体数组以0结尾
        DWORD Relity2 = 0;
        WriteFile(HNewFile, &Relity2, 4, (LPDWORD)&Relity2, NULL);
    
        //打印重定位数量:
        printf("%X", RelocOfNumber);
    
        ///////////////////////////////////////////////////////////////////////////////////////
    
        fclose(DataFile);
        CloseHandle(HNewFile);
        delete[]relocData;
        delete FileBuff;
    
        getchar();
    }

    重定位数量:

       

    转换后数据:

       

     4、添加.reloc区段 

    先填充一个区段头表为零:

       

    最末尾粘贴上.reloc数据。

       

    .reloc数据一共0xE99CD8大小。

       

       

    LoadPE添加区段,填上相应数值。 

       

    成功!

       

     个人总结:

    1. 四部分的难度是逐步上升的,保持好心态和耐性。

    2. 实现随机基址,主要是想尽量还原程序的运行环境,觉得改掉随机标志没什么意思,但是,费时...

    3. 静态编译的MFC,有点大...

    附件:

     UPX_MFC_Reloc

    KIDofot

  • 相关阅读:
    10 大排序算法总结
    什么是堆和栈
    8张思维导图学习javascript
    Service Locator 模式
    Unity系列文章
    IoC模式(依赖、依赖倒置、依赖注入、控制反转)
    ASP.NET应用程序与页面生命周期
    TCP/IP网络协议的通俗理解,SOCKET,HTTP,SOAP
    部分计算机上视频不能自动刷新的解决方案
    数梦工场Java实习面试(offer到手含面试经验及答案)
  • 原文地址:https://www.cnblogs.com/KIDofot/p/8611891.html
Copyright © 2020-2023  润新知