day3-1自定义Base64编码

自定义Base64编码

当遇到base64编码解不开的时候,那可能就是它的字典顺序被改写了。
解题思路:查找关键字->收集字典顺序->打开工具->解码完成。
在这里插入图片描述看到这个结果,可以推测是要求我们找找到正确的flag,才能输出正确的结果~~(这不是废话嘛)~~
接下来用文本编译器打开搜索关键字,发现了编码后的结果。
在这里插入图片描述开心的拿着这段数据去解码~
在这里插入图片描述
可以看到这里是解不开的,(不开心) 只能想起其他的办法了。
因为这个是一个exe文件,所以祭上IDA。打开后shift+F12找到关键字
在这里插入图片描述可以清楚的看到想要的两个数据。开始的时候想着自己可以倒推下编码算法,结果孟浪了。

std::string __cdecl base64Encode(std::string *p_decode)
{
  std::string *v1; // rdx
  char *v2; // rax
  int v3; // eax
  int v4; // ebx
  char *v5; // rax
  int v6; // eax
  int v7; // ebx
  char *v8; // rax
  int v9; // eax
  _BYTE *v10; // rax
  int v11; // eax
  char *v12; // rax
  int v13; // eax
  _BYTE *v14; // rax
  int v15; // eax
  char *v16; // rax
  int v17; // eax
  int v18; // ebx
  char *v19; // rax
  int v20; // eax
  _BYTE *v21; // rax
  int v22; // eax
  __int64 v24; // [rsp+0h] [rbp-80h]
  char v25; // [rsp+2Fh] [rbp-51h]
  int pos_0; // [rsp+30h] [rbp-50h]
  int pos; // [rsp+34h] [rbp-4Ch]
  int len; // [rsp+38h] [rbp-48h]
  int i; // [rsp+3Ch] [rbp-44h]
  std::string *p_encodeResult; // [rsp+60h] [rbp-20h]
  std::string *p_decodea; // [rsp+68h] [rbp-18h]

  p_encodeResult = p_decode;
  p_decodea = v1;
  std::allocator<char>::allocator((char *)&v24 + 47);
  std::string::string(p_encodeResult, &unk_489084, &v25);
  std::allocator<char>::~allocator(&v25);
  len = std::string::length(p_decodea);
  for ( i = 0; len / 3 > i; ++i )
  {
    v2 = (char *)std::string::operator[](p_decodea, 3 * i);
    v3 = *(char *)std::string::operator[](&baseKey, *v2 >> 2);
    std::string::operator+=(p_encodeResult);
    v4 = 16 * (*(_BYTE *)std::string::operator[](p_decodea, 3 * i) & 3);
    v5 = (char *)std::string::operator[](p_decodea, 3 * i + 1);
    v6 = *(char *)std::string::operator[](&baseKey, v4 | (*v5 >> 4));
    std::string::operator+=(p_encodeResult);
    v7 = 4 * (*(_BYTE *)std::string::operator[](p_decodea, 3 * i + 1) & 0xF);
    v8 = (char *)std::string::operator[](p_decodea, 3 * i + 2);
    v9 = *(char *)std::string::operator[](&baseKey, v7 | (*v8 >> 6));
    std::string::operator+=(p_encodeResult);
    v10 = (_BYTE *)std::string::operator[](p_decodea, 3 * i + 2);
    v11 = *(char *)std::string::operator[](&baseKey, *v10 & 0x3F);
    std::string::operator+=(p_encodeResult);
  }
  if ( len % 3 == 1 )
  {
    pos = 3 * (len / 3);
    v12 = (char *)std::string::operator[](p_decodea, 3 * (len / 3));
    v13 = *(char *)std::string::operator[](&baseKey, *v12 >> 2);
    std::string::operator+=(p_encodeResult);
    v14 = (_BYTE *)std::string::operator[](p_decodea, pos);
    v15 = *(char *)std::string::operator[](&baseKey, 16 * (*v14 & 3));
    std::string::operator+=(p_encodeResult);
    std::string::operator+=(p_encodeResult, "==");
  }
  if ( len % 3 == 2 )
  {
    pos_0 = 3 * (len / 3);
    v16 = (char *)std::string::operator[](p_decodea, 3 * (len / 3));
    v17 = *(char *)std::string::operator[](&baseKey, *v16 >> 2);
    std::string::operator+=(p_encodeResult);
    v18 = 16 * (*(_BYTE *)std::string::operator[](p_decodea, pos_0) & 3);
    v19 = (char *)std::string::operator[](p_decodea, pos_0 + 1);
    v20 = *(char *)std::string::operator[](&baseKey, v18 | (*v19 >> 4));
    std::string::operator+=(p_encodeResult);
    v21 = (_BYTE *)std::string::operator[](p_decodea, pos_0 + 1);
    v22 = *(char *)std::string::operator[](&baseKey, 4 * (*v21 & 0xF));
    std::string::operator+=(p_encodeResult);
    std::string::operator+=(p_encodeResult, "=");
  }
  return (std::string)p_encodeResult;
}

自己分析了老半天,没开明白。 知道这个编码的大致流程,然后就想起来开始那段字典顺序 ,接这个打开解码网站,嗯就是这个样子~~(还是工具香)~~
在这里插入图片描述然后就拿到了flag,over

