吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4563|回复: 25
收起左侧

[原创] 加壳原理笔记05:利用图片隐藏

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

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

目录:

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




前言

完成了简单的压缩壳之后放松下,在52论坛病毒分析区看到过几次把代码隐藏到图片里的做法,也看到过把程序转成图片后训练神经网络来判断有没有恶意的,于是就想,淦,这不是挺好玩的嘛。

0x01 思路

用图片保存程序最简单的做法就是直接把程序每个字节都转成像素,然后输出成灰度图。比较进阶的做法就像是二维码了,大色块,容错校验,图片被压到包浆也能扫出来。但那个有点点难(我菜)最终成果也大到不现实,而且实话说打包到程序里就不用考虑被二次压缩的情况了。所以简单的8bit灰度图就刑。

说到位图肯定有人想到了 BMP ,我记得上学那会儿还跟着网上哪儿找的教程,学着用 ffmpeg 把 Bad Apple 转成位图序列,再转成字符图合并成 HTML,用 js 播放。说起来都是泪。

现在已经成了正经的码农,再折腾 BMP 就没意思了,PNG 就挺好的。

图片可以放到 Section 里——但并没有意义,所以我选择放到资源里。写一个 .rc 文件用 windres 编译出目标文件,再拿 gcc 链接就行了。如此一来并没有 lief 出场的机会,编译好的加载器就是加完壳的程序。

加载器则采用开启 ASLR 的模式,这样程序的节表会比较干净,没有明显特征(虽然也没什么卵用)。

0x02 加载器

2.1 资源介绍

参考微软的文档 Using ResourcesMenu and Other Resources

A resource is binary data that you can add to the executable file of a Windows-based application. A resource can be either standard or defined. The data in a standard resource describes an icon, cursor, menu, dialog box, bitmap, enhanced metafile, font, accelerator table, message-table entry, string-table entry, or version information. An application-defined resource, also called a custom resource, contains any data required by a specific application.

资源就是一堆打包进可执行文件里的二进制数据,有标准资源类型和自定义的资源类型,标准的回头看就全是微软的历史包袱了,自定义的就是随便什么东西。

资源本身是有结构的,大体上分三层:

  1. 类型;比如图标、对话框、位图、Manifest等等。
  2. ID;资源的标识符,可以是数字或字符串。
  3. 语言;英语法语等等..

经过这样三层索引就能找到对应资源的原始数据了。

如图:

07_resource_tree.png

2.2 查找并加载资源

步骤很简单:

  1. FindResource 找到你要的资源
  2. SizeofResource 确定你要的资源大小
  3. LoadResource 加载资源,得到 HANDLE
  4. LockResource 锁定资源,得到资源首字节指针

实现比较啰嗦,主要是错误检查很啰嗦。我这返回值都是随便 return 的,更好的做法应该是 GetLastError 去拿错误码。

int get_resource(const char *name, void **buffer, size_t *length) {
  HRSRC res_found = FindResourceA(NULL, "BEAUTIFUL.PNG", RT_RCDATA);
  if (res_found == NULL) {
    MessageBoxA(NULL, "find resource failed", "FindResourceA", MB_OK);
    return 1;
  }

  DWORD sizeof_res = SizeofResource(NULL, res_found);
  if (sizeof_res == 0) {
    MessageBoxA(NULL, "sizeof resource failed", "SizeofResource", MB_OK);
    return 2;
  }

  HGLOBAL res_loaded = LoadResource(NULL, res_found);
  if (res_loaded == NULL) {
    MessageBoxA(NULL, "load resource failed", "LoadResource", MB_OK);
    return 3;
  }

  LPVOID res_acquired = LockResource(res_loaded);
  if (res_acquired == NULL) {
    MessageBoxA(NULL, "lock resource failed", "LockResource", MB_OK);
    return 3;
  }

  *buffer = malloc(sizeof_res);
  *length = sizeof_res;
  memcpy(*buffer, res_acquired, sizeof_res);
  UnlockResource(res_loaded);
  FreeResource(res_loaded);

  return 0;
}

得到数据后复制到新分配的内存里返回出去就完事了。

2.3 解析图片

得到了资源图片的内容之后,下一步就是把图片解码成像素,还原到程序本身了。

#include "png.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef uint8_t u8, *u8p;
typedef uint32_t u32, *u32p;

// decode PNG in memory
// https://stackoverflow.com/questions/53237065/using-libpng-1-2-to-write-rgb-image-buffer-to-png-buffer-in-memory-causing-segme
u8p read_program_from_png(u8p data, size_t length) {
  png_image image;
  memset(&image, 0, sizeof(image));
  image.version = PNG_IMAGE_VERSION;
  if (png_image_begin_read_from_memory(&image, data, length) == 0) {
    return NULL;
  }

  png_bytep buffer;
  image.format = PNG_FORMAT_GRAY;
  size_t input_data_length = PNG_IMAGE_SIZE(image);
  buffer = (png_bytep)malloc(input_data_length);
  memset(buffer, 0, input_data_length);

  if (png_image_finish_read(&image, NULL, buffer, 0, NULL) == 0) {
    return NULL;
  }

  u32 actual_len = *((u32 *)buffer);
  void *program = malloc(actual_len);
  memcpy(program, buffer + 4, actual_len);
  free(buffer);

  return (u8p)program;
}

面向 stackoverflow 编程,照着抄一个 libpng 的解码实现。不同的是把解码后的头4个字节作为小端序无符号整型,认为是程序的实际大小。因为程序的大小可能并不正好是图片的像素数量(width*height)。

最后是把解码后的内容复制到新分配的内存里返回。现在返回的指针应该就指向我们的 PE 文件内容了。

