freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

CTF杂项-图片隐写、压缩包隐写、音频隐写
2020-11-11 13:50:13

前言

对于刷的杂项题中,基础图片隐写、压缩包隐写、音频隐写。总结一下。。。

图片隐写

所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。

1.图种

一种采用特殊方式将图片文件(如jpg格式)与压缩文件结合起来的文件。该文件一般输出为jpg图片文件,可以正常预览图片;当有人将该图片下载到本地后,可以通过修改文件的后缀,将.jpg改为.zip,并用winrar/7zip查看得到数据。由于这种方式有利于某些网友(老司机)传播种子文件,故称为图种,又叫作内涵图。——百度百科

  • 图种的制作

将一个压缩包隐写到图片中:

命令如下

copy /b 1.zip+2.jpg out.jpg隐写的图片就是out.jpg

我们用010看一下out.zip,会发现这个文件中有一个zip文件

  • 解决办法

1.将后缀改为.rar,解压可以得到。但是,当隐写里面有多个文件的话,就会失败的。

2.用binwalk工具,检索图片中文件,然后用foremost或者dd将文件分离

2.LSB隐写

LSB隐写,也就是最低有效位,图片中的像数一般是由三原色组成,这三原色也可以组成别的颜色。在png图片的存储中,每个颜色会有8bit,LSB隐写就是修改像数中最低的1bit,写入加密信息。

  • 用stegsolve工具的Gray bits模块一张张查看
  • 也可以使用python脚本

3.文件格式缺失和gif隐写

比如拿到一张.gif的图片,打开发现报错。用010或者winhex打开,对图片修复之后,放在Stegsolve中,一帧一帧查看

  • Analyse--Frame Brower

stegsolve工具的使用

对于Analyse窗口下的功能介绍:

file format:文件格式,主要查看图片的具体信息

data extract:数据抽取,图片中隐藏数据的抽取

frame browser:帧浏览器,是对gif格式的动图进行分解,动图变成一张张图片

image combiner:拼接图片

压缩包隐写

1.zip伪加密

zip文件由三个部分组成:压缩源文件数据区+压缩源文件目录区+压缩源文件目录结束标志

在压缩源文件数据区有2个字节的 全局方式位标记,在压缩源文件目录区也有2字节的 全局方式位标记,都用以标记是否加密。

  • 若是没有加密的zip,它的压缩文件数据区的全局方式位标记和压缩源文件目录区的全局方式位标记都是00 00

  • 加密的zip两次都不是00 00,一般是01 00,但是不同版本的压缩软件或是算法,这里的值可能有不同,比如:09 00

  • 那么把未加密的zip压缩源文件目录区的全局方式位标记改成01 00(09 00)的话,压缩软件就认为是加密的,就是所谓的伪加密。

2.crc32碰撞

crc即循环冗余校验:

