写一个PE的壳_Part 3:Section里实现PE装载器


系列汇总


Part 3:Setion里实现PE装载器

Part 3主要做了什么?

  • Part 1和Part 2中我们写了一个简易的PE装载器(loader.exe),可以加载一个32位程序进入我们申请的内存,然后找到被加载程序的EP,并开始运行被加载程序

  • Part 3中我们将简易的PE装载器放在一个section里实现,用python将一个32位程序加载进入section(并且执行32位程序),最终生成一个可执行的二进制文件

1.unpack部分

这部分可以当成是解压缩部分,脱壳部分都可以,主要是为了还原原程序并执行它

step 1:通用想法

思路:修改Part 1和Part 2的C语言代码,功能转移到一个名为”.packed“的区段中读取PE文件,需要做的事情:

  • 1.列举当前运行程序的section
  • 2.找到名为”.packed“的section,后续里面会加载一个PE文件内容(即要加载的文件,如calc.exe)
  • 3.加载PE文件进入内存,并执行它

实现:Part 1和Part 2中的loader.exe的功能被修改后,构建成unpacker.exe,里面有一个”.packed“的区段,用来实现loader.exe的功能

step 2:修改代码

只需要改变loader.exe的主函数;为了简化PE的结果(Mingw会产生大量的区段),需要使用编译选项避免链接C的运行时库和C标准库,此时main函数已经不需要了,将函数的入口改成__start函数

#include <windows.h>
#include <winnt.h>
// loads a PE in memory, returns the entry point address
void* load_PE (char* PE_data);	//这个函数不变

// 3个以my开头的自定义函数
// some basic functions intended to limits the external libraries needed
int mystrcmp(char* a, char* b);
void mymemcpy(char* dst, char* src, unsigned int size);
void mymemset(char* dst, char c, unsigned int size);

int _start(void) { //Entrypoint for the program
    // Get the current module VA (ie PE header addr)
    char* unpacker_VA = (char*) GetModuleHandleA(NULL);

    // get to the section header
    IMAGE_DOS_HEADER* p_DOS_HDR    = (IMAGE_DOS_HEADER*) unpacker_VA;
    IMAGE_NT_HEADERS* p_NT_HDR     = (IMAGE_NT_HEADERS*) (((char*) p_DOS_HDR) + p_DOS_HDR->e_lfanew);
    IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1);

    char* packed_PE = NULL;
    char packed_section_name[] = ".packed";

    // search for the ".packed" section
    for(int i=0; i<p_NT_HDR->FileHeader.NumberOfSections; ++i) {
        if (mystrcmp(sections[i].Name, packed_section_name)) {
            packed_PE = unpacker_VA + sections[i].VirtualAddress;
            break;
        }
    }

    // load the data located at the .packed section
    // packed_PE当成一个PE文件处理,后续会用python中的lief放入一个PE文件进入.packed区段
    if(packed_PE != NULL) {
        void (*packed_entry_point)(void) = (void(*)()) load_PE(packed_PE);
        packed_entry_point();
    }
}

上面代码的解释:

  • 程序流程:分析当前模块(unpacker.exe)的PE文件头,找到一个命令为“.packed”的区段,将这个区段当成一个PE文件加载并运行

  • 处理细节:因为不链接C的库,上面代码中移除了 stdio.hstdlib.h 头文件,但是我们还需要 strcmp, memcpymemset函数,因此我们用my开头的自定义函数实现相同的功能

step 3:编译选项

1.使用的完整编译命令

i686-w64-mingw32-gcc.exe unpack.c -o unpacker.exe "-Wl,--entry=__start" -nostartfiles -nostdlib -lkernel32

2.参数解释

  • nostartfiles:移除C运行时库(会调用带有典型 argcargv 参数的main函数);Windows中操作系统不会分析命令行,为了获取每一个参数,程序需要调用 GetCommandLineh函数且切片来获取结果;这是C运行时库为我们做的事情之一,现在我们不需要了
  • nostdlib:不链接C标准库 (libC, kernel32.dll, user32.dll, etc …);我们需要告诉连接器需要的特定库,因此使用-lkernel32选项
  • -Wl,--entry=__start:设置程序的入口点,此时已经不是main函数了,而是我们在程序里写的 _start 函数

3.编译结果

会得到一个名为unpacker.exe的文件,如果查看它的导入表,将只会看到kernel32.dll,里面含有5个函数

在这里插入图片描述

到目前位置,我们已经生成了一个简易的壳(unpacker.exe),但是里面是没有名为”.packed“的section的

因此,下面的任务是增加一个”.packed“的section,且将一个二进制文件(32位)添加到section中

2.用python打包

既然使用python,就要先准备好工具,如果你是第一次接触python,只需要记住一点,python2和python3是不兼容的,出现问题百度就可以了

  • 工具:编写python最好使用IDE工具,便于调试,我一直使用的是社区版的pycharm(免费)
  • 库:python的库非常丰富,建议将Anaconda也一起安装一下,网上有教程

step 1:lief安装和使用

我们将用一个名为lief的python库处理PE文件,它是一个查看和修改PE文件的常用库,比如添加导入表等

lief的源码是用C++写的,有兴趣的可以看看,不是很复杂,但是对理解PE、ELF等还是很有帮助的

lief官网:GitHub - lief-project/LIEF: LIEF - Library to Instrument Executable Formats

pycharm中安装lief:Terminal下直接输入  python -m pip install lief  进行安装

