压缩包套娃
附件:first.zip
solution
本题是个多层密码加密的题,主要考察sha256爆破
、RSA copper smith攻击
、古典密码
、CRC32爆破
。
0x01 first key
打开key.py
:
import hashlib
import os
s=os.urandom(8)
print s[0:5].encode('hex')
print hashlib.sha256(s).hexdigest()
#3fcb70c42b
#4c6e139bd07cc2c00c8d39995727b697efa4171a9e475d194e654a0f4b0c3285
#password:s.encode('hex')
是个sha256的简单爆破,提供的脚本如下:
import hashlib
a='4c6e139bd07cc2c00c8d39995727b697efa4171a9e475d194e654a0f4b0c3285'
b=0x3fcb70c42b
def num2str(num):
h=hex(num)[2:].replace("L","")
if len(h)%2!=0:
h='0'+h
return h.decode('hex')
for i in range(0x100):
for j in range(0x100):
for m in range(0x100):
tmp=num2str(b)+chr(i)+chr(j)+chr(m)
if hashlib.sha256(tmp).hexdigest()==a:
print tmp.encode('hex')
爆破得到password:
3fcb70c42b95bbdf
0x02 second key
根据key进入second.zip
后,得到的是一个thrid.zip
与key.sage
from Crypto.PublicKey import RSA
key=RSA.generate(1024)
print hex(key.n),hex(key.e)
#0x907c4c35ef8defb9b0a5bf8ba3f1ad5d0d12ba79cb2913e6ef149a7b62ade6b08fad9618650c3508d8357933f83d1984516af4a1f6236ed734095d88a1c987912bf911d0187184c5182344bfab2203feb24b1f4ef7a94dfa86e5dc68caeead4318e3c043c9b19e1726b27c7948b522f89b5f83f37ea66de0e1ea2af36a38406bL 0x10001
m='*************'
c=power_mod(m,key.e,key.n)
print hex(c)
#0x6bad8e8bc4abe06db93504d120eca56ce6637906d5e2ada9c0010ac706490dfe877e913657ac9105b85ee83ea42c5b6989f067dcb2bacd24daad6679d30f304cda8dcf30509ccf67528ce9373bf469353052042bf1d73ee18e61e7b5eae9914b1619a48fcf48e97f483e50719d346af17e60b4bfd2d9f6ba68a44838db707c1dL
print hex((key.p>>200)<<200)
#0xb6b5afc607cdfb5b103b49b3f83a4fdca35ecf74e259a31b2c22898fe55aaea1ae88ad4be1d6b900000000000000000000000000000000000000000000000000L
#密码就在m中:)
从题目中我们可以知道:这是个RSA因子高位已知攻击。尝试copper smith攻击,sage脚本如下:
# -*- coding: utf-8 -*-
import time
from sage import *
# display matrix picture with 0 and X
def matrix_overview(BB, bound):
for ii in range(BB.dimensions()[0]):
a = ('%02d ' % ii)
for jj in range(BB.dimensions()[1]):
a += '0' if BB[ii,jj] == 0 else 'X'
a += ' '
if BB[ii, ii] >= bound:
a += '~'
print a
def coppersmith_howgrave_univariate(pol, modulus, beta, mm, tt, XX):
"""
Coppersmith revisited by Howgrave-Graham
finds a solution if:
* b|modulus, b >= modulus^beta , 0 < beta <= 1
* |x| < XX
"""
#
# init
#
dd = pol.degree()
nn = dd * mm + tt
#
# checks
#
if not 0 < beta <= 1:
raise ValueError("beta should belongs in (0, 1]")
if not pol.is_monic():
raise ArithmeticError("Polynomial must be monic.")
#
# calculate bounds and display them
#
# Coppersmith revisited algo for univariate
#
# change ring of pol and x
polZ = pol.change_ring(ZZ)
x = polZ.parent().gen()
# compute polynomials
gg = []
for ii in range(mm):
for jj in range(dd):
gg.append((x * XX)**jj * modulus**(mm - ii) * polZ(x * XX)**ii)
for ii in range(tt):
gg.append((x * XX)**ii * polZ(x * XX)**mm)
# construct lattice B
BB = Matrix(ZZ, nn)
for ii in range(nn):
for jj in range(ii+1):
BB[ii, jj] = gg[ii][jj]
# display basis matrix
if debug:
matrix_overview(BB, modulus^mm)
# LLL
BB = BB.LLL()
# transform shortest vector in polynomial
new_pol = 0
for ii in range(nn):
new_pol += x**ii * BB[0, ii] / XX**ii
# factor polynomial
potential_roots = new_pol.roots()
print "potential roots:", potential_roots
return potential_roots
length_N = 1024
N = 0x907c4c35ef8defb9b0a5bf8ba3f1ad5d0d12ba79cb2913e6ef149a7b62ade6b08fad9618650c3508d8357933f83d1984516af4a1f6236ed734095d88a1c987912bf911d0187184c5182344bfab2203feb24b1f4ef7a94dfa86e5dc68caeead4318e3c043c9b19e1726b27c7948b522f89b5f83f37ea66de0e1ea2af36a38406bL
qbar =0xb6b5afc607cdfb5b103b49b3f83a4fdca35ecf74e259a31b2c22898fe55aaea1ae88ad4be1d6b900000000000000000000000000000000000000000000000000L
F.<x> = PolynomialRing(Zmod(N), implementation='NTL');
pol = x - qbar
dd = pol.degree()
beta = 0.5 # q >= N^beta
epsilon = beta / 7 # <= beta/7
mm = ceil(beta**2 / (dd * epsilon)) # optimized
tt = floor(dd * mm * ((1/beta) - 1)) # optimized
XX = ceil(N**((beta**2/dd) - epsilon)) # |diff| < X
# Coppersmith
start_time = time.time()
roots = coppersmith_howgrave_univariate(pol, N, beta, mm, tt, XX)
c=0x6bad8e8bc4abe06db93504d120eca56ce6637906d5e2ada9c0010ac706490dfe877e913657ac9105b85ee83ea42c5b6989f067dcb2bacd24daad6679d30f304cda8dcf30509ccf67528ce9373bf469353052042bf1d73ee18e61e7b5eae9914b1619a48fcf48e97f483e50719d346af17e60b4bfd2d9f6ba68a44838db707c1d
q=qbar-roots[0][0]
p=N/q
e=0x10001
d=inverse_mod(e,(p-1)*(q-1))
m=power_mod(c,d,N)
print '{:x}'.format(int(m)).decode('hex')
得到结果:
So,the key is h3ll0
0x03 thrid key
根据key打开third.zip
后,发现有个fourth.zip
和key.py
。
import sys
alphaL = "abcdefghijklnmopqrstuvwxyz"
alphaU = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
num = "0123456789"
keychars = num+alphaL+alphaU
if len(sys.argv) != 3:
print("Usage: %s SECRET_KEY PLAINTEXT"%(sys.argv[0]))
sys.exit()
key = sys.argv[1].upper()
if not key.isalnum():
print("Your key is invalid, it may only be alphanumeric characters")
sys.exit()
plaintext = sys.argv[2]
ciphertext=""
for i in range(len(plaintext)):
rotate_amount = keychars.index(key[i%len(key)])
if plaintext in alphaL:
enc_char = ord('a') + (3*(ord(plaintext)-ord('a'))+2*rotate_amount)%26
elif plaintext in alphaU:
enc_char = ord('A') + (5*(ord(plaintext)-ord('A'))+rotate_amount)%26
elif plaintext in num:
enc_char = ord('0') + (7*(ord(plaintext)-ord('0'))+3*rotate_amount)%10
else:
enc_char = ord(plaintext)
ciphertext = ciphertext + chr(enc_char)
print("Encryption complete, ENC(%s,%s) = %s"%(plaintext,key,ciphertext))
# Encryption complete, ENC(ciphertext) = nxos ge kmqb dizh uic:d4k_4y6, cdz nxc jutv rgpv uk: FFB
# key hint:h3ll0长得像哪个单词呢?
leet的hello
可以写成h3ll0
,而且h3ll0
很像hello
。因此密钥为hello。
从中可以看出,key并没有很大程度影响enc_char内容,因此可以当常数看待,变成简单的Affine密码问题,解密脚本如下:
import gmpy2
alphaL = "abcdefghijklnmopqrstuvwxyz"
alphaU = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
num = "0123456789"
keychars = num+alphaL+alphaU
key="hello"
enc_text="nxos ge kmqb dizh uic:d4k_4y6, cdz nxc jutv rgpv uk: FFB"
plaintext=""
i1=gmpy2.invert(3,26)
i2=gmpy2.invert(5,26)
i3=gmpy2.invert(7,10)
for i in range(len(enc_text)):
rotate_amount = keychars.index(key[i%len(key)])
if enc_text in alphaL:
char = ord('a') + (i1*((ord(enc_text)-ord('a'))-2*rotate_amount))%26
elif enc_text in alphaU:
char = ord('A') + (i2*((ord(enc_text)-ord('A'))-rotate_amount))%26
elif enc_text in num:
char = ord('0') + (i3*((ord(enc_text)-ord('0'))-3*rotate_amount))%10
else:
char = ord(enc_text)
plaintext = plaintext + chr(char)
print(plaintext)
得到结果:
this is your next key:n3w_6u9, and the next hint is: CRC
0x04 fourth key
根据key进入fourth.zip
,发现有个flag.zip
,打得开flag.zip
,但是解压要密码。又有提示:CRC,因此采取CRC32爆破。但是爆破一个是5bytes,一个是6bytes。因此我们分别用两个脚本爆破。
# -*- coding: utf-8 -*-
# 5bytes CRC32
import itertools
import binascii
import 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 '======='
得到合适的结果:
6u9ku
#!/usr/bin/env python
# CRC32 tools by Victor 6bytes
import argparse
import os
import 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
得到合适的结果:
_w0rld
因此组合一起就出来key:
6u9ku_w0rld
0x05 decode
解压得到flag.txt
文件内容:
flag? 就在这里哟
4d5a5747435a33334f35535443595a514e555a56363542514c35525445364c514f525858323d3d3d
先hex解码,得到:
MZWGCZ33O5STCYZQNUZV65BQL5RTE6LQORXX2===
显然这是base64的编码,因此尝试各种base家族解码,最终发现为base32。
得到flag:
flag{we1c0m3_t0_c2ypto}