每个文件都有唯一的crc32值,即使数据中一个bit发生变化,也会导致crc32值不同。如果知道一段数据的长度和crc32值,那么就可以穷举数据,与其crc32对照,进行暴力猜解。适合小文本文件。

  • 假如有一个加密的rar,直接双击打开可以看到其中的信息,而知道其中全是数字,便可写脚本爆破。

  • 收集一些爆破脚本(一次比赛中,收集大佬的脚本,研究学习,,,)

    爆破5bytes:

    # -*- coding: utf-8 -*-# 5bytes CRC32import itertoolsimport binasciiimport string
    class crc32_reverse_class(object):
        # the code is modified from https://github.com/theonlypwner/crc32/blob/master/crc32.py
        def __init__(self, crc32, length, tbl=string.printable,
                     poly=0xEDB88320, accum=0):
            self.char_set = set(map(ord, tbl))
            self.crc32 = crc32
            self.length = length
            self.poly = poly
            self.accum = accum
            self.table = []
            self.table_reverse = []
    
        def init_tables(self, poly, reverse=True):
            # build CRC32 table
            for i in range(256):
                for j in range(8):
                    if i & 1:
                        i >>= 1
                        i ^= poly
                    else:
                        i >>= 1
                self.table.append(i)
            assert len(self.table) == 256, "table is wrong size"
            # build reverse table
            if reverse:
                found_none = set()
                found_multiple = set()
                for i in range(256):
                    found = []
                    for j in range(256):
                        if self.table[j] >> 24 == i:
                            found.append(j)
                    self.table_reverse.append(tuple(found))
                    if not found:
                        found_none.add(i)
                    elif len(found) > 1:
                        found_multiple.add(i)
                assert len(self.table_reverse) == 256, "reverse table is wrong size"
    
        def rangess(self, i):
            return ', '.join(map(lambda x: '[{0},{1}]'.format(*x), self.ranges(i)))
    
        def ranges(self, i):
            for kg in itertools.groupby(enumerate(i), lambda x: x[1] - x[0]):
                g = list(kg[1])
                yield g[0][1], g[-1][1]
    
        def calc(self, data, accum=0):
            accum = ~accum
            for b in data:
                accum = self.table[(accum ^ b) & 0xFF] ^ ((accum >> 8) & 0x00FFFFFF)
            accum = ~accum
            return accum & 0xFFFFFFFF
    
        def findReverse(self, desired, accum):
            solutions = set()
            accum = ~accum
            stack = [(~desired,)]
            while stack:
                node = stack.pop()
                for j in self.table_reverse[(node[0] >> 24) & 0xFF]:
                    if len(node) == 4:
                        a = accum
                        data = []
                        node = node[1:] + (j,)
                        for i in range(3, -1, -1):
                            data.append((a ^ node) & 0xFF)
                            a >>= 8
                            a ^= self.table[node]
                        solutions.add(tuple(data))
                    else:
                        stack.append(((node[0] ^ self.table[j]) << 8,) + node[1:] + (j,))
            return solutions
    
        def dfs(self, length, outlist=['']):
            tmp_list = []
            if length == 0:
                return outlist
            for list_item in outlist:
                tmp_list.extend([list_item + chr(x) for x in self.char_set])
            return self.dfs(length - 1, tmp_list)
    
        def run_reverse(self):
            # initialize tables
            self.init_tables(self.poly)
            # find reverse bytes
            desired = self.crc32
            accum = self.accum
            # 4-byte patch
            if self.length >= 4:
                patches = self.findReverse(desired, accum)
                for patch in patches:
                    checksum = self.calc(patch, accum)
                    print 'verification checksum: 0x{0:08x} ({1})'.format(
                        checksum, 'OK' if checksum == desired else 'ERROR')
                for item in self.dfs(self.length - 4):
                    patch = map(ord, item)
                    patches = self.findReverse(desired, self.calc(patch, accum))
                    for last_4_bytes in patches:
                        if all(p in self.char_set for p in last_4_bytes):
                            patch.extend(last_4_bytes)
                            print '[find]: {1} ({0})'.format(
                                'OK' if self.calc(patch, accum) == desired else 'ERROR', ''.join(map(chr, patch)))
            else:
                for item in self.dfs(self.length):
                    if crc32(item) == desired:
                        print '[find]: {0} (OK)'.format(item)
    def crc32_reverse(crc32, length, char_set=string.printable,
                      poly=0xEDB88320, accum=0):
        '''
    
        :param crc32: the crc32 you wnat to reverse
        :param length: the plaintext length
        :param char_set: char_set
        :param poly: poly , default 0xEDB88320
        :param accum: accum , default 0
        :return: none
        '''
        obj = crc32_reverse_class(crc32, length, char_set, poly, accum)
        obj.run_reverse()
    def crc32(s):
        '''
    
        :param s: the string to calculate the crc32
        :return: the crc32
        '''
        return binascii.crc32(s) & 0xffffffff
    
    l=[0x397E0355]
    for k in l:
            crc32_reverse(k,5)
            print '======='
    
    爆破6bytes
#!/usr/bin/env python# CRC32 tools by Victor 6bytes
import argparseimport osimport sys

