在 CTF 中 base64 出现的频率还是很高的,所以能 dump 出相应的 base64 索引表对解题是很有帮助的。
编码原理
维基百科:
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于 2^6 = 64,所以每6个比特为一个单元,对应某个可打印字符。3个字节有24个比特,对应于4个Base64单元,即3个字节可由4个可打印字符来表示。
简单来说就是将3个字节(24字),分成4组,每组6字;将这4组作为索引,在 base64 索引表中查询;最后得到编码后结果。具体过程可以参考下表。
+--------------------------------------------------------+
| text |M |a |n |
+--------------------------------------------------------+
| ascii |77 |97 |110 |
+--------------------------------------------------------+
| binary |0 1 0 0 1 1|0 1|0 1 1 0|0 0 0 1|0 1|1 0 1 1 1 0|
+--------------------------------------------------------+
| index |19 |22 |5 |46 |
+--------------------------------------------------------+
| encode |T |W |F |u |
+--------------------------------------------------------+
逆向思维
我们可以通过控制 index,然后将我们想输出的索引表对应索引组合成3个字节,进行 base64 编码,就可以得到有序的 base64 索引表。
例如我们有ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
这个索引表,我们想要输出索引表的前四位,于是取出前4位索引,每一位索引为6字大小,组合成3个字节(24字),就得到了待编码的字符串。对这个字符串进行编码就可以得到索引表的前四位。
+--------------------------------------------------------+
| table |A |B |C |D |
+--------------------------------------------------------+
| index |0 |1 |2 |3 |
+--------------------------------------------------------+
| binary |0 0 0 0 0 0|0 0|0 0 0 1|0 0 0 0|1 0|0 0 0 0 1 1|
+--------------------------------------------------------+
| hex | 0x00| 0x10| 0x83|
+--------------------------------------------------------+
我实现了如下 py 脚本来进行 dump 字符串的生成。脚本逻辑如上面所示。
walk = []
for i in range(0, 64, 4):
a = (i << 2) | ((i+1) >> 4)
b = (((i+1) & 0b1111) << 4) | ((i+2) >> 2)
c = (((i+2) & 0b11) << 6) | (i+3)
walk.append('x{:0>2x}x{:0>2x}x{:0>2x}'.format(a, b, c))
print(''.join(walk))
最后得到字符串x00x10x83x10x51x87x20x92x8bx30xd3x8fx41x14x93x51x55x97x61x96x9bx71xd7x9fx82x18xa3x92x59xa7xa2x9axabxb2xdbxafxc3x1cxb3xd3x5dxb7xe3x9exbbxf3xdfxbf
。这个字符串是由C语言中的转义字符组成,如果在调试器中使用的话可能需要通过二进制编辑写入缓冲区中。
需要注意的是字符串开头为x00
这可能会导致编码失败,所以我们可以将索引的位置进行变更,将索引0移到中间位置以消除x00
的出现。则之前字符串的前3位则变成x04x20x03
+--------------------------------------------------------+
| table |B |C |A |D |
+--------------------------------------------------------+
| index |1 |2 |0 |3 |
+--------------------------------------------------------+
| binary |0 0 0 0 0 1|0 0|0 0 1 0|0 0 0 0|0 0|0 0 0 0 1 1|
+--------------------------------------------------------+
| hex | 0x04| 0x20| 0x03|
+--------------------------------------------------------+
未完待续
上面的字符串甚至出现了不可见字符,这在逆向的时候只能进行特殊操作(例如二进制编辑内存)才能写入字符串,所以我试图通过较短的可见字符串来 dump base64 索引表,但是发现不能完全 dump 出整个索引表,而且还不知道怎么找到最短可见字符串...
from itertools import permutations
import string
it = permutations(string.ascii_letters, 3)
# product index list
dic = {}
for i in it:
pos0 = ord(i[0])
pos1 = ord(i[1])
pos2 = ord(i[2])
a = pos0 >> 2
b = pos0 & 0b11 | pos1 >> 4
c = pos1 & 0b1111 | pos2 >> 6
d = pos2 & 0b111111
lst = [a, b, c, d]
dic[i] = lst
# find position
resdic = {}
for i in range(64):
for j in dic.items():
if i in j[1]:
posi = j[1].index(i)
#print(f'[+] found {i} in {j[0]}: {j[1]} pos{posi}')
if posi not in resdic.keys():
resdic[posi] = [i]
else:
resdic[posi].append(i)
for i, j in resdic.items():
resdic[i] = list(set(j))
print(resdic)