2.4 入口点

在入口点,调用加载资源函数获得资源数据的指针,传给解码的函数,得到解码后的PE文件指针,然后加载并跳转到被加载程序的入口点,就这么简单。

int _start(void) {
  void *buffer = NULL;
  size_t length = 0;
  if (get_resource("BEAUTIFUL.PNG", &buffer, &length) != 0) {
    return 1;
  }

  u8p program = read_program_from_png((u8p)buffer, length);
  free(buffer);

  if (program != NULL) {
    void (*entrypoint)(void) = (void (*)(void))load_PE((char *)program);
    entrypoint();

    free(program);
    return 0;
  }

  MessageBoxA(NULL, ".packed section not found", "loader error", MB_OK);
  return 0;
}

0x03 加壳机

3.1 程序转图片

使用 pypng 这个包实现把二进制程序转图片。

IMG_PATH = 'packer5-packed.png'
ROW_LEN = 256
with open('example.exe', 'rb') as f:
    arr = []
    content = f.read()
    content = struct.pack('<I', len(content))+content

    for i in range(len(content)//ROW_LEN):
        t = content[i*ROW_LEN:i*ROW_LEN+ROW_LEN]
        arr.append(t)

    png.from_array(arr, 'L').save(IMG_PATH)

非常简单的一段脚本。把内容长度和内容拼接后,以 ROW_LEN 每行,拆成一个二维数组,然后用 pypng 编码并保存。

3.2 编译资源

随便新建一个 rsrc.rc

别问 .rc 怎么写,不知道,问就是面向谷歌编程抄的。

beautiful.png RCDATA "packer5-packed.png"

然后在脚本里调用 windres 编译。

def windres(sources, output):
    executable = 'windres'
    args = ''
    if isinstance(sources, (str, bytes)):
        args += sources
    elif isinstance(sources, (list, tuple)):
        args += ' '.join(sources)

    cmd = f'{executable} {args} -o {output}'
    proc = run(cmd, shell=True, stderr=STDOUT)
    if proc.returncode != 0:
        raise CompilationError(proc.returncode, proc.stdout)

windres(path.join(src_dir, 'rsrc.rc'), path.join(src_dir, 'rsrc.o'))

就得到了 rsrc.o

3.3 编译加载器

# compile shifted loader program
def compile(sources, flags):
    args = ''
    compiler = 'gcc'

    args += ''
    if isinstance(sources, (str, bytes)):
        args += sources
    elif isinstance(sources, (list, tuple)):
        args += ' '.join(sources)

    args += ' '
    if isinstance(flags, (str, bytes)):
        args += flags
    elif isinstance(flags, (list, tuple)):
        args += ' '.join(flags)

    cmd = f'{compiler} {args}'
    proc = run(cmd, shell=True, stderr=STDOUT)
    if proc.returncode != 0:
        raise CompilationError(proc.returncode, proc.stdout)

cflags = [
    '-m32',
    '-O2',
    '-Wall',
    '-I.',
    '-Wl,--entry=__start',
    '-nodefaultlibs',
    '-nostartfiles',
    '-lkernel32',
    '-luser32',
    '-lmsvcrt',
    '-lpng',
    '-o',
    'packed.exe'
]
compile([path.join(src_dir, src) for src in ['loader.c', 'png_decode.c', 'rsrc.o']], cflags)
print('[+] compile loader with resource success.')

主要是加上 -lpng 链接参数,链接 libpng 。输入文件里加上 png_decode.c 这个里面实现了 read_program_from_png,还有编译好的资源 rsrc.o

0x04 成果展示

4.1 完整代码

github.com - packer05

4.2 成果

png-packer

image-20211021172402101

image-20211021172445654

image-20211021172504888

总结

这次实验主要是验证了从资源加载程序,本质和之前的其他加壳方式没有区别。把应用程序转换成图片后看到的效果确实比较有趣,我想如果用一张普通的图片或者其他文件类型,藏起来可能更隐蔽。

但到这里还是有明显的问题:壳和被加载的程序还是泾渭分明。

免费评分

参与人数 12吾爱币 +12 热心值 +10 收起 理由
debug_cat + 1 + 1 用心讨论,共获提升!
yufan1123 + 1 + 1 谢谢@Thanks!
努力加载中 + 1 + 1 用心讨论,共获提升!
hap + 1 + 1 我很赞同!
陈世界 + 1 + 1 我很赞同!
swhyy + 1 我很赞同!
唐小样儿 + 1 + 1 热心回复!
netle8 + 1 用心讨论,共获提升!
shc1221 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
woyucheng + 1 + 1 谢谢@Thanks!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
sam喵喵 + 1 + 1 谢谢@Thanks!这篇代码更清楚,必赞

查看全部评分

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

视觉℡ 发表于 2021-10-22 08:55
前排沙发,高级啊,真是高级,学习学习
头像被屏蔽
mokson 发表于 2021-10-22 09:01
l441669899 发表于 2021-10-22 09:24
DQQQQQ 发表于 2021-10-22 09:29
这个玩意儿要做好,要采用隐写,这样图片看起来不会违和。一堆乱七八糟的图片,容易引起警觉
LoveCpp 发表于 2021-10-22 10:29
感谢楼主分享!,涨经验。
godmessengers 发表于 2021-10-22 11:04
楼主威武
thinking5763 发表于 2021-10-22 11:46
点个赞,学习学习@!
cjjv123 发表于 2021-10-22 12:07
学到了 谢谢打个
dwcj 发表于 2021-10-22 12:30
感谢提供完整代码,把代码隐藏到图片是一个挺好的思路
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-12-23 00:47

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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