permitted_characters = set(
    map(ord, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_'))  # \w

testing = False

args = None
def get_poly():
    poly = parse_dword(args.poly)
    if args.msb:
        poly = reverseBits(poly)
    check32(poly)
    return poly
def get_input():
    if args.instr:
        return tuple(map(ord, args.instr))
    with args.infile as f:  # pragma: no cover
        return tuple(map(ord, f.read()))
def out(msg):
    if not testing:  # pragma: no cover
        args.outfile.write(msg)
        args.outfile.write(os.linesep)

table = []
table_reverse = []
def init_tables(poly, reverse=True):
    global table, table_reverse
    table = []
    # build CRC32 table
    for i in range(256):
        for j in range(8):
            if i & 1:
                i >>= 1
                i ^= poly
            else:
                i >>= 1
        table.append(i)
    assert len(table) == 256, "table is wrong size"
    # build reverse table
    if reverse:
        table_reverse = []
        found_none = set()
        found_multiple = set()
        for i in range(256):
            found = []
            for j in range(256):
                if table[j] >> 24 == i:
                    found.append(j)
            table_reverse.append(tuple(found))
            if not found:
                found_none.add(i)
            elif len(found) > 1:
                found_multiple.add(i)
        assert len(table_reverse) == 256, "reverse table is wrong size"
        if found_multiple:
            out('WARNING: Multiple table entries have an MSB in {0}'.format(
                rangess(found_multiple)))
        if found_none:
            out('ERROR: no MSB in the table equals bytes in {0}'.format(
                rangess(found_none)))
def calc(data, accum=0):
    accum = ~accum
    for b in data:
        accum = table[(accum ^ b) & 0xFF] ^ ((accum >> 8) & 0x00FFFFFF)
    accum = ~accum
    return accum & 0xFFFFFFFF
def rewind(accum, data):
    if not data:
        return (accum,)
    stack = [(len(data), ~accum)]
    solutions = set()
    while stack:
        node = stack.pop()
        prev_offset = node[0] - 1
        for i in table_reverse[(node[1] >> 24) & 0xFF]:
            prevCRC = (((node[1] ^ table) << 8) |
                       (i ^ data[prev_offset])) & 0xFFFFFFFF
            if prev_offset:
                stack.append((prev_offset, prevCRC))
            else:
                solutions.add((~prevCRC) & 0xFFFFFFFF)
    return solutions
def findReverse(desired, accum):
    solutions = set()
    accum = ~accum
    stack = [(~desired,)]
    while stack:
        node = stack.pop()
        for j in table_reverse[(node[0] >> 24) & 0xFF]:
            if len(node) == 4:
                a = accum
                data = []
                node = node[1:] + (j,)
                for i in range(3, -1, -1):
                    data.append((a ^ node) & 0xFF)
                    a >>= 8
                    a ^= table[node]
                solutions.add(tuple(data))
            else:
                stack.append(((node[0] ^ table[j]) << 8,) + node[1:] + (j,))
    return solutions
# Tools
def parse_dword(x):
    return int(x, 0) & 0xFFFFFFFF
def reverseBits(x):
    # http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
    # http://stackoverflow.com/a/20918545
    x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1)
    x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2)
    x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4)
    x = ((x & 0x00FF00FF) << 8) | ((x & 0xFF00FF00) >> 8)
    x = ((x & 0x0000FFFF) << 16) | ((x & 0xFFFF0000) >> 16)
    return x & 0xFFFFFFFF
# Compatibility with Python 2.6 and earlier.if hasattr(int, "bit_length"):
    def bit_length(num):
        return num.bit_length()else:
    def bit_length(n):
        if n == 0:
            return 0
        bits = -32
        m = 0
        while n:
            m = n
            n >>= 32
            bits += 32
        while m:
            m >>= 1
            bits += 1
        return bits
def check32(poly):
    if poly & 0x80000000 == 0:
        out('WARNING: polynomial degree ({0}) != 32'.format(bit_length(poly)))
        out('         instead, try')
        out('         0x{0:08x} (reversed/lsbit-first)'.format(poly | 0x80000000))
        out('         0x{0:08x} (normal/msbit-first)'.format(reverseBits(poly | 0x80000000)))
def reciprocal(poly):
    ''' Return the reversed reciprocal (Koopman notatation) polynomial of a
        reversed (lsbit-first) polynomial '''
    return reverseBits((poly << 1) | 1)
def print_num(num):
    ''' Write a numeric result in various forms '''
    out('hex: 0x{0:08x}'.format(num))
    out('dec:   {0:d}'.format(num))
    out('oct: 0o{0:011o}'.format(num))
    out('bin: 0b{0:032b}'.format(num))