我们要做的事很简单:给我们前面编译的uppacked.exe添加一个名为“.packed”的区段,里面包含一个32位PE文件的拷贝(比如calc.exe)

step 2:避免告警

为了避免告警,需要下面2个python函数

def align(x, al):
    """ return <x> aligned to <al> """
    if x % al == 0:
        return x
    else:
        return x - (x % al) + al

def pad_data(data, al):
    """ return <data> padded with 0 to a size aligned with <al> """
    return data + ([0] * (align(len(data), al) - len(data)))

其中:第一个函数是按照int进行对齐来使用的,第二个函数是将真实长度与对齐长度之间的数据清零

step 3:命令行参数

由于没有链接C库,我们要自己手动分析命令行参数,使用下面的代码:

parser = argparse.ArgumentParser(description='Pack PE binary')
parser.add_argument('input', metavar="FILE", help='input file')
parser.add_argument('-p', metavar="UNPACKER", help='unpacker .exe', required=True)
parser.add_argument('-o', metavar="FILE", help='output', default="packed.exe")

args = parser.parse_args()

step 4:准备文件

现在我们想2个问题,给哪个文件添加区段?区段里的内容是什么?

答案:给上面写的空壳程序unpacker.exe添加区段,区段里的内容是测试程序calc.exe;因此,python编程中要先打开这2个PE文件

# open the unpack.exe binary
unpack_PE = lief.PE.parse(args.p)	#unpacker.exe

# we're going to keep the same alignment as the ones in unpack_PE,
# because this is the PE we are modifying
file_alignment = unpack_PE.optional_header.file_alignment
section_alignment = unpack_PE.optional_header.section_alignment

# read the whole file to be packed
with open(args.input, "rb") as f:	#比如calc.exe
    input_PE_data = f.read()

准备工作已经做完了,现在需要给unpacker.exe添加一个名为”.packed“的区段

step 5:给PE文件添加一个section

  • 先创造一个区段

区段名字是".packed",内容content是测试程序calc.exe

# lief expects a list, not a "bytes" object.
packed_data = list(input_PE_data) 
# pad with 0 to align with file alignment (removes a lief warning)
packed_data = pad_data(packed_data, file_alignment) 

packed_section = lief.PE.Section(".packed")
packed_section.content = packed_data		#添加内容,比如calc.exe
packed_section.size = len(packed_data)
packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ
                                | lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE
                                | lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA)
# We don't need to specify a Relative Virtual Address here, lief will just put it at the end, that doesn't matter.
unpack_PE.add_section(packed_section)		#unpacker.exe加了一个区段,里面内容是calc.exe
  • 保存修改(或者叫打包)成二进制文件

lief为我们做了大量的计算,比如会自动更新section个数等,因此只要将你感兴趣的区域设置成0即可,保存修改并命名为packed.exe

# remove the SizeOfImage, which should change, as we added a section. Lief will compute this for us.
unpack_PE.optional_header.sizeof_image = 0

# save the resulting PE
if(os.path.exists(args.o)):
    # little trick here : lief emits no warning when it cannot write because the output
    # file is already opened. Using this function ensure we fail in this case (avoid errors).
    os.remove(args.o)

builder = lief.PE.Builder(unpack_PE)	#构建unpacker.exe
builder.build()
builder.write(args.o)

3.结果展示

打包生成二进制文件,python命令:python.exe .\packer.py C:\Windows\SysWOW64\calc.exe -p .\unpacker.exe

注意:上面命令要根据自己的文件路径进行实时调整

如果使用pycharm打包,需要附带参数,pycharm设置参数路径方法:

#路径:run菜单 -> Edit Configurations中,Parameters
#内容:C:\Windows\SysWOW64\calc.exe -p C:\unpacker.exe -o C:\packed.exe

CFF查看打包前后的区段变化

在这里插入图片描述

相对于unpacker.exe,仅仅增减了一个.packed”的区段;直接执行packed.exe,会出现计算器

Part 3我们做了什么有用的?感觉什么也没做

  • 没有减少打包程序的大小:我们相当于在原程序(calc.exe)外面套了一个外壳(unpacker.exe),使整体变大了
  • 没有混淆代码,我们仅仅改变了导入表:packed.exe里只能看到kernel32.dll,隐藏了calc.exe的导入表,但是这对于脱壳来说没什么实际用处;要想恢复原始PE也很简单:只需提取.packed部分内容即可
  • 反调试没有支持:原始PE文件原封不动的包含在“.packed”里,没有任何反调试扩展支持

那Part 3有什么用?Part 3相当于一个框架,可以在里面添加一个很多特定需求

4.遗留问题

现在看起来一切还都是正常的,但是有一个隐患还没有解决;Mingw32无法生成重定位表,如果尝试打包一个用Mingw32编译的二进制文件,它不会正确运行

  • 1.正常的情况

现在看一下打包calc.exe,生成的packed.exeDLLCharacteristics特性

在这里插入图片描述

此时显示packed.exe不能移动,勾选上,保存,还是可以正常运行,似乎没有什么问题

  • 2.异常的情况

用Mingw32编译的二进制文件(test.exe),正常运行效果如下:

在这里插入图片描述

打包命令python.exe .\packer.py C:\OneDriver\test\test.exe -p .\unpacker.exe

运行效果:产生的二进制文件不能运行,即使强制勾选DLLCharacteristics使能DLL can move,但是由于没有重定位表,还是不能运行

详细原因和解决办法,可以看Part 4Part 5

5.参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值