2018 DDCTF 杂项 writeup

前言

  今年的DDCTF玩的非常充实,7天的时间里基本每天都在学习新东西,整个Writeup会按题目类型进行分类。

(╯°□°)╯︵ ┻━┻

  签到题就直接pass了,从第二道开始。题目就给出了一串16进制的字符:

1
d4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9b2b2e1e2b9b9b7b4e1b4b7e3e4b3b2b2e3e6b4b3e2b5b0b6b1b0e6e1e5e1b5fd

  一开始是想看看是不是什么文件的,但没有发现。




  而且观察发现按两位数转10进制的话全都超出了可见字符的ASCII码表,当然解题时也考虑过对每两个16进制进行^异或,但都不对。然后尝试对每两个字符转出来的10进制进行减128,因为这样每个值得范围就能落在ASCII码表。
1
2
3
4
5
6
7
8
9
import re
sss = 'd4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9b2b2e1e2b9b9b7b4e1b4b7e3e4b3b2b2e3e6b4b3e2b5b0b6b1b0e6e1e5e1b5fd'
nums = re.findall('\w{2}',sss)
flag = ''
for one in nums:
ch = chr(int(one,16) - 128)
flag += ch
print(flag)
# That was fast! The flag is: DDCTF{922ab9974a47cd322cf43b50610faea5}

第四扩展FS

  附件下下来发现很大,用winhex查看了一下,发现后面全是填充的,然后还有个压缩包,我们把它提取出来,但需要解压密码,然后解压密码可以在原图片的备注信息里找到。




  接着我们就能拿到一堆乱码的字符。




  因为题目描述中说到频次很重要,所以尝试使用在线的词频密码分析网站,但没有发现,所以考虑另一种可能,就是不用还原成句子,只需要里面的每个词的频次。然后写个脚本跑一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding:utf-8 -*-
with open ('file.txt','r') as f:
dd3 = f.read()

a = {}
for i in dd3:
if i not in a:
a[i]=1
else:
a[i] +=1

b = sorted(a.items(), lambda x, y: cmp(x[1], y[1]), reverse=True)
flag = ''
for i in b:
flag += i[0]
print(flag)

# DCTF{x1n9shaNgbIci}

流量分析

  下载后使用wireshark进行分析,流量分析题如果不掌握点小技巧,估计眼睛会看瞎。

  打开后直接拉到灰色报文块,如下图,然后一把TCP追踪流过去。






  然后逐个查看流,然后你会发现有两个压缩包,但后面发现那就是坑来的,反正我没用上。。。。在查看他们的邮件往来记录的时候有一张图片引起了我的注意:




  恢复过来后看起来比较奇怪,,因为以前也没搞过密码,所以一开始还真没注意到它的作用。




  接着再往下走,有一个地方引起了我的注意。




  TLSv1.2https上使用的,后面的传输数据都会被服务器的私钥进行加密,然后这时想起上面得到的那张图片,就联系起来了。百度了一下在wireshark里还原https数据的方法:传送门。按照这里的方法进行恢复,然后就能拿到flag了。




  PS一下:这里对我提取出来的文件进行md5,但都没有得到题目中给的那个值。。。

安全通信

  题目描述:

1
请通过nc 116.85.48.103 5002答题,mission key是f49348cf84d390da52498077ae7137d5,agent id随意填就可以

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env python
import sys
import json
from Crypto.Cipher import AES
from Crypto import Random


def get_padding(rawstr):
remainder = len(rawstr) % 16
if remainder != 0:
return '\x00' * (16 - remainder)
return ''


def aes_encrypt(key, plaintext):
plaintext += get_padding(plaintext)
aes = AES.new(key, AES.MODE_ECB)
cipher_text = aes.encrypt(plaintext).encode('hex')
return cipher_text


def generate_hello(key, name, flag):
message = "Connection for mission: {}, your mission's flag is: {}".format(name, flag)
return aes_encrypt(key, message)


def get_input():
return raw_input()


def print_output(message):
print(message)
sys.stdout.flush()


def handle():
print_output("Please enter mission key:")
mission_key = get_input().rstrip()

print_output("Please enter your Agent ID to secure communications:")
agentid = get_input().rstrip()
rnd = Random.new()
session_key = rnd.read(16)

