不同平台架构UPX手工寻找OEP小记

一、简介

UPX (the Ultimate Packer for eXecutables)是一款先进的可执行程序文件压缩器,压缩过的可执行文件体积缩小50%-70% ,这样减少了磁盘占用空间、网络上传下载的时间和其它分布以及存储费用。 通过 UPX 压缩过的程序和程序库完全没有功能损失和压缩之前一样可正常地运行,对于支持的大多数格式没有运行时间或内存的不利后果。 UPX 支持许多不同的可执行文件格式 包含 Windows 95/98/ME/NT/2000/XP/CE 程序和动态链接库、DOS 程序、 Linux 可执行文件和核心。

对于可执行程序资源压缩,是保护文件的常用手段. 俗称加壳,加壳过的程序可以直接运行,但是不能查看源代码.要经过脱壳才可以查看源代码。

其实是利用特殊的算法,对EXE、DLL文件里的资源进行压缩。类似WINZIP 的效果,只不过这个压缩之后的文件,可以独立运行,解压过程完全隐蔽,都在内存中完成。解压原理,是加壳工具在文件头里加了一段指令,告诉CPU,怎么才能解压自己。当加壳时,其实就是给可执行的文件加上个外衣。用户执行的只是这个外壳程序。当执行这个程序的时候这个壳就会把原来的程序在内存中解开,解开后,以后的就交给真正的程序。

EP(Entry Point),意即程序的入口点。而OEP是程序的原始入口点,一个正常的程序只有EP,只有入口点被修改的程序(加壳等),才会拥有OEP。

二、准备工作

运行环境:

1、Windows 7 x64
gcc(mingw)
OllyDBG
Ghidra

2、Ubuntu 20.04
sudo apt install -y upx gcc-arm-linux-gnueabihf gcc-mips-linux-gnu gdb gdb-multiarch qemu-user

我们准备一段简单的代码,下面是经典的helloworld代码:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv)
{
    printf("hello world\n");
    return 0;
}

编译不同架构的程序

序号平台架构参数
1Windows x86gcc helloworld.c -o hello.exe
2Linux x86gcc helloworld.c -o hello.x86 -m32 -static
3Linux arm32arm-linux-gnueabihf-gcc helloworld.c -o hello.arm32 -static
4Linux mips32mips-linux-gnu-gcc helloworld.c -o hello.mips32 -static

使用upx给编译好的程序进行加壳操作,命令:upx -1 文件名 -o 新文件名

# upx -h
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2018
UPX 3.95        Markus Oberhumer, Laszlo Molnar & John Reiser   Aug 26th 2018

Usage: upx [-123456789dlthVL] [-qvfk] [-o file] file..

Commands:
  -1     compress faster                   -9    compress better
  --best compress best (can be slow for big files)
  -d     decompress                        -l    list compressed file
  -t     test compressed file              -V    display version number
  -h     give this help                    -L    display software license

三、手工寻找OEP

1、Windows平台

通常手动脱壳,可以采用堆栈平衡法,使用OD调试,找到PUSHAD(把从EAX到EDI寄存器压入堆栈,保存现场)

使用单步法,先一步步向下查看,直到找到POPAD语句出现的地方,因为一般UPX壳都是加在原程序的上方。

找到一个大跳转,jmp

跳过去,找到入口点

最后与原始程序对比,的确是OEP,是_mainCRTStartup函数的开头。

2、Linux x86平台

首先我们用readelf查看入口点:0x8093718

与Windows的脱壳方式类似,也是找pushad、popad,不过Linux上显示的是pusha、popa,使用gdb调试,在入口点下断点

我们找到后面的popa,下个断点,如果有多个popa,可以下多个断点,可以配合静态反编译工具进行查找,最终断下来

来到了一个函数,在末尾有个jmp edx,可以确定它进行了一些操作

跟进,发现是一个函数

使用命令x/1024i $pc查看多行汇编代码,找到一个大跳转的地方,跳转地址edi是动态生成的,基本可以确定做了一些操作

下断点,进入,找到了最后的ini 80中断和popa

继续单步到ret

对比原始程序,这里就是实际程序的入口点

查看程序的map

dump出现在的内存,可以用于静态分析(无法运行的,如果想要运行,得修复区段,这里暂时不再分析)

(gdb) dump memory hello.x86.dump 0x8048000 0x80e7000

3、Linux arm平台

相比x86架构,arm架构并没有pushad、popad,只能进行简单的寄存器操作,可是不管怎样,都应该有一个特征:出现大跳转之后就接近原始的OEP了。

对于ARM架构来说,大跳转一般不会采用BL这样的函数跳转方式,要不就是使用pop {xx, pc},或者add pc, xx, xx、ldr pc, xx、mov pc, xx等方式,以下是Ghidra找到的可疑跳转(其实通过flags=0x7d、len=0x5这里可以判断出来是sys_protect系统调用的一个参数)

使用qemu-arm和gdb-multiarch进行调试,首先下断点0x4353c

使用命令x/1024i $pc查看多行汇编代码,找到能够访问的内存末尾,最终找到一个pop {xx, pc}

在0xff7eefe0下断点,并单步步过,查看内存找到一个ldr pc, xx的跳转

下断点并单步步过,最终来到主程序的地址范围

继续单步步过,最终来到一个地址0x10328,与原始程序对比可知是入口点

后面的脱壳与linux x86类似,这里不再赘述。

4、Linux mips平台

与arm平台类似,都是使用寄存器直接跳转的,那么找寄存器pc操作的地方就行,同时找到len=0x5的地方,应该就是调用地点,最终找到0x4468b0

使用qemu-mips和gdb-multiarch进行调试

单步步过

使用命令x/1024i $pc查看多行汇编代码,找到能够访问的内存末尾,最终找到一个跳转0x7f7fe2ec

下断点0x7f7fda9c,并单步步过,查看汇编,找到一个jr at跳转0x7f7fda9c

下断点0x7f7fda9c,并单步步过,最终回到主程序的内存区域

继续单步步过,发现原始程序的入口点

使用readelf查看原程序的入口点,与上述的地址一致

后面的脱壳与linux x86类似,这里不再赘述。

四、通用脱壳方式

upx -d 文件名 -o 新文件名

如果脱壳时报错,就查看upx的版本是否与壳的版本一致,更换到对应的版本,使用16进制工具查看程序,如下所示可以知道upx版本3.95。

五、参考链接

https://baike.baidu.com/item/upx/4630968

https://blog.csdn.net/qq_41426887/article/details/90552292

https://baike.baidu.com/item/OEP/4885720

https://github.com/upx/upx

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注