系列汇总

  • 写一个PE的壳_Part 1:加载PE文件到内存
  • 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc)
  • 写一个PE的壳_Part 3:Section里实现PE装载器
  • 写一个PE的壳_Part 4:修复对ASLR支持+lief构建新PE
  • 写一个PE的壳_Part 5:PE格式修复+lief源码修改
  • 写一个PE的壳_Part 6:简单的混淆

文章目录

  • Part 3:Setion里实现PE装载器
  • 1.unpack部分
    • step 1:通用想法
    • step 2:修改代码
    • step 3:编译选项
  • 2.用python打包
    • step 1:lief安装和使用
    • step 2:避免告警
    • step 3:命令行参数
    • step 4:准备文件
    • step 5:给PE文件添加一个section
  • 3.结果展示
  • 4.遗留问题
  • 5.参考

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 headerIMAGE_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" sectionfor(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 xelse:return x - (x % al) + aldef 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.exeinput_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 4和Part 5

5.参考

  • 1.Writing a PE packer – Part 3 : packing with python

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

  1. 写一个PE的壳_Part 5:PE格式修复+lief源码修改

    系列汇总 写一个PE的壳_Part 1:加载PE文件到内存 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc) 写一个PE的壳_Part 3:Section里实 ...

  2. 写一个PE的壳_Part 4:修复对ASLR支持+lief构建新PE

    系列汇总 写一个PE的壳_Part 1:加载PE文件到内存 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc) 写一个PE的壳_Part 3:Section里实 ...

  3. 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc)

    系列汇总 写一个PE的壳_Part 1:加载PE文件到内存 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc) 写一个PE的壳_Part 3:Section里实 ...

  4. 一个用Shell脚本写的猜丁壳游戏

    为什么80%的码农都做不了架构师?>>> 这几天在学习Linux的Shell脚本,写了一个猜丁壳游戏. 游戏规则:每回合玩家和电脑各出石头.剪子.布中的一个,分别以0.1.2表示,规 ...

  5. 案例一: 使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程。 首先用文本编辑器写一个C++源程序名为StackFrame.cpp ,代码如下:

    案例一: 使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程. 首先用文本编辑器写一个C++源程序名为StackFrame.cpp ,代码如下: 1 ...

  6. 如何写一个Android inline hook框架

    Android_Inline_Hook https://github.com/GToad/Android_Inline_Hook_ARM64 有32和64的实现,但是是分离的,要用的话还要自己把两份代 ...

  7. 写一个 iOS 复杂表单的正确姿势

    前言 这几天项目的新需求中有个复杂的表单界面,在做的过程中发现要比想象中复杂很多,有好多问题需要处理.有很多东西值得写下来好好梳理下. 需求分析: 6创建网店1.png 上图便是UI根据需求给的高保真 ...

  8. python提取数据库nosql_用 Python 写一个 NoSQL 数据库

    本文译自 What is a NoSQL Database? Learn By Writing One In Python. 完整的示例代码已经放到了 GitHub 上, 请 点击这里, 这仅是一个极 ...

  9. python编写数据库连接工具_详解使用Python写一个向数据库填充数据的小工具(推荐)...

    一. 背景 公司又要做一个新项目,是一个合作型项目,我们公司出web展示服务,合作伙伴线下提供展示数据. 而且本次项目是数据统计展示为主要功能,并没有研发对应的数据接入接口,所有展示数据源均来自数据库 ...

最新文章

  1. 【c语言】蓝桥杯算法训练 P0505
  2. WM_NCPAINT消息
  3. 从单片机工程师的角度看嵌入式Linux
  4. bat 修改txt_善用bat命令提高办公效率
  5. 云计算市场多元并立,唯有合作才能共赢
  6. java+ssh+mysql酒店网站管理系统源码
  7. PHP学习总结(3)——PHP入门篇之PHP的echo语句
  8. 寻找三角形(编程题)
  9. “间谍”软件克星Ad-Aware
  10. 190311每日一句
  11. 跳妹儿学编程之ScratchJr(五):ScratchJr入门程序积木块功能介绍与使用技巧
  12. 手机--修改dns服务器地址,DNS怎么修改 电脑和手机设置DNS全攻略
  13. Package com.myapp signatures do not match the previously installed version
  14. java创建Shape类,求子类circle,圆形rectangle矩形,rhombus菱形的周长和面积
  15. 鼠标滑过图片,图片抖动
  16. matlab中图像压缩
  17. display:flex 常用
  18. FCKEditor 使用
  19. 常见的计算机网络教学模式有哪几种,常见的教学方法有哪几种
  20. 计算机授课教案模板,讲课教案模板.doc

热门文章

  1. 这 6 个开源项目很 Cool
  2. js中的JSON对象转换,过滤特殊字符数据
  3. 在LabVIEW中如何使用Flash动画
  4. 招投标知识分享:影响投标报价编制的8大重要因素
  5. MySQL索引及视图
  6. Selenium+iframe准确定位元素
  7. Cocos2d-x下Lua调用自定义C++类和函数的最佳实践
  8. 某音热门---图片转字符SpringBoot版
  9. 沉痛悼念互联网[云原生领域]技术大牛----左耳朵耗子(陈皓老师)
  10. 如何升级自己的思维?成为你想成为的自己。