DeepDarkFantasy
题目描述和hint如下
原始附件如下:
链接:https://pan.baidu.com/s/1Rs1A6viKUXuNvxExAvtvSQ
提取码:lqln
下载后得到一个pth文件,根据第一个提示,其被以简单异或的方式加密,遍历得到解密比特为0xde,此时可以发现PK头
解密后的文件如下:
链接:https://pan.baidu.com/s/1fTiS7DzQP_Rpv_fkB5CtbQ
提取码:7zel
由于找不到pytorch保存模型字典对象的方便的解析脚本,使用pycharm进行动态调试的方法对模型结构进行推测
这个报错是由于保存路径时文件名为model导致的,但我默认为main,修改文件名(这个坑了我俩小时)
提示需要一个MyAutoEncoder类,我们直接将myclass名字改为MyAutoEncoder
同样的,提示需要Encoder和Decoder类,一样给加上
当然我们也可以使用Netron这个软件来发现需要这两个类
补上Decoder和Encoder后,代码如下:
import torch
from torch.nn import *
class Encoder(Module):
def __init__(self):
super().__init__()
pass
class Decoder(Module):
def __init__(self):
super().__init__()
pass
class MyAutoEncoder(Module):
def __init__(self):
super().__init__()
pass
path='./decrypted.pth'
mymodel=MyAutoEncoder()
mymodel.load_state_dict(torch.load(path,map_location=torch.device('cpu')))
此时的报错信息变成了
提示我们存在俩多出来的键,自然想到去看一看state_dict和model中的内容,这时我们利用pycharm的debug功能单步运行
这样我们就利用pytorch自带的解析器得到了整个网络的结构和所有参数,然后我们使用控制台将这些信息dump出来看一看细节
网络结构如下:
我们根据其网络结构构建需要的Autoencoder,并从题目给的解密后的文件中读取参数
import torch
from torch.nn import *
from torchvision import transforms
class Encoder(Module):
def __init__(self):
super().__init__()
self.conv = Sequential(
Conv2d(1, 16, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)),
ReLU(),
MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False),
Conv2d(16, 8, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)),
MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False),
ReLU(),
Conv2d(8, 8, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)),
MaxPool2d(kernel_size=2, stride=2, padding=1, dilation=1, ceil_mode=False),
ReLU(),
MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False),
Flatten(start_dim=1, end_dim=-1)
)
self.fc=Linear(in_features=32, out_features=16, bias=True)
def forward(self, x):
x=self.conv(x)
x=self.fc(x)
return x
class Decoder(Module):
def __init__(self):
super().__init__()
self.convt=Sequential(
ConvTranspose2d(1, 256, kernel_size=(1, 1), stride=(1, 1)),
ReLU(),
ConvTranspose2d(256, 256, kernel_size=(1, 1), stride=(1, 1)),
ReLU(),
ConvTranspose2d(256, 512, kernel_size=(1, 1), stride=(1, 1)),
ReLU(),
ConvTranspose2d(512, 128, kernel_size=(4, 4), stride=(4, 4)),
ReLU(),
ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(4, 4)),
ReLU(),
ConvTranspose2d(64, 32, kernel_size=(2, 2), stride=(2, 2)),
ReLU(),
ConvTranspose2d(32, 1, kernel_size=(2, 2), stride=(2, 2)),
Sigmoid()
)
def forward(self, x):
x = self.convt(x)
return x
class MyAutoEncoder(Module):
def __init__(self):
super().__init__()
self.encoder=Encoder()
self.decoder=Decoder()
def forward(self, x):
x=self.encoder.forward(x)
x=self.decoder.forward(x)
return x
path='./decrypted.pth'
mymodel=MyAutoEncoder()
mymodel.load_state_dict(torch.load(path,map_location=torch.device('cpu'))['state_dict'])
接下来让我们简单讲讲Autoencoder做了啥,参考知乎链接
https://zhuanlan.zhihu.com/p/123810703
简单来说,就是俩类似CNN的结构反着放,左边的是编码器,输入是图片输出是特征向量,用于提取能简洁地表示图片特征的向量。右边的是解码器,输入特征向量能生成图片。自动编码器可以进行降维,图像压缩,图像降噪,图像生成,特征提取等操作。本题中利用了其生成的图像和输入的图像相差无几的功能,通过控制中间的d的特征(如下图),还原出flag的不同部分
这题的出题思路估计是这样的:出题人把一张含有flag的图片分成很多份,之后padding后送入Autoencoder进行训练,这样使得解码器具有在给定特定特征的情况下生成出flag残片的功能,因此我们可以控制d的值来控制输出。
尝试生成图片结果如下:
可以看出生成的图片明显分成有分界线,尝试后找到输入tensor形状为[1,1,1,1]时恰好能生成一块残片,编写代码遍历d
mymodel.eval()
num=-40.0
for i in range(7000):
print(i,' ',i*0.01,' ',num)
mylist=[[[[num+0.01*i]]]]
print(mylist)
data=torch.FloatTensor(mylist)
#data = torch.randn(1, 1, 16, 16)
pics = mymodel.forward(data)[0]
# print(pics.shape)
toPIL = transforms.ToPILImage()
pic = toPIL(pics[0])
pic.save('./pic/random'+str(i)+'.jpg')
得到所有的flag片段,多次尝试后拼接得到正确结果为
L3HCTF{blackbinarysencoder0xb1a876a}
图片压缩包如下:
链接:https://pan.baidu.com/s/1B1jnNzvpi9P4_gChlh5ZXQ
提取码:bqfy
完整代码如下:
import torch
from torch.nn import *
from torchvision import transforms
class Encoder(Module):
def __init__(self):
super().__init__()
self.conv = Sequential(
Conv2d(1, 16, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)),
ReLU(),
MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False),
Conv2d(16, 8, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)),
MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False),
ReLU(),
Conv2d(8, 8, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)),
MaxPool2d(kernel_size=2, stride=2, padding=1, dilation=1, ceil_mode=False),
ReLU(),
MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False),
Flatten(start_dim=1, end_dim=-1)
)
self.fc=Linear(in_features=32, out_features=16, bias=True)
def forward(self, x):
x=self.conv(x)
x=self.fc(x)
return x
class Decoder(Module):
def __init__(self):
super().__init__()
self.convt=Sequential(
ConvTranspose2d(1, 256, kernel_size=(1, 1), stride=(1, 1)),
ReLU(),
ConvTranspose2d(256, 256, kernel_size=(1, 1), stride=(1, 1)),
ReLU(),
ConvTranspose2d(256, 512, kernel_size=(1, 1), stride=(1, 1)),
ReLU(),
ConvTranspose2d(512, 128, kernel_size=(4, 4), stride=(4, 4)),
ReLU(),
ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(4, 4)),
ReLU(),
ConvTranspose2d(64, 32, kernel_size=(2, 2), stride=(2, 2)),
ReLU(),
ConvTranspose2d(32, 1, kernel_size=(2, 2), stride=(2, 2)),
Sigmoid()
)
def forward(self, x):
x = self.convt(x)
return x
class MyAutoEncoder(Module):
def __init__(self):
super().__init__()
self.encoder=Encoder()
self.decoder=Decoder()
def forward(self, x):
#x=self.encoder.forward(x)
x=self.decoder.forward(x)
return x
path='./decrypted.pth'
mymodel=MyAutoEncoder()
mymodel.load_state_dict(torch.load(path,map_location=torch.device('cpu'))['state_dict'])
mymodel.eval()
num=-40.0
for i in range(7000):
mylist=[[[[num+0.01*i]]]]
data=torch.FloatTensor(mylist)
pics = mymodel.forward(data)[0]
toPIL = transforms.ToPILImage()
pic = toPIL(pics[0])
pic.save('./pic/random'+str(i)+'.jpg')