跳至主要内容

Base64编码

编码过程演示

让我们通过一个具体的例子来看看它是如何工作的。
假设我们要编码字符串:”Man”

第一步:获取 ASCII 码并转换为二进制

  • M = 77 = 01001101
  • a = 97 = 01100001
  • n = 110 = 01101110
    将它们连成一串 24 bit 的数据流:
    010011010110000101101110

第二步:按 6 bit 一组进行拆分

我们将上面的 24 bit 拆分为 4 组:

  1. 010011
  2. 010110
  3. 000101
  4. 101110

第三步:计算十进制值并查表

  1. 010011 = 19 查表得 T
  2. 010110 = 22 查表得 W
  3. 000101 = 5 查表得 F
  4. 101110 = 46 查表得 u
    结果: “Man” 的 Base64 编码结果为 TWFu

关于“=”号(Padding 补位)

你经常会看到 Base64 字符串的末尾有一个或两个等号 ==。这是为什么?
因为并不是所有数据都能刚好被 3 整除。

  • 如果剩 1 个字节(8 bit):需要补 4 bit 的 0 凑成 12 bit(2 个 6 bit 组)。结果会产生 2 个 Base64 字符,剩下 2 个位置用 = 填充。
  • 如果剩 2 个字节(16 bit):需要补 2 bit 的 0 凑成 18 bit(3 个 6 bit 组)。结果会产生 3 个 Base64 字符,剩下 1 个位置用 = 填充。
    例子:编码字母 “A”
  1. ASCII: 65 (01000001)
  2. 分组:
    • 第一组 6 bit: 010000 (16 -> Q)
    • 剩余 2 bit 01,后面补四个 0: 010000 (16 -> Q)
  3. 结果: QQ
  4. 填充: Base64 要求输出长度必须是 4 的倍数,所以补两个等号。
  5. 最终结果: QQ==

变异的Base64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//此题为0xGame 2025 Week4的题目

int __cdecl sub_191070(int a1, int a2, int i_1)
{
int result; // eax
char v4; // [esp+Ah] [ebp-56h]
int i; // [esp+Ch] [ebp-54h]
char n61_1; // [esp+12h] [ebp-4Eh]
char n61; // [esp+13h] [ebp-4Dh]
int v8; // [esp+14h] [ebp-4Ch]
int v9; // [esp+14h] [ebp-4Ch]
char ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_[68]; // [esp+18h] [ebp-48h] BYREF

v8 = 0;
strcpy(
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_,
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
for ( i = 0; i < i_1; i += 3 )
{
n61 = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_[(((int)*(unsigned __int8 *)(i + a1 + 1) >> 4)
| (unsigned __int8)(16 * *(_BYTE *)(i + a1))) & 0x3F];
if ( i + 1 >= i_1 )
n61 = 61;
else
v4 = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_[(((int)*(unsigned __int8 *)(i + a1 + 2) >> 6) | (unsigned __int8)(4 * *(_BYTE *)(i + a1 + 1))) & 0x3F];
if ( i + 2 >= i_1 )
n61_1 = 61;
else
n61_1 = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_[*(_BYTE *)(i + a1 + 2) & 0x3F];
*(_BYTE *)(v8 + a2) = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_[((int)*(unsigned __int8 *)(i + a1) >> 2) & 0x3F];
v9 = v8 + 1;
*(_BYTE *)(v9 + a2) = v4;
*(_BYTE *)(++v9 + a2) = n61;
*(_BYTE *)(++v9 + a2) = n61_1;
v8 = v9 + 1;
result = i + 3;
}
*(_BYTE *)(v8 + a2) = 0;
return result;
}
  • 这个代码看起来困难
  • 其实确实有点困难
  • 首先这个a2并非int类型,ida分析的只是==伪代码==,并非正式代码,这个是一个指针*(_BYTE *)(v8 + a2)这里就能看出来,v8明明是int类型的,*还为取值符,更能证明a2是一个指针
  • (i + a1) >> 2) & 0x3F这里计算的其实是第一个字符的前六个字节,算为第一部分
  • (i + a1 + 1) >> 4) | (unsigned __int8)(16 * *(_BYTE *)(i + a1))) & 0x3F这里计算的是第二部分,16 * 一个字节,就相当于左移4位(乘2的n次幂相当于左移n位)
  • (((int)*(unsigned __int8 *)(i + a1 + 2) >> 6) | (unsigned __int8)(4 * *(_BYTE *)(i + a1 + 1))) & 0x3F这里就是第三部分了
  • ((int)*(unsigned __int8 *)(i + a1) >> 2) & 0x3F这就是最后部分了,第四部分了
  • 最后就可以看出来第二部分和第三部分顺序错误了,找脚本改一下就好了
1
2
3
4
5
6
7
8
9
10
11
12
13
import base64

enc=[0x4D, 0x41, 0x50, 0x44, 0x63, 0x33, 0x47, 0x74, 0x50, 0x51, 0x33, 0x34, 0x76, 0x4D, 0x33, 0x70, 0x4E, 0x42, 0x62, 0x31, 0x50, 0x57, 0x47, 0x78, 0x63, 0x63, 0x48, 0x74, 0x76, 0x78, 0x48, 0x31, 0x4E, 0x56, 0x61, 0x39, 0x4A, 0x50, 0x54, 0x74, 0x61, 0x63, 0x53, 0x35, 0x37, 0x49, 0x69, 0x6C, 0x4E, 0x35, 0x75, 0x78, 0x4A, 0x53, 0x57, 0x35, 0x49, 0x77, 0x6D, 0x3D]
basedecoded=""
for i in range(0, len(enc),4):
basedecoded+=chr(enc[i])
basedecoded+=chr(enc[i+2])
basedecoded+=chr(enc[i+1])
basedecoded+=chr(enc[i+3])
print(basedecoded)
#MPADcG3tP3Q4v3MpNbB1PGWxcHctvHx1NaV9JTPtaSc57iIlNu5xJWS5Imw=
xorencoded=base64.b64decode(basedecoded)

关于本文

由 GuQing 撰写,采用 CC BY-NC 4.0 许可协议。

#Note_For_Reverse