wtto WTTO'S BLOG

通过修改文件头来伪装文件

Posted on: 2022年6月28日  at 18:08
通过修改文件头来伪装文件

工作中遇到一种情况,黑产通过调用我们公开的上传图片接口,把自己的视频切片文件上传到我们公司的服务器储存上面,然后他们的用户在他们的产品中看视频,消耗的是我们公司的 CDN 流量。导致我们每次的云服务费用都很高。

黑产把他们的视频切片 ts 文件,改名为 png 文件,然后通过我们的上传图片接口,上传到我们的云服务器储存。

可是我们上传图片接口是有判断文件类型的,我们下载了他们伪装的 png 文件,发现可以按照图片格式,使用看图软件打开,不会报错,只是图片没有内容,只有一个小点。然后用开源库的识别文件类型测了下该文件,发现得到的结果确实是 png,没有问题。

可是我把该 png 文件改名为 ts 文件,然后使用视频播放器打开,就能发现这是一段视频切片,可以正常播放视频内容。

然后我研究了下这个的实现逻辑,列出如下。

获取文件真实类型

import { fileTypeFromFile } from 'file-type'

console.log(await fileTypeFromFile('./source/0.ts'))

// { ext: 'mts', mime: 'video/mp2t' }

获取文件表面的类型

import mime from 'mime'

console.log(mime.getType('./source/0.ts'))

// video/mp2t

获取文件的 Buffer

import fs from 'fs'

const bytes = fs.readFileSync('./source/0.ts')

console.log(bytes)

// <Buffer 47 40 11 10 00 42 f0 25 00 01 c1 00 00 ff 01 ff 00 01 fc 80 14 48 12 01 06 46 46 6d 70 65 67 09 53 65 72 76 69 63 65 30 31 77 7c 43 ca ff ff ff ff ff ... 2931246 more bytes>

这里打印的 Buffer 可以看作是一个十六进制的数组

console.log(bytes.at(0))
console.log(bytes[0])
// 71

单个 Buffer 元素打印,看到的是十进制的值

修改文件头

这里把 ts 文件伪装成 png PNG 文件头格式解析

// png前缀
const prefix = [
  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x01, 0x73,
  0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1,
  0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x12, 0x74, 0x00, 0x00,
  0x12, 0x74, 0x01, 0xde, 0x66, 0x1f, 0x78, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0xf8,
  0xff, 0xff, 0xff, 0x7f, 0x00, 0x09, 0xfb, 0x03, 0xfd, 0x05, 0x43, 0x45, 0xca, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
  0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
]
const prefixLen = prefix.length
const buffer = Buffer.alloc(bytes.length + prefixLen)
prefix.forEach((b, i) => {
  buffer[i] = b
})
bytes.forEach((b, i) => {
  buffer[prefixLen + i] = b
})

保存 buffer 到新的伪装文件

fs.writeFileSync('./target/0.png', buffer)

console.log(await fileTypeFromFile('./target/0.ts'))
// { ext: 'png', mime: 'image/png' }
console.log(mime.getType('./target/0.ts'))
// image/png

直接打开 target/0.png,可以作为图片打开,不报错。 直接更改后缀 target/0.pngtarget/0.ts,可以用视频播放器播放,不报错。


文件头

  • image/png

    const HEX = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
    const DEC = [137, 80, 78, 71, 13, 10, 26, 10]
    
  • video/mp2t

    // Raw MPEG-2 transport stream (188-byte packets)
    const HEX = [0x47, ...[187], 0x47, ...[]];
    const DEC = [71, ...[187], 71, ...[]];
    
    // Blu-ray Disc Audio-Video (BDAV) MPEG-2 transport stream has 4-byte TP_extra_header before each 188-byte packet
    const HEX = [...[4], 0x47, ...[187], ...[4], 0x47, ...[]];
    const DEC = [..[4], 71, ...[187], ...[4], 71, ...[]];
    

可参考利用文件头标志判断文件类型

作者:  wtto
发表时间: 2022年6月28日
最后更新时间:  2024年2月5日
版权说明:  CC BY-NC-ND 4.0 DEED