Oops-re's Blog.

UPX逆向分析

字数统计: 2.7k阅读时长: 10 min
2023/04/23

UPX逆向分析

UPX原理

UPX是一种流行的开源可执行文件压缩工具,它可以减小可执行文件的体积,并且可以提高程序的启动速度。UPX实现可执行文件的压缩是通过加壳技术实现的。

UPX的加壳原理大致如下:

1. UPX首先读取可执行文件的头部信息,包括文件格式、程序头、节表等信息。

2. 然后,UPX在可执行文件中添加一个新的“壳”程序,通常是一个简单的解压缩程序。这个壳程序会被添加到文件的头部或尾部,具体位置取决于可执行文件格式和UPX的配置。

3. 接下来,UPX将原始可执行文件的内容压缩,并将压缩后的数据添加到壳程序的后面。

4. 最后,UPX修改可执行文件的程序头和节表等其他信息,使其指向壳程序的入口点和压缩数据的位置。

当用户运行经过UPX加壳的可执行文件时,
会先执行壳程序,壳程序会将压缩的数据解压缩并加载到内存中,
然后跳转到原始可执行文件的入口点开始执行。
由于压缩数据的存在,
可执行文件的体积得到了缩小,从而减少了磁盘空间的使用和网络传输的时间,
同时也提高了程序的启动速度。

总之,UPX的加壳原理是在原始可执行文件中添加一个解压缩壳程序,并将原始程序的内容压缩后添加到壳程序后面,从而实现可执行文件的压缩和解压缩。

版本:upx-3.96-win64 可以去我库里找找 附带IDA分析 UPX
虽然 有源码 但是想练一练逆向 所以就有了这篇文章

PE分析

居然自己还套一个壳,改个名 脱了
PEwithUPX
upx -d a.exe
peNoUPX

动态静态分析

带参数的动调 file->改变命令行 输入参数
IDA跑 看到入口main函数 FCG还是挺长的 工作量估计有点大
UPX_FCG

通过字符串查找packer函数

开始运行后,首先会打印“Ultimate Packer for eXecutables“,然后就开始根据你的命令参数执行函数
字符列表找到字符串,交叉引用 有三个函数进行了调用
入口点
一个一个看一下

第一个sub_417930
    没有参数 输出 使用手册
第二个sub_417DC0
    输出一些upx的项目说明
第三个sub_469900
    输出upx 文件的状态

这三个函数都是通过sub_41DE80

这里主要是去分析他的upx 加壳和脱壳(解压缩) 所以着重分析sub_469900

sub_469900

一个个函数跟进简单分析结果如下
sub_469900
分为三个部分:

UPX程序部分
    checkupx函数
打印部分
    sub_417870函数 打印 Ultimate Packer for eXecutables ...
    sub_4669E0函数 打印 File size         Ratio      Format      Name ...
检验参数部分
    判断是要脱壳还是加壳 还是其他操作
        packer--sub_469340(v8, Destination); 里面包括加壳,脱壳以及多参数运行
    根据参数输出结果
        switch...case
            2 输出Unpacked
            3 输出Tested
            4 输出分割符
            5 ret


先分析 加壳–sub_469340(v8, Destination);

sub_469340

进入函数以后 先检验 命令行的第二个参数指向的文件
通过stat库函数获取文件属性

是否是一个非目录文件 (v16.st_mode & 0xF000) == 0x4000
是否是一个有效文件  (v16.st_mode & 0xF000) != 0x8000 
文件是否为空 v16.st_size <= 0
文件是否过小 size<511
文件是否过大 size>0x30000000
文件是否可写(有没有写保护)

check_file
后面就是创建类了 里面的成员 动态得到大概
class
之后就又来到了参数判断

v22是临时文件类 创建了一个临时文件 xxx.upx
Destinationa 是一个嵌套类 里面有 参数类和文件类(xxx.exe)

  v10 = *off_4D6100;
  if ( *off_4D6100 == 1 )                       // 这里保存参数个数 argc-1
  {
    sub_45D960(Destinationa, v22);              // 加壳 只有一个参数 所以就是进行加壳操作
  }
  else
  {
    switch ( v10 )
    {
      case 2:
        sub_45D9A0(Destinationa, v22); //这里进去看了一下 又和脱壳相关
        break;
        //后面都是多参运行 不是很关心 主要还是看加壳 脱壳
      case 3:
        sub_45D9E0(Destinationa);
        break;
      case 4:
        sub_45DA10(Destinationa);
        break;
      case 5:
        sub_45DA40(Destinationa);
        break;
      default:
        sub_4D5118("invalid command");
    }
  }

