没想到binwalk也会出漏洞,这漏洞用来投毒钓鱼,真的绝啊!
漏洞成因
根据描述问题是出在PFS文件系统提取器当中,通过目录穿越写入恶意的插件。
最关键的点是outfile_path
获取写入路径的时候没有将路径限制为当前的路径,导致存在目录遍历的可能,接着下面就是将PFS文件的数据写入,导致任意文件写的漏洞。
def extractor(self, fname):
fname = os.path.abspath(fname)
out_dir = binwalk.core.common.unique_file_name(os.path.join(os.path.dirname(fname), "pfs-root"))
try:
with PFS(fname) as fs:
# The end of PFS meta data is the start of the actual data
data = binwalk.core.common.BlockFile(fname, 'rb')
data.seek(fs.get_end_of_meta_data())
for entry in fs.entries():
outfile_path = os.path.join(out_dir, entry.fname)
if not outfile_path.startswith(out_dir):
binwalk.core.common.warning("Unpfs extractor detected directory traversal attempt for file: '%s'. Refusing to extract." % outfile_path)
else:
self._create_dir_from_fname(outfile_path)
outfile = binwalk.core.common.BlockFile(outfile_path, 'wb')
outfile.write(data.read(entry.fsize))
outfile.close()
data.close()
except KeyboardInterrupt as e:
raise e
except Exception as e:
return False
所以需要制作一个PFS文件,写入的路径为Binwalk插件的路径,内容就是初始化插件,成功写入之后当Binwalk调用这个恶意插件的时候就可以实现任意代码执行。
PFS文件解析
PFS文件需要去逆unpfs.py这个文件是如何解析PFS文件的,还有少量公开的PFS格式解析。下面是解析PFS解析工具,里面已经透露了很多信息了。
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#define u32 uint32_t
#define u16 uint16_t
#define u8 uint8_t
struct pfs_header {
char magic[8]; /* PFS/0.9, are there others? */
u32 wtf1;
u16 wtf2;
u16 entries;
};
struct pfs_entry {
char path[128]; /* i've seen 32 and 40-byte variants */
u32 wtf; /* what this be? it seems it's always non zero. path terminator? */
u32 offset;
u32 size;
};
/* so noone with BE machine complains! */
static u16 le16_to_cpu(u16 x)
{
u16 lower = ((u8*)&x)[0];
u16 upper = ((u8*)&x)[1];
return lower + (upper << 8);
}
static u32 le32_to_cpu(u32 x)
{
u32 b0 = ((u8*)&x)[0];
u32 b1 = ((u8*)&x)[1];
u32 b2 = ((u8*)&x)[2];
u32 b3 = ((u8*)&x)[3];
return b0 + (b1<<8) + (b2 << 16) + (b3 << 24);
}
/**
* yes, I know, many checks are missing, and real/lseek is ugly,
* CBA to clean it up
**/
int main()
{
int i;
struct pfs_entry *entries;
struct pfs_header header;
void *filebuf;
char buf[128];
int path_len;
char *tmp;
/* read header */
read(0, &header, sizeof(header));
if (strncmp(header.magic, "PFS", 3) != 0) {
fprintf(stderr, "no PFS signature, exiting");
return 1;
}
header.wtf1 = le32_to_cpu(header.wtf1);
header.wtf2 = le16_to_cpu(header.wtf2);
header.entries = le16_to_cpu(header.entries);
printf("sig:t%sn", header.magic);
printf("wtf1:t0x%08xn", header.wtf1);
printf("wtf2:t0x%04xn", header.wtf2);
printf("entries: %in", header.entries);
/* read directory entries */
entries = malloc(sizeof(entries[0]) * header.entries);
#define MAX_SIZE (128*1024)
filebuf = malloc(MAX_SIZE);
assert(entries);
assert(filebuf);
read(0, buf, 128);
tmp = &buf[strlen(buf)];
while (*tmp == ' ')
tmp++;
path_len = tmp - buf;
lseek(0, -128, SEEK_CUR);
printf("detected path length of %i bytesn", path_len);
for (i=0; i<header.entries; i++) {
read(0, entries[i].path, path_len);
read(0, &entries[i].wtf, 12); /* remaining 3*4 bytes */
entries[i].wtf = le32_to_cpu(entries[i].wtf);
entries[i].offset = le32_to_cpu(entries[i].offset);
entries[i].size = le32_to_cpu(entries[i].size);
}
/* read and write files */
for (i=0; i<header.entries; i++) {
FILE *file;
int r;
printf("%-36s", entries[i].path);
printf("?1:0x%08x ", entries[i].wtf);
printf("offset: %-5i ", entries[i].offset);
printf("size: %in", entries[i].size);
file = fopen(entries[i].path, "wb");
assert(entries[i].size <= MAX_SIZE);
r = read(0, filebuf, entries[i].size);
fwrite(filebuf, 1, entries[i].size, file);
fclose(file);
}
return 0;
}
Header
header的长度为16,前8个字节是固定的,为PFS/0.9,然后6个字节为 0,剩下两个字节用来标识PFS里面的文件数量。
self.num_files = self._make_short(header[-2:], endianness)
Fname
接下来128字节是PFS的文件名。
Inode_no
这四个字节不知道有啥用,填 0没啥问题。
Foffset
文件偏移也是填 0就行。
Fsize
这个得是写入文件的大小,需要填写入的恶意文件的大小。
Data
写入恶意的插件。
下面是PFS的结构重要部分的划分。
完整的POC就不放了,成功触发可以实现任意命令执行。
补丁比对
在终端下两种代码的运行结果:
>>> import os
>>> os.path.join("/var", "../etc/passwd")
'/var/../etc/passwd'
>>> os.path.abspath(os.path.join("/var", "../etc/passwd"))
'/etc/passwd'
参考链接
-
firmware reverse engineering(http://0entropy.blogspot.com/2011/08/firmware-reverse-engineering.html)
-
fix path traversal in PFS extractor script(https://github.com/ReFirmLabs/binwalk/pull/617)
-
pfstool(https://lekensteyn.nl/files/pfs/pfs.txt)
-
Security Advisory: Remote Command Execution in binwalk(https://onekey.com/blog/security-advisory-remote-command-execution-in-binwalk/?cve=title#bonus)
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
原文始发于微信公众号(ChaMd5安全团队):Binwalk目录遍历漏洞复现