【攻防世界】十一、base64stego-编程知识网

步骤

下载所给附件,发现是一个zip压缩文件,并且解压需要密码。
查看了师傅们的文章,发现是伪加密:

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

伪加密原理:zip伪加密是在文件头中加密标志位做修改,然后在打开时误被识别成加密压缩包。

【攻防世界】十一、base64stego-编程知识网
压缩源文件数据区:

50 4B 03 04:头文件标记
14 03:解压文件所需 pkware 版本
00 00:全局方式位标记(判断有无加密的重要标志)
08 00:压缩方式
68 BF:最后修改文件的时间
9B 48:最后修改文件的日期
FE 32 7D 4B:CRC-32校验
E9 0D 00 00:压缩后尺寸
B5 1B 00 00:未压缩尺寸
09 00:文件名长度
00 00:扩展记录长度

【攻防世界】十一、base64stego-编程知识网

压缩源文件目录区:

50 4B 01 02:目录中文件文件头标记
3F 03:压缩使用的 pkware 版本
14 03:解压文件所需 pkware 版本
09 00:全局方式位标记(有无加密的重要标志,这个更改这里进行伪加密,改为00 00打开不会提示要密码了)
08 00:压缩方式
68 BF:最后修改文件的时间
9B 48:最后修改文件的日期
FE 32 7D 4B:CRC-32校验
E9 0D 00 00:压缩后尺寸
B5 1B 00 00:未压缩尺寸
09 00:文件名长度
24 00:扩展字段长度
00 00:文件注释长度
00 00:磁盘开始号
00 00:内部文件属性
20 80 ED 81:外部文件属性
00 00 00 00:局部头部偏移量

【攻防世界】十一、base64stego-编程知识网

压缩源文件目录结束标志:

50 4B 05 06:目录结束标记
00 00:当前磁盘编号
00 00:目录区开始磁盘编号
01 00:本磁盘上纪录总数
01 00:目录区中纪录总数
5B 00 00 00:目录区尺寸大小
10 0E 00 00:目录区对第一张磁盘的偏移量
00 00:ZIP 文件注释长度

判断是否加密:

  • 无加密
    • 压缩源文件数据区的全局加密应当为00 00
    • 压缩源文件目录区的全局方式位标记应当为00 00
  • 伪加密
    • 压缩源文件数据区的全局加密应当为00 00
    • 压缩源文件目录区的全局方式位标记应当为09 00
  • 真加密
    • 压缩源文件数据区的全局加密应当为09 00
    • 压缩源文件目录区的全局方式位标记应当为09 00

使用winhex打开压缩包,根据观察可知这个文件使用了伪加密,所以我们只要将压缩源文件目录区的全局方式位标记改为00 00即可解压
【攻防世界】十一、base64stego-编程知识网
修改完之后保存解压,发现了一个txt文件
【攻防世界】十一、base64stego-编程知识网
里面全是base64编码的字符串,很明显是base64隐写

base64编码原理

base64编码就是用64个ascii字符作为基础来编码二进制内容的一种编码方式。编码后的数据是一个字符串, 包含的字符为: A-Za-z0-9+/共 64 个字符,= 是填充字符。

【攻防世界】十一、base64stego-编程知识网

  • 编码规则:把3个8位字节(3×8=24)转化为4个6位的字节(4×6=24),再根据每个字节的值,用base64编码表中的值替换,不足4个字节的,补“=”。编码得到的字符串长度必为4的倍数。

  • 编码步骤:
    1、 将待编码字符串各个字符换为对应ASCII码值
    2、 将得到的ASCII码值转换为8位二进制
    3、 将得到的8位二进制序列分割为6位一组(不足6位的末尾添0补上)
    4、 将每个6位二进制数列转换为十进制数字。(6位二进制最大值为63)
    5、将转换所得的十进制值对应Base64编码表中的字符进行替换
    6、若编码所得字符串长非4倍,添一个或两个“=”补上

编码的文本字节数是3的倍数时,刚好可以编码成4个字符
【攻防世界】十一、base64stego-编程知识网
编码的文本字节数不是3的倍数时,剩下1个字符时
【攻防世界】十一、base64stego-编程知识网
编码的文本字节数不是3的倍数时,剩下2个字符时
【攻防世界】十一、base64stego-编程知识网

  • 解码步骤:
    1、将待解码字符串中的字符对应查找Base64编码表中的序列值(末尾的“=”直接忽略)
    2、将所得对应序列值转换为6位二进制字串
    3、将所有6位二进制字串从左至右按8位分割(多的是补的0直接丢掉不影响数据还原结果)
    4、将每个8位二进制字串转换为十进制
    5、十进制值对应的ASCII字符串即为结果