终于找到了dopack函数了 – sub_45D960

dopack – sub_457BA0函数

1
2
3
4
5
6
7
8
9
__int64 __fastcall sub_45D960(_QWORD *a1, __int64 a2)
{
__int64 v4; // rax

v4 = sub_45D8C0(*a1);
*a1 = 0i64;
a1[1] = v4;
return sub_457BA0(v4, a2);
}

里面又调用了两个函数
第一个函数 最后调用了sub_45C2D0 里面有很多的if…else 分类 pack
第二个函数则是对返回的类操作了一下调用了里面的方法 感觉应该是 用作检查 更新的

这是我粗浅的判断 实际上 sub_45C2D0 这个函数使用来获取加壳文件的类型
而sub_457BA0才是dopack函数

直接来到
sub_457BA0 OD也跟进来

1
2
3
4
5
6
__int64 __fastcall sub_457BA0(_QWORD *a1, __int64 a2)
{
sub_465880(a1[36]); // uiPackStart
(*(*a1 + 104i64))(a1, a2); // Pack
return sub_466C50(a1[36], a2); // uiPackEnd
}

从IDA可以看出Pack函数是 间接调用是虚函数 从OD来在IDA里找到这个虚函数
virtual_func
这里分成4个部分 对照源码分析中的Pack1、pack2、packe3、pack4
这里通过他的字符串做简单的识别

pack1

生成文件头DOS头+upx节表
upx_hande

pack2

压缩数据并添加到file中

pack3

添加loader函数 在UPX运行时解压缩程序
loader

pack4

添加壳的头部

