耐 不 住 寂 寞 的 Hacker
都 关 注 了 这 个 公 众 号
这是最后一部分,有关 Crypto & MISC 的
Crypto 篇
Magic
本题依据原理为Hill密码。magic使用希尔密码对明文字符串加密,获得密文。加密的秘钥是一个有限域GF(2)中的矩阵M,设明文为向量p,则加密后得到的密文向量为c=Mp。出题过程依据的便是该公式。若已知c,若要求p,则在两边同时乘以M的逆矩阵M^(-1),便得到p=M^(-1) c。下面的解题代码中先从magic.txt文件中读取矩阵M,将其转换成0、1矩阵的形式,再利用SageMath求解M的逆矩阵(SageMath脚本略),之后乘以向量c得到明文向量。代码如下
def getCipher():
with open("cipher.txt") as f:
s = f.readline().strip()
s = int(s, 16)
return s
def getMagic():
magic = []
with open("magic.txt") as f:
while True:
line = f.readline()
if (line):
line = int(line, 16)
magic.append(line)
# print bin(line)[2:]
else:
break
return magic
def magic2Matrix(magic):
matrix = ""
for i in range(len(magic)):
t = magic[i]
row = ""
for j in range(len(magic)):
element = t & 1
row = ", " + str(element) + row
t = t >> 1
row = "[" + row[2:] + "]"
matrix = matrix + row + ",\n"
matrix = "[" + matrix[:-1] + "]"
with open("matrix.txt", "w") as f:
f.write(matrix)
def prepare():
magic = getMagic()
magic2Matrix(magic)
cipher = getCipher()
cipherVector = ""
for i in range(len(magic)):
element = cipher & 1
cipherVector = ", " + str(element) + cipherVector
cipher = cipher >> 1
cipherVector = "[" + cipherVector[2:] + "]"
with open("cVector.txt", "w") as f:
f.write(cipherVector)
def trans2Flag():
#此处的向量v由SageMath计算得来
v = [0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1,
0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1,
0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1,
0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1,
0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0,
0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1,
0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1]
flag = 0
for i in range(len(v)):
flag = flag << 1
flag = flag ^ v[i]
flag = hex(flag)[2 : -1]
flag = flag.decode("hex")
print flag
if __name__ == "__main__":
prepare()#该步骤用于从magic中读取矩阵M,写入到matrix.txt中,之后到SageMath中计算
trans2Flag()#将明文向量转换成flag字符串
Pass
本题依据的原理是SRP(Security Remote Password)的一个缺陷。SRP的基本原理如下,客户端计算SCarol = (B − kgx)(a + ux) = (kv + gb − kgx)(a + ux) = (kgx − kgx + gb)(a + ux) = (gb)(a + ux);服务器端计算SSteve = (Avu)b = (gavu)b = [ga(gx)u]b = (ga + ux)b = (gb)(a + ux),之后分别计算S的Hash值K,计算K||salt的hash值h。双方最后通过验证h是否一致来实现password验证和身份认证,本质上是Diffie-Hellman秘钥交换的一种演变,都是利用离散对数计算复杂度高实现的密码机制。该缺陷在于若客户端将A强行设置为0或者N的整数倍,那么服务器端计算得到的S SSteve 必为0,此时客户端再将本地的S强行设置为0,便可以得到与服务器端相同的S,进而得到相同的K和相同的h,进而通过服务器端的password验证。在本题的设计中,一旦通过服务器端验证,服务器会发送本题的flag。解题代码如下。
-*- coding: UTF-8 -*-
# 文件名:client.py
#恶意攻击者,将A设置为0、N或者其他N的倍数,导致服务器端计算S时得到的值一定是0;攻击者进一步将自己的S值也设置为0,
import socket
import gmpy2 as gm
import hashlib
import agree
def main():
s = socket.socket()
s.connect(("game.suctf.asuri.org", 10002))
print "connecting..."
#计算a,向服务器端发送I和A,相当于发起认证请求
a = gm.mpz_rrandomb(agree.seed, 20)
A = gm.powmod(agree.g, a, agree.N)
A = 0 #此处为攻击第一步
print "A:", A
message = agree.I + "," + str(A).encode("base_64").replace("\n", "") + "\n"
s.send(message)
# 等待接收salt和B,salt稍后用于和password一起生成x,若client口令正确,则生成的x和服务器端的x一致
message = s.recv(1024)
print message
message = message.split(",")
salt = int(message[0].decode("base_64"))
B = int(message[1].decode("base_64"))
print "received salt and B"
print "salt:", salt
print "B:", B
# 此时服务器端和客户端都已掌握了A和B,利用A和B计算u
uH = hashlib.sha256(str(A) + str(B)).hexdigest()
u = int(uH, 16)
print "利用A、B计算得到u", u
# 开始计算通信秘钥K
# 利用自己的password和服务器端发来的salt计算x,如果passowrd与服务器端的一致,则计算出的x也是一致的
# xH = hashlib.sha256(str(salt) + agree.P).hexdigest()
wrongPassword = "test"
xH = hashlib.sha256(str(salt) + "wrong_password").hexdigest()
x = int(xH, 16)
print "x:", x
#客户端公式:S = (B - k * g**x)**(a + u * x) % N
#服务器端公式:S = (A * v**u) ** b % N
S = B - agree.k * gm.powmod(agree.g, x, agree.N)#此值应当与g**b一致
S = gm.powmod(S, (a + u*x), agree.N)
S = 0 #此处为攻击第二步
K = hashlib.sha256(str(S)).hexdigest()
print "K:", K
#最后一步,发送验证信息HMAC-SHA256(K, salt),如果得到服务器验证,则会收到确认信息
hmac = hashlib.sha256(K + str(salt)).hexdigest() + "\n"
s.send(hmac)
print "send:", hmac
print "receive:", s.recv(1024)
message = s.recv(1024)
print message
s.close()
if __name__ == "__main__":
main()
Enjoy
本题依据原理为针对IV=Key的CBC模式选择密文攻击,前提是明文泄露。enjoy.py使用AES、CBC模式对明文加密后发送到服务器端,且将CBC模式的初始化向量IV设置为AES的秘钥,而秘钥正是flag。随意设置一秘钥加密明文发往服务器端,服务器很容易泄露对应明文。因此选择三个分组的密文C||0||C发往服务器获得泄露出的明文p_1 ||p_2 ||p_3,因此根据CBC模式的加解密原理有:
IV⊕D(K,C)=p_1 (1)
0⊕D(K,C)=p_3 (2)
其中D为AES解密算法,两式做异或得:IV=p_1⊕p_3。
解题代码如下:
#coding: UTF-8
import socket
import flag
from Crypto.Cipher import AES
def padding(message):
toPadByte = 16 - len(message) % 16
paddedMessage = message + chr(toPadByte) * toPadByte
return paddedMessage
def encrypt(plain):
key = flag.flag[5:-1]
assert len(key) == 16
iv = key
plain = padding(plain)
aes = AES.new(key, AES.MODE_CBC, iv)
cipher = aes.encrypt(plain)
cipher = cipher.encode("base_64")
return cipher
def runTheClient(cipher):
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
host = "registry.asuri.org"
port = 10003
# plain = "blablabla_" +
"I_enjoy_cryptography" + "_blablabla"
# cipher = encrypt(plain)message =
s.recv(1024)
s.connect((host, port))
# message = s.recv(1024)
# print message
s.send(cipher)
message = s.recv(1024)
print message
s.close()
return message
def crack():
block = "A" * 16
cipher = block + "\x00"*16 + block
cipher = cipher.encode("base_64") + "\n"
message = runTheClient(cipher)
if "high ASCII" in message:
begin = message.find(":")
plain = message[begin+1:].strip().decode("base_64")
block1 = plain[:16]
block3 = plain[32:48]
key = ""
for i in range(16):
key = key + chr(ord(block1[i]) ^ ord(block3[i]))
print "key", key
if __name__ == "__main__":
crack()
Rsa
求逆元
from Crypto.Random import random
import binascii
import hashlib
from binascii import *
def invmod(a, n):
t = 0
new_t = 1
r = n
new_r = a
while new_r != 0:
q = r // new_r
(t, new_t) = (new_t, t - q * new_t)
(r, new_r) = (new_r, r - q * new_r)
if r > 1:
raise Exception('unexpected')
if t < 0:
t += n
return t
def b2n(s):
return int.from_bytes(s, byteorder='big')
def n2b(k):
return k.to_bytes((k.bit_length() + 7) // 8, byteorder='big')
def debytes(n,d,cbytes):
c = b2n(cbytes)
m = pow(c, d, n)
return n2b(m)
if __name__ == '__main__':
n = 66149493853860125655150678752885836472715520549317267741824354889440460566691154181636718588153443015417215213251189974308428954171272424064509738848419271456903929717740317926997980290509229295248854525731680211522487069759263212622412183077554313970550489432550306334816699481767522615564029948983958568137620658877310430228751724173392407096452402130591891085563316308684064273945573863484366971922314948362237647033045688312629960213147916734376716527936706960022935808934003360529947191458592952573768999508441911956808173380895703456745350452416319736699139180410176783788574649448360069042777614429267146945551
e = 3
d = 44099662569240083770100452501923890981810347032878178494549569926293640377794102787757812392102295343611476808834126649538952636114181616043006492565612847637935953145160211951331986860339486196832569683821120141014991379839508808414941455385036209313700326288366870889877799654511681743709353299322639045424737161223404842883211346043467541833205836604553399746326181139106884008412679110817142624390168364685584282908134947826592906891361640349523847551416712367526240125746834000852838264832774661329773724115660989856782878284849614002221996848649738605272015463464761741155635215695838441165137785286974315511355
c2 = 44072159524363345025395860514193439618850855989758877019251604535424645173015578445641737155410124722089855034524900974899143590319109150794463017988146330700682402644722045151564192212786022295270147246354021288864468319458821200111865992881657865302651297307278194354152154089398262689939864900434490148230032752585607483545643297707980226837109082596681204037909705850077064452350740011904984407745294229799642805761872912116003683053767810208214723900549369485228083610800628462169538658223452866042552036179759904943895834603686937581017818440377415869062864539021787490351747089653244541577383430879642738738253
r =
45190871623538944093785281221851226180318696177837272787303375892782101654769663373321786970252485047721399081424329576744995348535617043929235745038926187396763459008615146009836751084746961130136655078581684659910694290564871708049474081354336784708387445467688447764440168942335060081663025621606012816840929949463114413777617148271738737393997848713788551935944366549647216153686444107844148988979274170780431747264142309111561515570105997844879370642204474047548042439569602410985090283342829596708258426959209412220871489848753600755629841006861740913336549583365243419009944724130194890707627810912114268824770
cipher2 = n2b(c2)
plaintext3 = debytes(n,d,cipher2)
print (plaintext3)
p3 = b2n(plaintext3)
p4 = (p3 * invmod(r, n)) % n
plaintext4 = n2b(p4)
print (plaintext4)
Asa good
CCA Attack
已知 pow(flag, e, n),解密 pow(flag, e, n) * pow(2, e, n) % n,获得 pow(2 * flag, e, n) 的明文 2 * flag 即可
这题脚本有问题,每次交互的n都是一样的,而且最重要的是n有问题,能被分解,p还是个7 emmmmm。。。。。。。
MISC 篇
Game
中国剩余定理
import gmpy2 as gm
def crack():
holes = [257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373]
remains = []
with open("sand.txt") as f:
line = f.readline().strip()
while line:
remains.append(int(line))
line = f.readline().strip()
M = gm.mpz(1)
for hole in holes:
M = M * hole
m = []
m_inv = []
for i in range(len(holes)):
m.append(M / holes[i])
m_inv.append(gm.invert(m[i], holes[i]))
re = gm.mpz(0)
for i in range(len(holes)):
re = re + m[i] * m_inv[i] * remains[i]
re = gm.f_mod(re, M)
print "flag{" + hex(re)[2:].decode("hex") + "}"
if __name__ == "__main__":
crack()
Cycle
本题依据原理为Vernam密码。cycle.py通过循环使用秘钥字符串(flag)对明文进行异或操作,得到密文。该种加密方式类似于Vernam密码。cycle.py中已经告知秘钥字符串长度不超过50,且明文为英文,那么通过遍历秘钥长度,在不同秘钥长度下分别遍历单个字节的秘钥,通过统计明文中出现的英文字符出现情况即可发现最佳秘钥长度和最佳秘钥。例如,若秘钥长度为L,则先分别取密文第0,L-1,2L-1,…个字符,遍历256单字节秘钥,取明文最可能是英文的那个秘钥;然后取密文1,L,2L,…个字符,以同样方式获得秘钥第二个字节。按此方法,破解长度为L的复杂度为256L,而将L遍历1到n=50的总复杂度为O(n^2)。破解代码如下。
# --encoding=utf-8
def scoreChar(str):
chars = "abcdefghijklmnopqrstuvwxyz"
chars += chars.upper()
chars += " \n,."
count = 0
for i in range(len(str)):
if str[i] in chars:
count += 1
return count
def decrypt(c, key):
m = ""
for i in range(len(c)):
byte = ord(c[i])
byte = byte ^ key
m = m + chr(byte)
return m
def crack(cs):
re = {}
# print "cracking ", cs.encode("hex")
bestS = ""
bestScore = -100000000
bestKey = ''
for key in range(0x100):
s = decrypt(cs, key)#cs为字节数组
score = scoreChar(s)
if (score > bestScore):
bestS = s
bestScore = score
bestKey = chr(key)
return [bestS, bestKey, bestScore]
def getCipherBytesFromFile(file):
cipherText = ""
with open(file) as f:
line = f.readline().strip()
while(len(line) > 1):
cipherText += line
line = f.readline().strip()
cipherText = cipherText.decode("base_64")
return cipherText
def crackKeyWithSize(keySize,
cipherText):
cs = [""] * keySize
ms = [""] * keySize
key = ""
totalScore = 0;
for i in range(len(cipherText)):
cs[i % keySize] += cipherText[i]
for i in range(keySize):
[ms[i], k, score] = crack(cs[i])
totalScore += score
key += k
m = ""
for i in range(0, len(cipherText)):
m += ms[i % keySize][i / keySize]
return [m, key, totalScore]
def decryptWithKey(key):
cipherText = getCipherBytesFromFile("data6")
m = ""
keyBytes = []
for i in range(len(key)):
keyBytes.append(ord(key[i]))
for i in range(len(cipherText)):
byte = ord(cipherText[i]) ^ keyBytes[i % len(key)]
m += chr(byte)
return m
def main():
cipherText = getCipherBytesFromFile("cipher.txt")
bestScore = -10000
bestKey = bestM = ""
for size in range(1, 50):
[m, key, score] = crackKeyWithSize(size, cipherText)
if score > bestScore:
bestScore = score
bestKey = key
bestM = m
print bestKey
print bestScore
print bestM
if __name__ == "__main__":
main()
TNT
step 1
1.流量分析,发现全是SQLMAP的流量,注入方式为时间盲注。首先写代码把所有的请求URL提取出来(或者用wireshark打开pcpa文件-导出分组解析结果-为csv)
step 2
Sqlmap在时间盲注时,最后会使用!=确认这个值,所以可以提取出所有带有!=的URL,提取出后面的ascii码,使用chr(数字)将其转换为字符
step 3
打印出来,会发现一串BASE64,BASE64有点奇怪,反复分析,发现没有大写X,多了符号.,当然是把点替换成X啦.解码,保存为文件(可以使用Python,记得写入的时候使用open(file,'wb') 二进制写入模式。)
step 4
文件头为BZ,应该是个bzip文件,把扩展名改成bz2,winrar解压(或者直接在linux下用bunzip2解压).file1文件头为xz的文件头,使用xz解压,里面又是一个gz格式的file1,再解压一次,得到33.333文件,文件尾部有Pk字样,说明是zip,二进制编辑器改文件头为zip文件头,解压.改文件头为rar,解压得到flag.
import urllib
import sys
import re
import base64
f=open('''exm1.pcap''','rb').read()
pattern=re.compile('''GET /vulnerabilities/sqli_blind/.+HTTP/1\.1''')
lines=pattern.findall(f)
a=0
for line in lines:
raw_line=urllib.unquote(line)
i=raw_line.find("!=")
if i>0:
a=0
asc=raw_line[i+2:]
asc=asc[:asc.find(')')]
sys.stdout.write(chr(int(asc)))
else:
a+=1
if a>10:
sys.stdout.write('\n')
a=0
str='QlpoOTFBWSZTWRCesQgAAKZ///3ry/u5q9q1yYom/PfvRr7v2txL3N2uWv/aqTf7ep/usAD7MY6NHpAZAAGhoMjJo0GjIyaGgDTIyGajTI0HqAAGTQZGTBDaTagbUNppkIEGQaZGjIGmgMgMjIyAaAPU9RpoMjAjBMEMho0NMAjQ00eo9QZNGENDI0zUKqflEbU0YhoADQDAgAaaGmmgwgMTE0AGgAyNMgDIGmTQA0aNGg0HtQQQSBQSMMfFihJBAKBinB4QdSNniv9nVzZlKSQKwidKifheV8cQzLBQswEuxxW9HpngiatmLK6IRSgvQZhuuNgAu/TaDa5khJv09sIVeJ/mhAFZbQW9FDkCFh0U2EI5aodd1J3WTCQrdHarQ/Nx51JAx1b/A9rucDTtN7Nnn8zPfiBdniE1UAzIZn0L1L90ATgJjogOUtiR77tVC3EVA1LJ0Ng2skZVCAt+Sv17EiHQMFt6u8cKsfMu/JaFFRtwudUYYo9OHGLvLxgN/Sr/bhQITPglJ9MvCIqIJS0/BBxpz3gxI2bArd8gnF+IbeQQM3c1.M+FZ+E64l1ccYFRa26TC6uGQ0HnstY5/yc+nAP8Rfsim4xoEiNEEZclCsLAILkjnz6BjVshxBdyRThQkBCesQg='.replace('.','X')
print ''
print str
fw=open('file1.bz2','wb')
fw.write(base64.b64decode(str))
fw.close()
Game
出题人前一段时间沉迷ACM无法自拔,觉得博弈论实在是太有意思了,又觉得作为一名优秀的选手,掌握这些优秀的算法是非常基础的(x,于是就出了这个题。
用到的三个博弈分别为Bash game, Wythoff game 和 Nim game。具体的推导和结论么,都给你名字了还不去查维基百科(x
解题脚本:
from pwn import *
import math
import hashlib
import string
p = remote('game.suctf.asuri.org', 10000)
p.recvuntil('Prove your heart!\n')
def proof(key, h):
c = string.letters+string.digits
for x0 in c:
for x1 in c:
for x2 in c:
for x3 in c:
if (hashlib.sha256(key + x0 + x1 + x2 + x3).hexdigest() == h):
return x0 + x1 + x2 + x3
p.recvuntil('sha256(')
key = p.recv(12)
p.recvuntil('== ')
h = p.recvline().strip()
print key, h
s = proof(key, h)
print s
p.sendline(s)
p.recvuntil('Let\'s pick stones!')
for i in xrange(20):
p.recvuntil('===========================================================================')
p.recvuntil('There are ')
n = int(p.recvuntil('stones')[:-6])
p.recvuntil(' - ')
x = int(p.recvuntil('once')[:-4])
print n, x
if (n % (x + 1) == 0):
p.sendline('GG')
continue
else:
p.sendline(str(n % (x + 1)))
n -= n % (x + 1)
while(n > 0):
p.recvuntil('I pick ')
g = int(p.recvuntil('!')[:-1])
p.sendline(str(x + 1 - g))
n -= x + 1
print "level 1 pass"
p.recvuntil('You have 8 chances to input \'GG\' to skip this round.')
for i in xrange(20):
p.recvuntil('===========================================================================')
a = 99999
b = 99999
while (a != 0 and b != 0):
p.recvuntil('Piles: ')
g = p.recvline().strip().split(' ')
a, b = int(g[0]), int(g[1])
print a, b
if (a == 0):
p.sendline("%d 1" % b)
break
if (b == 0):
p.sendline("%d 0" % a)
break
if (a == b):
p.sendline("%d 2" % a)
break
z = abs(a - b)
x = min(a, b)
y = max(a, b)
maxd = int(z * (1 + math.sqrt(5)) / 2)
if (maxd < x):
l = [x - maxd, 2]
elif (maxd > x):
t = 1
while True:
g = int(t * (1 + math.sqrt(5)) / 2)
if (g in (a, b) or (g + t) in (a, b)):
break
t = t + 1
if (g == a and g + t == b):
p.sendline('GG')
print "GG"
break
if (g == a):
l = [b - (g + t), 1]
if (g == b):
l = [a - (g + t), 0]
if (g + t == a):
l = [b - g, 1]
if (g + t == b):
l = [a - g, 0]
else:
p.sendline('GG')
print "GG"
break
if (l[1] == 0 or l[1] == 2):
a -= l[0]
if (l[1] == 1 or l[1] == 2):
b -= l[0]
p.sendline("%d %d" % (l[0], l[1]))
print "level2 pass"
def xxor(l):
r = 0
for i in l:
r ^= i
return r
p.recvuntil('Last one is winner. You have 5 chances to skip.')
for i in xrange(20):
print p.recvuntil('===========================================================================')
r = [99999] * 5
while (sum(r) != 0):
p.recvuntil('Piles: ')
r = p.recvline()
#print r
r = map(int, r.strip().split(' '))
print r
xor = 0
for j in xrange(5):
xor ^= r[j]
if (xor == 0):
p.sendline('GG')
print "GG"
break
else:
for mx in xrange(5):
for d in xrange(r[mx] + 1):
l = list(r)
l[mx] -= d
if (xxor(l) == 0):
q = [d, mx]
break
p.sendline("%d %d" % (q[0], q[1]))
r[q[1]] -= q[0]
print "level3 pass"
p.interactive()
// 电脑的策略和这个策略是一样的(也没其他策略啊
打下来得到鬼畜的
FlagSUCTF{gGGGGggGgGggGGggGGGggGgGgggGGGGGggggggGgGggggGg}
Padding的秘密
step 1
下载附件,修改secret后缀为zip,发现有.git。
老招数了,通过git回溯版本可以拿到源码SUcrypto.py和key.jpg
step 2
分析源码(后为hint1)可知,为one-time-pad(一次性密码本加密)相关漏洞。
一次性密码本多次使用后将存在泄露风险。
即我们可以通过词频分析(工具请自行上gayhub搜索),获得脚本中的密钥key,和所有的tips(nc上的2选项templates)
此处省略漫长的分析过程。。。。。。
step 3
获得了密钥key:“FL4G is SUCTF{This_is_the_fake_f14g},guys”后,通过nc提交得到新的hint:“嘤嘤嘤,flag不在这里,人家说secret里有、东西”
这里有大师傅在做题过程中提示会有非预期解,是本人的疏忽,深表歉意
回到secret压缩包里有winrar注释,一大长串的padding串。转ascii后发现有09、20、0D0A三种字符。结合新hint:有“.”东西,可想到带‘.’的加解密中,最容易想到的摩斯电码。
1.09 -> .2.20 -> -3.0D0A -> 空格
摩斯电码解密 再hex一下会得到缺了一部分的flag。
结合key.jpg即可获得flag。
签到题
base32编码,直接解码得到Flag