import itertools
def ranges(i):
    for kg in itertools.groupby(enumerate(i), lambda x: x[1] - x[0]):
        g = list(kg[1])
        yield g[0][1], g[-1][1]
def rangess(i):
    return ', '.join(map(lambda x: '[{0},{1}]'.format(*x), ranges(i)))
# Parsers
def get_parser():
    ''' Return the command-line parser '''
    parser = argparse.ArgumentParser(
        description="Reverse, undo, and calculate CRC32 checksums")
    subparsers = parser.add_subparsers(metavar='action')

    poly_flip_parser = argparse.ArgumentParser(add_help=False)
    subparser_group = poly_flip_parser.add_mutually_exclusive_group()
    subparser_group.add_argument(
        '-m', '--msbit', dest="msb", action='store_true',
        help='treat the polynomial as normal (msbit-first)')
    subparser_group.add_argument('-l', '--lsbit', action='store_false',
                                 help='treat the polynomial as reversed (lsbit-first) [default]')

    desired_poly_parser = argparse.ArgumentParser(add_help=False)
    desired_poly_parser.add_argument(
        'desired', type=str, help='[int] desired checksum')

    default_poly_parser = argparse.ArgumentParser(add_help=False)
    default_poly_parser.add_argument(
        'poly', default='0xEDB88320', type=str, nargs='?',
        help='[int] polynomial [default: 0xEDB88320]')

    accum_parser = argparse.ArgumentParser(add_help=False)
    accum_parser.add_argument(
        'accum', type=str, help='[int] accumulator (final checksum)')

    default_accum_parser = argparse.ArgumentParser(add_help=False)
    default_accum_parser.add_argument(
        'accum', default='0', type=str, nargs='?',
        help='[int] starting accumulator [default: 0]')

    outfile_parser = argparse.ArgumentParser(add_help=False)
    outfile_parser.add_argument('-o', '--outfile',
                                metavar="f",
                                type=argparse.FileType('w'),
                                default=sys.stdout,
                                help="Output to a file instead of stdout")

    infile_parser = argparse.ArgumentParser(add_help=False)
    subparser_group = infile_parser.add_mutually_exclusive_group()
    subparser_group.add_argument('-i', '--infile',
                                 metavar="f",
                                 type=argparse.FileType('rb'),
                                 default=sys.stdin,
                                 help="Input from a file instead of stdin")
    subparser_group.add_argument('-s', '--str',
                                 metavar="s",
                                 type=str,
                                 default='',
                                 dest='instr',
                                 help="Use a string as input")

    subparser = subparsers.add_parser('flip', parents=[outfile_parser],
                                      help="flip the bits to convert normal(msbit-first) polynomials to reversed (lsbit-first) and vice versa")
    subparser.add_argument('poly', type=str, help='[int] polynomial')
    subparser.set_defaults(
        func=lambda: print_num(reverseBits(parse_dword(args.poly))))

    subparser = subparsers.add_parser('reciprocal', parents=[outfile_parser],
                                      help="find the reciprocal (Koopman notation) of a reversed (lsbit-first) polynomial and vice versa")
    subparser.add_argument('poly', type=str, help='[int] polynomial')
    subparser.set_defaults(func=reciprocal_callback)

    subparser = subparsers.add_parser('table', parents=[outfile_parser,
                                                        poly_flip_parser,
                                                        default_poly_parser],
                                      help="generate a lookup table for a polynomial")
    subparser.set_defaults(func=table_callback)

    subparser = subparsers.add_parser('reverse', parents=[
        outfile_parser,
        poly_flip_parser,
        desired_poly_parser,
        default_accum_parser,
        default_poly_parser],
        help="find a patch that causes the CRC32 checksum to become a desired value")
    subparser.set_defaults(func=reverse_callback)

    subparser = subparsers.add_parser('undo', parents=[
        outfile_parser,
        poly_flip_parser,
        accum_parser,
        default_poly_parser,
        infile_parser],
        help="rewind a CRC32 checksum")
    subparser.add_argument('-n', '--len', metavar='l', type=str,
                           default='0', help='[int] number of bytes to rewind [default: 0]')
    subparser.set_defaults(func=undo_callback)

    subparser = subparsers.add_parser('calc', parents=[
        outfile_parser,
        poly_flip_parser,
        default_accum_parser,
        default_poly_parser,
        infile_parser],
        help="calculate the CRC32 checksum")
    subparser.set_defaults(func=calc_callback)

    return parser