这里添加的内容如下(这是Linux系统的UPX源码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void PackUnix::writePackHeader(OutputFile *fo)
{
unsigned char buf[32];
memset(buf, 0, sizeof(buf));

const int hsize = ph.getPackHeaderSize();
assert((unsigned)hsize <= sizeof(buf));

// note: magic constants are always le32
set_le32(buf+0, UPX_MAGIC_LE32);//UPX!--upx标签
set_le32(buf+4, UPX_MAGIC2_LE32);//

checkPatch(nullptr, 0, 0, 0); // reset
patchPackHeader(buf, hsize);
checkPatch(nullptr, 0, 0, 0); // reset

fo->write(buf, hsize);
}

void PackUnix::pack4(OutputFile *fo, Filter &)
{
writePackHeader(fo);
//填写UPX的版本和upx标签

unsigned tmp;
set_te32(&tmp, overlay_offset);
fo->write(&tmp, sizeof(tmp));//写入overlay_offset
}

upx_hande

这里主要是和脱壳相关,所以这里删除啥的没有影响,但是这是UPX的一个特征码 这里就是脱壳机关心的东西,通过删除这些东西,可以做到初步免查壳,exeinfo_PE就看不到UPX的版本信息了,但是仍能识别UPX


在运行的时候会生成两个临时文件 分别是xxx.upx和xxx.000
分别是 DOS头和DOS块 – xxx.upx
以及 添加了压缩数据 – xxx.000 (没有导入表)

总结一下UPX加壳流程

pack()函数为加壳函数,
分别调用pack1()进行头部的处理,对头部的section信息进行擦出,
pack2()对数据段进行压缩,
pack3()修改LOAD段,
pack4()则添加最后的壳的头部,并修改壳的section段。

绕过UPX查壳

一般市面上的查壳软件都是通过特征码进行识别的,虽然效率很高,但是 只要通过去除混淆特征码,那么就可以实现绕过查杀的目的。

特征码

就像PE文件都有MZ头一样,(MZ就算是PE文件的特征码)UPX也有自己的特征码

包括

  • UPX节表名

image-20230424161631580

1
2
3
4
5
6
7
通过学习PE文件结构我们可以知道,
节表名在PE加载在内存中没有什么作用(起作用的主要还是VirtualAddress)
所以我们修改这里的节表名 对PE文件的运行没有影响
但 对于 UPX -d 这个自己的UPX解壳程序来说 他是通过检查PE的节表名来判断该文件是否有UPX壳
所以这里修改一下 就能让UPX自己无法完成脱壳
但是Exeinfo_PE还是可以查出来是UPX壳 甚至还有版本信息
这就是关于下面的几个特征码了
  • UPX头

    image-20230424161709231

    1
    这里是UPX头部,是通过Pack4()函数添加的,相关信息,各个字段所代表的属性 在上面已经列出
    1
    2
    3
    ExeinfoPE就是通过这读取到的UPX版本信息,记录的都是和UPX和文件的基本信息,包括文件大小,压缩等级...
    替换这些信息也不会对PE文件的运行造成影响
    但ExeinfoPE获取到的UPX版本信息就会有错误,比如将版本字段改为6666

    image-20230424170311338

    但是仍然还有UPX壳的信息

    来看下一个特征码

  • psuhad … 机器码(60 BE …)

    image-20230424164356186

    这里的特征码是pushad的特征码,用作upx的loader

    exeinfoPE就是通过查特征码表查的

    image-20230424183308500

    改了这里以后 exeinfoPE还有是有疑似UPX的提示

    pushad

    还有什么捏 不到了

    点一下Scan/t 提示 “3个节的结构 似乎是UPX”(口翻一下)

    这里试一下再添加一个节

    添加一个空节

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <1>判断节表区域是否有空闲区域添加一个节表.APEX
    <2>在节表中新增一个成员
    复制其中一个 比较省事 这里复制了upx1节
    <3>修改PE头部中节的数量
    标准PE头 3-》4
    <4>修改SizeOfImage的大小
    可选PE头 原有+内存偏移
    <5>在原有的数据最后,新增一个节的数据(内存对齐的整数倍)
    直接00
    <6>修正新增节表的属性
    //可以便增加边修改
    需要修改的节表属性
    VirtualAddress(在内存中的偏移) = 上一个节表的VirtualAddress+上一个节表的(Misc在内存中的大小(内存对齐整数倍))
    PointerToRawData(在文件中的偏移)

    image-20230425112446297

    可以看到显示了4个节,他就不知道是啥保护了,但是还有提示了有壳 试一下把入口改了

    在新增节的位置添加跳转代码 并修改OEP–》0x2f000

    image-20230425115522791

    虽然还有是提示有壳保护 但是 点击Scan/t他分析不出来是UPX了

    image-20230425114457329

    后面还有能咋弄捏?不到了QAQ

这里由于妹有改什么特别的东西直接ESP定律就能手脱捏

虽然PE文件还是有点乱(毕竟多了一个节) 但是 IDA雀氏能分析了

image-20230425123130810

手动加壳

再套一层 把upx的loader代码加密了 这应该就看不出了叭

还是新增一个节 用来保存解密upx的代码 同时修改OEP

这里和上面的一样

加密oep代码

010Editor进行异或0x10叭

在apex节添加解密代码

1
2
3
4
5
6
7
8
9
10
11
0042F000 | 60                       | pushad                                |保存一下现场
0042F001 | 90 | nop |
0042F002 | BF B0D34200 | mov edi,hello3.42D3B0 | upx的解压缩
0042F007 | B9 90010000 | mov ecx,190 | 异或长度
0042F00C | 80340F 10 | xor byte ptr ds:[edi+ecx],10 | 解密
0042F010 | 83F9 00 | cmp ecx,0 |
0042F013 | 74 03 | je hello3.42F018 |
0042F015 | 49 | dec ecx | ecx--
0042F016 | EB F4 | jmp hello3.42F00C |
0042F018 | 61 | popad | 解压完成
0042F019 | E9 92E3FFFF | jmp hello3.42D3B0 | 跳回upx解压缩

程序能跑再看一下 同样识别不出来UPX了

image-20230425154037149

所以这也是一种绕过方法

CATALOG
  1. 1. UPX逆向分析
    1. 1.1. PE分析
    2. 1.2. 动态静态分析
      1. 1.2.1. 通过字符串查找packer函数
      2. 1.2.2. sub_469900
        1. 1.2.2.1. sub_469340
      3. 1.2.3. dopack – sub_457BA0函数
        1. 1.2.3.1. pack1
        2. 1.2.3.2. pack2
        3. 1.2.3.3. pack3
        4. 1.2.3.4. pack4
    3. 1.3. 绕过UPX查壳
      1. 1.3.1. 特征码
      2. 1.3.2. 手动加壳