flag = '<secret>'
print_output(generate_hello(session_key, agentid, flag))
while True:
print_output("Please send some messages to be encrypted, 'quit' to exit:")
msg = get_input().rstrip()
if msg == 'quit':
print_output("Bye!")
break
enc = aes_encrypt(session_key, msg)
print_output(enc)


if __name__ == "__main__":
handle()

  这道题考的是aes ecb的攻击方法,经过一通google后找到了一个比较有参考意义的writeup,传送门

  需要了解的是ECB分组加密的(一组16个字符),也就是一块一块的,这点是我们后续攻击的重要前提。




  然后我们看一下他这个脚本,经过分析后我们可以发现加密的消息的长度我们是可以控制的。




  而且对于一次连接,在同一密钥的情况下,我们可以随意加密任何数据任何次。




  所以攻击手段就很清晰了。
1
2
3
4
5
6
7
明文分组(16个字符)             对于的密文(对得到的一长串密文进行32位切割)
Connection for m ----> eae138090c7a60d97a6c54ce15fe7888 # 第1块
ission: 12345678 ----> aaaab01d2ba84861447153e790047db4 # 第2块
90123, your miss ----> d9d2a15efa5c6f75097d89f1e5cc629c # 第3块
ion's flag is: D ----> 8e16cf020a37e52a28fdee32d469c2b4 # 第4块
DCTF{afafjafj101 ----> 5ca9b0e48d1dec7bb06a73dd163380a0 # 第5块
.....

  如上所示,我们可以控制name的长度来使(在这种情况下)第4块的最后一个字符是flag中我们要求的第一个字符,而它明文的前15个字符我们是知道的,密文我们也知道,所以我们可以使用这样来暴力猜测出最后一位到底是什么。

1
2
3
4
5
6
7
# 伪代码
raw = 'ion's flag is: '
for ch in range(33,128):
tmp = raw + chr(ch)
my_miwen = encrypt(tmp)
if my_miwen == true_miwen:
print('要求的是:',tmp)

  所以只要我们设计好填充的长度就能将flag的所有字符都爆破出来。填充的时候有一个需要注意的地方就是块的推进(进位),因为你不能无限制退后,所以当我们用完填充块的大小后要更新需要爆破的块。代码如下:

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
41
42
43
44
45
46
47
48
49
50
51
52
# coding:utf-8
import socket # 导入 socket 模块
import re

sc = None
c = '-'
def sendAgent(agent):
global sc
sc = socket.socket() # 创建 socket 对象
host = "116.85.48.103" # 获取本地主机名
port = 5002 # 设置端口
addr = (host, port)
sc.connect(addr) # 绑定端口号
sc.recv(1024) # 打印接收的数据
sc.send('f49348cf84d390da52498077ae7137d5\n')
sc.recv(1024) # 打印接收的数据
sc.send(agent+'\n')
return sc.recv(1024)

def tryChar():
raw = '0000000000000000'
allstr = "Connection for mission: {}, your mission's flag is: "
while True:
# 除去 {},
req = 15 - (len(allstr)-2) % 16 # 求出要填充的长度
raw_t = raw[:req]

raw_t_concat = allstr.format(raw_t).replace('-','{',-1).replace('=','}',-1)
print(raw_t_concat)
rex = re.compile('.{1,16}') # 对明文进行分组
tmp = re.findall(rex, raw_t_concat)
print(tmp)
rs_len = len(tmp) # 分组长度
rs = tmp[-1] # 要爆破的块(最后一块)
print('正在爆破:%s' % rs)
# 重新连接 发送Agent = raw_t
firstRs = sendAgent(raw_t)
block = re.findall('.{32}',firstRs)[rs_len-1] # 拿到密文对应需要爆破的明文分组的一块

# 爆破
for cs in range(33,128):
sc.recv(1024)
sc.send(rs+ chr(cs)+'\n')
rss = sc.recv(1024).split('\n') [0]
if rss==block:
c = chr(cs)
allstr+=c.replace('{','-').replace('}', '=')
break
if c=='}':
print('over!')
return
tryChar()

  最后得到flag:DDCTF{87fa2cd38a4259c29ab1af39995be81a}
  
  
  
  
  
  
  
  
  
  
  
  
  

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×