def reciprocal_callback():
    poly = parse_dword(args.poly)
    check32(poly)
    print_num(reciprocal(poly))
def table_callback():
    # initialize tables
    init_tables(get_poly(), False)
    # print table
    out('[{0}]'.format(', '.join(map('0x{0:08x}'.format, table))))
def reverse_callback():
    # initialize tables
    init_tables(get_poly())
    # find reverse bytes
    desired = parse_dword(args.desired)
    accum = parse_dword(args.accum)
    # 4-byte patch
    patches = findReverse(desired, accum)
    for patch in patches:
        out('4 bytes: {{0x{0:02x}, 0x{1:02x}, 0x{2:02x}, 0x{3:02x}}}'.format(*patch))
        checksum = calc(patch, accum)
        out('verification checksum: 0x{0:08x} ({1})'.format(
            checksum, 'OK' if checksum == desired else 'ERROR'))
    # 6-byte alphanumeric patches
    for i in permitted_characters:
        for j in permitted_characters:
            patch = [i, j]
            patches = findReverse(desired, calc(patch, accum))
            for last_4_bytes in patches:
                if all(p in permitted_characters for p in last_4_bytes):
                    patch.extend(last_4_bytes)
                    out('alternative: {1}{2}{3}{4}{5}{6} ({0})'.format(
                        'OK' if calc(patch, accum) == desired else 'ERROR', *map(chr, patch)))
def undo_callback():
    # initialize tables
    init_tables(get_poly())
    # calculate checksum
    accum = parse_dword(args.accum)
    maxlen = int(args.len, 0)
    data = get_input()
    if not 0 < maxlen <= len(data):
        maxlen = len(data)
    out('rewinded {0}/{1} ({2:.2f}%)'.format(maxlen, len(data),
        maxlen * 100.0 / len(data) if len(data) else 100))
    for solution in rewind(accum, data[-maxlen:]):
        out('')
        print_num(solution)
def calc_callback():
    # initialize tables
    init_tables(get_poly(), False)
    # calculate checksum
    accum = parse_dword(args.accum)
    data = get_input()
    out('data len: {0}'.format(len(data)))
    out('')
    print_num(calc(data, accum))
def main(argv=None):
    ''' Runs the program and handles command line options '''
    parser = get_parser()

    # Parse arguments and run the function
    global args
    args = parser.parse_args(argv)
    args.func()
if __name__ == '__main__':
    main()  # pragma: no cover

3.直接爆破

AccentRPR利用GPU爆破

  • 暴力:选择密码范围,长度等,由如那件组合生成密码进行爆破

  • 掩码:知道密码中的一部分,只需按规则构造其余部分

  • 字典:通常是多用户常用的一些密码字典,导入字典文件用其中的密码进行爆破。

已知明文攻击即crc32爆破:

所谓明文攻击就是通过其他手段知道zip加密文件中的某些内容,比如在某些网站上发现它的readme.txt文件,或其他文件,这时就尝试破解。

音频隐写

音频相关的题有mp3隐写,波形隐写,lsb隐写,频谱隐写等 使用工具:

  • audacity,ocenaudio音频编辑软件

  • mp3stego 将需要加密的数据压缩加密隐写藏在MP3文件中

  • video to picture可以将视频的每帧提转成图片

  • QR Research ctf 常用的二维码识别软件

  • mangeMagick 免费创建、编辑、合成图片的软件

  • 莫斯电码图

1.mp3隐写

  • 题目中如果没有key,而是附件中只有一个MP3,那就是用mp3stegosaurus隐藏的数据,也可能是在音频的频谱中隐藏了数据。

  • mp3stego工具隐写命令encode -E hidden_text.txt -P pass svega.wavsvega_stego.mp3

2.频谱隐写

频谱隐写,特征是听起来杂音或者刺耳。

  • 用audacity工具打开,能够看到的。

# CTF 图像隐写
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者