文章目录
  1. 1. 0x00 口胡
  2. 2. GO!
  3. 3. 0x01 了解熟悉UPX加壳流程
    1. 3.1. 参数check
    2. 3.2. do_files(i,argc,argv)
    3. 3.3. do_one_file(iname,oname)
    4. 3.4. doPack(fo)

0x00 口胡

这次不知道怎么说,心血来潮的成分更多一点吧。自己也有过很多次这种心血来潮,希望这次能够实践下去。。。

记得刚入二进制坑的时候,就接触了脱壳,自己的脱壳技巧也是停留在那时的水平,也只是会一些压缩壳,而且也只是傻逼式的用工具,根本不懂原理,以及为什么这么做。。。

这次准备以UPX为引导,准备实现以下目标:
1. 了解熟悉UPX加壳流程
2. 逆向分析UPX脱壳桩代码
3. 分析从OEP处dump的话具体是dump了个什么东西
4. 分析dump后重建IAT的原理
5. 根据加壳和脱壳流程,写出自己的脱壳脚本(python或IDApython)
6. 有时间的话,去看看各种压缩算法,估计没时间

GO!

0x01 了解熟悉UPX加壳流程

近阶段时间有限,要干和想干的事情太多,加上近机场ctf我都没开题,所以在前段日子看了近三周源码后决定先搁置一段时间,好好修炼一下pwn,真心被打的有点小郁闷。。。

网上资料真心少啊,本来以为前人有做这方面工作的,结果,并没找到,没办法,自己撸源码。。。

UPX作为一个经典的压缩壳,自己对待它的源码还是很细致的剖析了一下的,所以耗费了大把时间,也想学习下一个小的工程程序它的大体架构是怎样的,毕竟不能只做脚本小子:p。但只看了加壳流程的大半部分,不过,应该可以扯很长时间了。。。

没捷径,一步步来吧,从main开始,由于没看完,所以别妄想我能够从总体上把握所谓的加壳的整体架构和流程之类的,毕竟不是战略家,科科。。。

开扯

参数check

  1. _set_abort_behavior:首先设定程序崩溃时所采取的动作,详见msdn,用户友好,并没有什么影响
  2. acc_wildargv:并不知道干嘛的
  3. upx_sanity_check():里面是一系列的断言,进行初步检测
  4. opt->reset():opt是声明的一个全局变量,是从options.h中导出的(extern struct options_t *opt;),这是个蛮复杂的结构,从他的具体结构中(注释和变量命名),我们可以看到UPX所支持的一系列操作,该语句是对该结构的一些变量进行初始化赋值,设定一些默认值。
  5. 紧接着是对后缀进行处理。我们知道windows一般都是带.exe这种后缀的,下面的#if,#else预处理就是把这个后缀去掉
  6. set_term(stderr);设置输出终端为标准错误流
  7. 下面的预处理是判断两种压缩算法是否可用,不可用输出错误信息,然后直接退出
  8. 设置随机种子,下面好像没有用到
  9. 参数解析,只对几个很简单的参数(help这种)进行操作,如果存在,输出相关信息
  10. 一个并不知道干嘛的预处理
  11. 重置终端为标准输入(默认)
  12. 参数判断,并作相应处理
  13. 只有一个参数,显示提示信息后退出
  14. 重置输出为错误输出
  15. 还是参数检查-_-
  16. 紧接着的语句说明upx支持批量加壳

参数到这就算检查完了,参数通过检查,则把参数信息记录下来后开始工作,不出意料应该是记录到opt全局变量中去。然后就是重置输出为标准输出,然后进入新世界,do_files


do_files(i,argc,argv)

  1. 对版本的检查,版本大于1显示信息。
  2. 初始化静态变量infoheader
  3. 从批量的待加壳文件中取出一个,进行单个加壳操作->do_one_file,后面跟一大堆捕获异常的块,就不看了。