参考知识: Base64编码原理:
它是用64个可打印字符表示二进制所有数据方法。由于2的6次方等于64,所以可以用每6个位元为一个单元,对应某个可打印字符。我们知道三个字节有24个位元,就可以刚好对应于4个Base64单元,即3个字节需要用4个Base64的可打印字符来表示。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9 ,这样共有62个字符,此外两个可打印符号在不同的系统中一般有所不同。但是,我们经常所说的Base64另外2个字符是:“+/”。这64个字符,所对应表如下。
图片: https://uploader.shimo.im/f/iewmUJ3VPiZg1Udk.png
转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲区中剩下的bit用0补足。然后,每次取出6个bit,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。所以加密后的数据量是原来的4/3
这样说,仔细思考一下,你会很快发现,一个问题,就是每次转换的字节数不一定就是24的整数倍,会出现有多余不足六位的情况,在base64中处理的方法是加零凑够六位,但是这样一来在解码的时候就会出现多余的位 这该怎么办呢? 不用担心,base64想到了一个很好的解决办法。
这个办法就是在 base64凑零的同时,还要满足凑出来的位数是8的倍数,不然就加一个或者两个特殊的六位 = 符号。为什么是一个或者两个=符号呢? 因为多个8位转为6位 只会出现 剩余 2位,4位的情况,剩余2位 只需要一个 表示六位的 = 便可变为8的整数;而剩余4位 需要两个表示6位的 = 便可以变成16 是8的整数。然后在解密的时候不解析 =即可。
之所以位的总数需要凑成8的倍数,是因为base64主要用于加密后的数据传送,而在传送机制中都认为传送的最小单位是按照字节算的,所以不能出现不是位总数不是8的倍数的情况,在接收到数据后,按顺序将6位的base64直接按照顺序解密成字节就完成解密了。
接下来通过几张图为大家进行详细的解释:

这是恰好三个字节转为base64
图片: https://uploader.shimo.im/f/lCuZtS3BVdXR2wBm.png

这是字节的位总数不是6的倍数的情况,当剩下4位时,我们需要补2个 = 凑齐8的倍数;当剩下的是2位时,我们需要补齐1个 = 抽泣8的倍数。
在这里插入图片描述

base64 基本原理2

btoa 和 atob 的意义
首先我们要知道为什么编码叫 btoa,而解码叫 atob

btoa = binary to ASCII = encode

atob = ASCII to binary = decode

C语言中有一个函数叫 atoi,意思是 convert a string to an integer,也就是 “10” => 10

i 是 integer 很好理解,那为什么 a 是 string 呢? a 其实是 ASCII 的缩写,ASCII 也是一种编码,string 为什么还有编码呢?因为电脑只认识二进制0和1,string 和 二进制的对应关系就是编码,比如 01100001 对应字符 ‘a’ 就是由 ASCII 编码规定的

理解了 atoi 再理解 btoa 就很简单了,a 是 ASCII,也就是 string,而 b 就是 binary

base64 的作用
虽然有时候我们用字符串作为 btoa 的参数,但 base64 大部分用处是用在二进制文件上的
base64 的字典表
有人说,感觉 base64 有几种字典表啊

我们先来看看标准的 base64 字典表

base64 使用 64 个 ASCII 字符

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

[A-Za-z0-9+/] = 26 + 26 + 10 + 2 = 64

注意这64位是按顺序的,就是说 0 = 00000000 = A, 63 = 11111111 = /

URL Safe Base64
标准 base64 中的两个非字母数字 + 和 / 设计的特别坑爹,因为它们不管在 url 中还有文件系统中都属于特殊字符

而剩下的那个 padding 字符 = 就更坑爹了,因为 = 直接就是 query string 中的 equal 字符

因此机智的人类又发明了 base64 url safe 版本,和标准版区别有三

  • 被 - 代替,加号对应减号
    / 被 _ 代替
    没有 = 这个 padding 字符
    也就是下面这个表

这个规则并不是一些语言库自己定的,而是被写进了 base64 的标准 RFC 4648,因此大可放心的用,python 和 golang 中都有 url safe base64

golang https://golang.org/pkg/encoding/base64/#URLEncoding

python 19.6. base64 — Base16, Base32, Base64, Base85 Data Encodings

为什么是64而不是其他呢?
我们知道64是2的6次方,一连串二进制被6位6位的等分

那为什么不是5位等分或者7位等分呢?

7位等分就需要128个字符,找不到这么多现成的字符,而5位等分只需要32个字符,连大小写字符都没用完,太浪费了

6等分造成的一个后果就是 base64 后的文本大小始终是大于等于源数据的 8/6,也就是至少比源文件大 1/3,那也肯定比源数据大的,因为虽然6等分了,但一个字符还是8bit。但要注意的是 gzip 后这个大小差距会减少

base64 的补齐
base64 有一个 padding 字符 =

base64 按6位分,而 byte 是8位二进制,最小公倍数是24

因此不管把 6bit 组转成 8bit 组还是把 8bit 组转成 6bit 组,都需要先用 0 补成 24 的倍数

一次加密事例
源文件是字符串 “abc”
先转成二进制 [97, 98, 99]
对应的二进制就是 [01100001, 01100010, 01100011],取法 ‘a’.charCodeAt(0).toString(2)
正好24位不用补齐
重新按6位分割成新的数组 [011000, 010110, 001001, 100011]
对应十进制就是 [24, 22, 9, 35]
映射到 base64 表中就是 [‘Y’, ‘W’, ‘J’, ‘j’],也就是 YWJj
但真的要把字符串拼出来然后再 split 吗?当然不行

我们必须用男人的方式来解决此问题,没错就是二进制!

011000 = 01100001, 右移两位就是, 也就是 97 >> 2 = 24
010110 = 01100001, 01100010,((97 & 0x03) << 4) | (98 >> 4) = 22
001001 = 01100010, 01100011,((98 & 0x0f) << 2) | (99 >> 6) = 9
100011 = 01100011,99 & 0x3f = 35`

  • 0
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

£仲夏☆如烟彡

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值