base64隐写原理
了解了base64编码的原理之后,那么base64隐写理解起来也就简单了

它之所以能够进行信息的隐藏,原因就在于解码时的第三步,会有部分多余数据被丢弃,而且这些数据是我们进行补充的0,那要是我们不全用0进行补充,而是用1或0进行填充(二进制),然后就会起到隐藏信息的作用

提取信息也很简单,将我们填充进去的数据拿出来然后组成一串二进制字符串进行转码即可,由于一串base64编码最多只有4bit的隐写空间,所以实现隐写需要大量的编码串。

上python3脚本

# 此方法用来将包含隐藏信息的字母转换为base64编码表对应的序列值(十进制数)并返回
def base64change(s):table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' # base64编码表,恰好索引值对应字母for i in range(len(table)):if table[i] == s:return i# 此方法用来获取隐藏信息二进制字符串
def base64solve():f = open('C:\\Users\\admin\\Desktop\\stego.txt','r')lines = f.readlines()flag_bin = ''for line in lines:# print(line)l = line.strip() # 去掉两边的空格if l[-1] == '=':if l[-2] == '=': # 含有两个=则包含4bit信息# 将返回的十进制转换为二进制数,由于返回的二进制数为0b开头,所以从第三位开始取,然后用0填充头部为4位,再取后四位隐藏的信息flag_bin += bin(base64change(l[-3]))[2:].zfill(4)[-4:]else:# 只含一个=则包含二bit信息flag_bin += bin(base64change(l[-2]))[2:].zfill(2)[-2:]#print(flag_bin)flag = ''for i in range(len(flag_bin)//8):flag += chr(int(flag_bin[i * 8:(i + 1) * 8], 2))print(flag)if __name__ == '__main__':base64solve()

执行程序,成功拿到flag,记得以flag{xxx}的形式提交
【攻防世界】十一、base64stego-编程知识网
使用Java的小伙伴可以用这个:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;public class Base64Security {public static void main(String[] args) throws IOException {// write your code hereString table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";File file = new File("C:\\Users\\admin\\Desktop\\stego.txt");BufferedReader reader = null;StringBuffer s = new StringBuffer();//二进制信息StringBuffer flag = new StringBuffer();//最终转为字符的信息try {reader = new BufferedReader(new FileReader(file));String tempStr;//存储读取每一行的字符串while ((tempStr=reader.readLine())!=null){//循环读取每一行//s.append(tempStr+"\n");int length = tempStr.length();//System.out.println(tempStr.charAt(length - 1));if ("=".equals(String.valueOf(tempStr.charAt(length-1)))){//最后一个字符为=的字符串//System.out.println("daol");if ("=".equals(String.valueOf(tempStr.charAt(length-2)))){//倒数第二个为=的字符串int i = table.indexOf(tempStr.charAt(length - 3));//找出含有信息的字符对应的序列十进制//System.out.println(i);String bin = Integer.toBinaryString(i);//转换为二进制字符串int i1 = Integer.parseInt(bin);//转换为int型String formatBin = String.format("%04d", i1);//0表示前面补0,4表示总长度为4,d表示整数//System.out.println(formatBin);int length1 = formatBin.length();//System.out.println(length1+"==");String substring = formatBin.substring(length1 - 4, length1);//获取包含隐藏信息的二进制字符串//System.out.println(substring);s.append(substring);//循环存储二进制字符串}else {int i = table.indexOf(tempStr.charAt(length - 2));//找出含有信息的字符对应的序列十进制//System.out.println(i);String bin = Integer.toBinaryString(i);//二进制int i1 = Integer.parseInt(bin);String formatBin = String.format("%04d", i1);//System.out.println(formatBin);int length1 = formatBin.length();//System.out.println(length1+"=");String substring = formatBin.substring(length1 - 2, length1);//System.out.println(substring);s.append(substring);}}}reader.close();//System.out.println(s.toString());for (int i=0;i<(s.length()/8);i++){flag.append((char)(Integer.parseInt(s.substring(i*8,(i+1)*8),2)));}System.out.println("flag:"+flag);}catch (IOException e){e.printStackTrace();}finally {if (reader !=null){try {reader.close();}catch (IOException e1){e1.printStackTrace();}}}}
}

【攻防世界】十一、base64stego-编程知识网
唉,不愧是人生苦短,我选Python~
不知道为啥,后面还有一个乱码符号…

总结

密码学:base64隐写的考察
代码能力的考察
zip伪加密的考察