do_one_file(iname,oname)

  1. 检查输入文件的合法性和权限,当输入只读且要在源文件基础上输出会报错
  2. InputFile fi;fi.st = st;fi.sopen(iname, O_RDONLY | O_BINARY, SH_DENYWR);/二进制和只读方式打开文件/
  3. // open output file/创建一个临时输出文件,根据抛出的异常提示信息,可以看出打开输出文件时面临哪些问题/OutputFile fo;
1
2
3
PackMaster pm(&fi, opt);/*根据opt和fi创建加壳管理对象,这个对象很重要*/
if (opt->cmd == CMD_COMPRESS)
pm.pack(&fo);/*加壳,参数为fo,结果输出到fo中,着重分析这个函数,加壳算法都在这里面,*/

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
void PackMaster::pack(OutputFile *fo)
{
p = getPacker(fi);?*获取加壳文件的格式,支持很多种格式*/
fi = NULL;/*前面都是一些check*/
p->doPack(fo);/*得到具体的加壳对象后,下面开始加壳*/
}

Packer *PackMaster::getPacker(InputFile *f)/*根据文件的类型得到与之相对应的文件加壳对象*/
{
Packer *pp = visitAllPackers(try_pack, f, opt, f);/*try_pack是一个函数指针,visitAllPackers实际上是对try_pack的一个封装*/
if (!pp)/*用try_pack去不停的尝试,直至匹配到相应的文件类型,然后返回相应的加壳对象*/
throwUnknownExecutableFormat();
pp->assertPacker();//这是返回具体的Packer后,检查具体的Packer的有效性,与前面的检查相同
return pp;
}

Packer* PackMaster::visitAllPackers(visit_func_t func, InputFile *f, const options_t *o, void *user)
{
仅截取部分相关代码
if ((p = func(new PackW32Pe(f), user)) != NULL)/*我们以最能说明情况的32exe为例*/
return p; /*与PackExe对象相差dos_exe.force_stub这个东西,这个应该是区分DOS和x32的标志*/
delete p; p = NULL; /*对于pe32文件,返回的就是这个对象,该对象在构造的过程中会进行相应的检查*/
} /*看下这个对象的继承关系,PackW32PE继承自PeFile,PeFile继承自Packer*/
if ((p = func(new PackArmPe(f), user)) != NULL)/*考虑继承时构造函数的执行顺序问题(一般先执行父类的构造函数)*/
return p;/*所以在构造中进行的检查为 */
delete p; p = NULL;/*计算文件的大小和创建压缩信息显示对象(UI的)UiPacker(Packer);PeFile检查PE的相关数据结构大小和字节对齐(主要是为了兼容平台;而构造PackW32Pe时只记录了简单的一些变量信息)*/*/
if ((p = func(new PackExe(f), user)) != NULL)
return p;

fi的使命已完成,置空并进入doPack(fo)进行加壳

1
2
fi = NULL;/*前面都是一些check*/
p->doPack(fo);/*得到具体的加壳对象后,下面开始加壳*/

doPack(fo)

1
2
3
4
5
6
void Packer::doPack(OutputFile *fo)
{
uip->uiPackStart(fo);/*又是UI,更新压缩文件数,可以看出,支持多文件加壳*/
pack(fo);/*虚函数,需要到相应的具体实现中去看,不同的文件格式有不同的实现这里以pe32为例*/
uip->uiPackEnd(fo);/*加壳完成后,输出相应的信息*/
}

doPack()的具体实现就不贴了,到这开始就开始与文件结构有紧密联系了,总的思路就是不同的文件格式有不同的压缩和加壳策略,对应着doPack()的不同实现。

对于文件格式的信息,给出以前学PE的图片,结合图片来看。

ih指IMAGE_FILE_HEADER

文章目录
  1. 1. 0x00 口胡
  2. 2. GO!
  3. 3. 0x01 了解熟悉UPX加壳流程
    1. 3.1. 参数check
    2. 3.2. do_files(i,argc,argv)
    3. 3.3. do_one_file(iname,oname)
    4. 3.4. doPack(fo)