吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4313|回复: 11
收起左侧

[原创] 加壳原理笔记04 - zlib压缩壳案例

[复制链接]
weakptr 发表于 2021-10-20 19:59
本帖最后由 weakptr 于 2021-11-4 10:34 编辑

这是刚进论坛没多久的萌新一边学一边写的,内容可能还有错误,还望大佬指出,也请多包涵!

目录:

加壳原理笔记01:PE头格式、加载、导入表、重定位
加壳原理笔记02:加壳案例
加壳原理笔记03 - 加载到固定基址
加壳原理笔记04 - zlib压缩壳案例
加壳原理笔记05:利用图片隐藏
加壳原理笔记06:反调试技术入门
加壳原理笔记07:花指令入门
加壳原理笔记08:代码混淆技术入门




前言

本文在前一篇基础上,写一个使用 zlib 的压缩壳案例。

0x01 zlib 解压

1.1 概述

关于 zlib 的用法找了这些参考资料:

尝试了 zlib、lzo、Windows Compression API,对压缩和解压 API 的基本模式的基本认识大概是这样:

  • 首先,你得有被压缩数据的大小(要么分块压缩,要么有整个压缩后的大小)
  • 然后得有解压后的预期大小,这个能通过 尝试解压 的操作来实现。比如 Windows Compression API 和 lzo 都可以在解压 buffer 传 NULL,尝试取得解压后的大小,再分配好内存解压。
  • zlib 这样的流式压缩、解压处理文件比较友好,但全程在内存里进行的话,流式解压就会导致大量内存分配 =。= 除非一开始就分配足够的空间,不然一个一个内存块申请和合并会很蛋疼。

1.2 内存布局

压缩后的 .packed 节在头部留出 8 个字节,分别保存压缩后大小和压缩前大小,以便一次分配好内存完成解压。

偏移 大小 内容
0 DWORD 小端序,压缩后大小
4 DWORD 小端序,压缩前大小
8 可变 压缩后的数据

1.3 解压代码

解压过程在加载 PE 之前,找到 .packed 节后,开始读取头部大小,并调用解压代码。

if (packed != NULL) {
    DWORD compressed_size = *((DWORD *)packed);         // compressed size little-endian
    DWORD decompressed_size = *((DWORD *)(packed + 4)); // decompressed size little-endian
    void *compressed = (void *)(packed + 8);            // compressed buffer
    void *decompressed = malloc(decompressed_size);     // decompressed buffer
    if (decompressed == NULL) {
        MessageBoxA(NULL, "memory allocate failed", "malloc", MB_OK);
        return 0;
    }

    decompress(compressed, compressed_size, decompressed, decompressed_size);

    void (*entrypoint)(void) = (void (*)(void))load_PE(decompressed);
    entrypoint();

    return 0;
}

应该没有太多疑问。接下来的是解压代码。

void decompress(void *compressed, size_t length, void *decompressed, size_t decompressed_length) {
  z_stream inflate_stream;
  inflate_stream.zalloc = Z_NULL;
  inflate_stream.zfree = Z_NULL;
  inflate_stream.opaque = Z_NULL;
  inflate_stream.avail_in = (uInt)length;
  inflate_stream.next_in = (Bytef *)compressed;
  inflate_stream.avail_out = (uInt)decompressed_length;
  inflate_stream.next_out = (Bytef *)decompressed;
  inflateInit(&inflate_stream);

  int err = inflate(&inflate_stream, Z_NO_FLUSH);
  if (err != Z_STREAM_END) {
    inflateEnd(&inflate_stream);
    MessageBoxA(NULL, "zlib decompression failed", "zlib", MB_OK);
    return;
  }
  inflateEnd(&inflate_stream);
  return;
}

定义 inflate 流:

  • avail_in 是可用的输入 buffer 大小
  • avail_out 是可用的输出 buffer 大小
  • next_in 是输入 buffer 的指针
  • next_out 是输出 buffer 的指针
  • zalloczfreeopaque 初始化成 NULL

使用 inflateInit() 初始化流,然后调用 inflate() 解压。inflate() 会返回错误码,如果长度正好,会返回 Z_STREAM_END。如果输出 buffer 长度不足,但解压成功,会返回 Z_OK。其他情况会返回错误码。因为这里很清楚给定的压缩前长度,解压必定返回 Z_STREAM_END,其他情况都有问题,所以只做了一个判断。

对于其他情况,错误码可以用 zError 获取错误描述。

解压结束后要使用 inflateEnd() 关闭流。

0x02 zlib压缩

因为使用 python 写加壳机,就不用这么麻烦了。

在处理 .packed 节的时候,使用 structzlib 两个 python 自带的库就能完成压缩和填充头。

在脚本头部添加两句 import

import struct
import zlib

然后修改加壳代码中,添加 .packed 节的代码。

# add packed section
with open('example.exe', 'rb') as f:
    file_content = f.read()
    origin_length = len(file_content)
    compressed = zlib.compress(file_content, 9)
    compressed_length = len(compressed)
    section_content = struct.pack('<II', compressed_length, origin_length)
    section_content += compressed

    packed_section = lief.PE.Section('.packed')
    packed_section.content = list(section_content)
    packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ |
                                      lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA)
    output.add_section(packed_section)

可以看到使用 zlib.compress 就完成了压缩,不用原始 zlib 流那么麻烦。

struct.pack 指定了小端序,两个4字节int,分别填写压缩后大小和原始大小,连接压缩后的数据,填充进.packed 节。

就这样,压缩功能成功完成。

0x03 成果展示

compression-packer

image-20211020154513713

image-20211020154539546

总结

偷懒了,用了一些 msvcrt 的函数,比如 malloc,要加个 -lmsvcrt 链接选项。最终成品压缩率还可以,从107KB 压缩到了 49KB,zlib 不负期望。

写好壳程序之后,不管是加密还是压缩都是很容易的事情(指单纯做个简单实现),但问题依然存在:

  • 64位程序——我觉得可以以后再说吧?我连64位汇编都还不会(泪)。
  • 脱壳跟玩一样——现在看 .packed 已经没有 MZ 这个摆明了是原始程序的标志了,但并没有卵用。壳程序也没混淆和反调试,节表也是清晰可见,根本不用分析。

下一篇还没想好做什么,得先继续学习充实下自己,找个方向。

免费评分

参与人数 8吾爱币 +7 热心值 +6 收起 理由
OYyunshen + 1 + 1 热心回复!
diliuyue + 1 谢谢@Thanks!
yan182 + 1 + 1 我很赞同!
macolma + 1 谢谢@Thanks!
culushishui + 1 + 1 我很赞同!
笙若 + 1 + 1 谢谢@Thanks!
sam喵喵 + 1 谢谢@Thanks!带源码的必须赞
Li1y + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

dingfen 发表于 2021-10-21 07:46
感谢楼主提供分享,学习了!
weiwude 发表于 2021-10-21 10:26
bestwars 发表于 2021-10-21 14:27
space218 发表于 2021-10-22 10:01
努力学习,谢谢分享!
cjc3528 发表于 2021-10-22 12:18
很好的教程,太强大了,向大佬学习,谢谢分享
咔c君 发表于 2021-10-22 20:41
学习了不错
hjtkxg 发表于 2021-10-22 21:43

系列讲座啊,支持。感谢楼主分享
头像被屏蔽
machenglin 发表于 2021-10-23 19:57
提示: 作者被禁止或删除 内容自动屏蔽
GuiXiaoQi 发表于 2021-10-24 06:38
谢谢大佬的分享,又学习到了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 23:58

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表